Skip to content

Commit

Permalink
fix: correct spacing in Mutation Orchestration Design.md
Browse files Browse the repository at this point in the history
  • Loading branch information
dupdob committed Feb 3, 2024
1 parent 6fbb419 commit 629a8c7
Showing 1 changed file with 27 additions and 26 deletions.
53 changes: 27 additions & 26 deletions docs/technical-reference/Mutation Orchestration Design.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Used by main logic:
- `InStaticValue`: property, `true` when inside a static constructor or initializer. This is used to identify static mutants.
- `MustInjectCoverageLogic`: property, `true` when Stryker needs to inject runtime static markers for proper static mutations detection (it depends on Stryker configuration)
- `FindHandler(...)`: returns an `NodeOrchestrator` instance able to handle the provided `SyntaxNode` type.
###Mutations management
### Mutations management
Orchestrator will use this method to control at which syntax level mutations will be inserted and to inject mutations for compatible syntax node.

Methods useful for orchestrators:
Expand All @@ -132,19 +132,19 @@ Methods useful for orchestrators:
Used by main logic:
- `GenerateMutantsForNode(...)`: returns the list of `Mutants` for the provided node using the configured list of mutators.
- `FilterMutator(...)`: adjusts the list of active mutators ‘in flight’. This method is used when parsing Stryker Comments.
###Implementation details : `MutationStore`
### Implementation details : `MutationStore`
An important part of mutation context is the `MutationStore` class, which is in charge of storing generated mutations until they are injected.
It uses an internal stacks of stores, where each entry matches a syntax level (member, block, statement, expression, memberAccess). Note that the stack may go deep thanks to local functions and lambdas/anonymous functions.
An entry is pushed on the stack when an orchestrator calls `MutationContext.Enter(...) ` which is popped when `MutationContext.Leave()`is called (the algorithm compresses empty levels to keep the stack as small as possible.
The store automatically promotes any pending mutations to the next higher level when leaving it, or flag them as compilation error if promotion is not possible.

It also implements injection method that generates mutated version of syntax nodes (expression, statement or block) enclosed in the appropriate mutation switching construct: ternary operator(s) for expressions and if statement(s) for statements and blocks.

##MutantPlacer class
## MutantPlacer class
`MutantPlacer` main responsibility is to ensure any code modification may be rolled back. As a reminder, Stryker assumes the mutation phase will result in compilation errors. When these are detected during compilation, Stryker will try to locate the code transformation that is the cause of this compilation error and undo it. Alas, this is more complex than simply ‘putting back the original code’. Using such a coarse approach would result in rolling back viable mutations due to the removal of any inner modifications.
Instead, Stryker relies on code injection engines that provides a method to revert the change(s) made.

###Injection engines
### Injection engines

The `MutantPlacer` class implements the infrastructure for this logic, via a strategy pattern (again). Any class that modifies the source code must implement `IInstrumentCode` which exposes two members:
1. `string IntstrumentEngineId {get…}` which provides the (unique) engine identifier
Expand All @@ -154,10 +154,10 @@ It must also be registered with the `MutantPlacer class` (via `MutantPlacer.Regi
Note that there is no signature for the injection part, only the removal. Reason is twofold:
1. Engines are task specific and there is no identified situation where Stryker would benefit from a ‘generic’ injection mechanism
2. This allows method signature to better match the use case(s).
###Injection methods
### Injection methods
The orchestration logic use a shared `MutantPlacer` during the mutation process of one project. This instance stores the unique namespace name that is used by the mutation switching logic for this project. So injection methods related to mutation switching are instance methods, the others are often static.
MutantPlacer aggregates a few injection engines that are useful across the mutation logic. Those can be used via `MutantPlacer`’s methods.
####Compilation helpers
#### Compilation helpers
Those engines inject code that are used to reduce the number of compilation errors. Indeed, some mutants alter the control flow which can lead to using non initialized variables, or failing to return a value. So Stryker injects statements to limit the risk of such an event.
- `AddEndingReturn(...)`: returns a copy of the provided `BlockSyntax` with a `return default` statement added at the end. This method does not add a `return` if:
- this is an iteration method (`yield` statement)
Expand All @@ -168,16 +168,16 @@ Those engines inject code that are used to reduce the number of compilation erro
- `PlaceStaticContextMarker(...)`: injects the logic used by Stryker to track _static context_ (i.e. code that is run during some static initialization). It relies on a `using` expression. This method supports blocks and expressions.
This method must be used to mark any static construct, disregarding mutation.
- `InjectOutParametersInitialization(...)`: adds statements to initialize to default value any out parameters.
####Switching logic injectors
#### Switching logic injectors
Those engines inject mutations and the switching logic (`if` or `?: `) to control them.
- `PlaceStatementControlledMutations(…)`: build an `if` statement with the provided mutation as the `true` condition and the provided statement/bloc has the `false` condition. The `if` statement will be chained if there are more than one mutation.
- `PlaceExpressionControlledMutations`: build a ternary operator (`?:`) expression with the provided mutation as the `true` condition and the provided expression has the `false` condition . The conditional expression will be chained if there are more than one mutation.
####Rollback helpers
#### Rollback helpers
Those engines are used during to remove mutations (and helpers) that cause compilation errors.
- `RemoveMutant(...)`: returns a syntax node with the mutation removed.
- `RequiresRemovingChildMutations(...)`: returns true if the syntax node contains an injection that can be removed without removing any child mutation/injection first.
- `FindAnnotations(...)`: returns information regarding injection in the provided syntax node, such as used engine and mutant id (if relevant)
##Node orchestrators
## Node orchestrators
You can browse this list for figuring out how each syntax structure is mutated, which is useful when trying to understand why Stryker does not give the result you expected or fails. Remember that Stryker tries out orchestrators in declaration order until one `CanHandle` returns true; so make sure that the most specific orchestrators are declared first.
Also, you may want to check `DoNotMutateOrchestrator` when trying to understand why some syntax constructs are not mutated: this may be deliberately.
You can start by looking at `CSharpMutantOrchestrator.BuildOrchestratorList()` to see which orchestrators are used and their relative declaration order.
Expand Down Expand Up @@ -216,12 +216,12 @@ This class is used for:
- `InitializerExpressionSyntax`: for non empty array declarations.
- `AssignmentExpressionSyntax`( x=value) cannot be part of a ternary operation.

####MemberDefinitionOrchestrator
#### MemberDefinitionOrchestrator
`MemberDefinitionOrchestrator<T>` is a generic (base) class to help implement orchestrator for SyntaxNode that define class members. It implements:
- `PrepareContext(...)`: creates a `MutantControl.Member` context.
- `RestoreContext()`: leaves the current context

####BaseFunctionOrchestrator
#### BaseFunctionOrchestrator
`BaseFunctionOrchestrator<T>` is an abstract base class helping orchestrate methods, functions (locals and anonymous) as well as accessors. This class will convert from expression body to block body form if any mutation requires it. This class also implements `IInstrumentCode` and provides the adequate roll back logic for the compilation phase. It inherits from `MemberDefinitionOrchestrator<T>`.
Child classes must implement abstract methods that abstract some common operations:
- `ParameterList(T)`: must return the `ParameterListSyntax` instance listing all the provided method’s parameters. Returns an empty list if there are none.
Expand All @@ -235,7 +235,7 @@ Finally, this class implements an injection engine:
- `RemoveInstrumentation(...)`: reverts the conversion. Note that this method requires that the body contains only a `return` or expression statement.
### Specialized orchestrators
These orchestrators are specific for certain syntax node types.
####MemberAccessOrchestrator
#### MemberAccessOrchestrator
`MemberAccessOrchestrator<T>`is used for ‘MemberAccess’ kind of expression, which is defined a lower level than expression. This is because while those syntax constructs inherit from `ExpressionSyntax`, they cannot be replaced by other kind of expressions, which means that Stryker cannot use a conditional operator (`?:`) to mutate them in place; it must happen at a higher hierarchical level in the expression.
It is declared for `MemberAccessExpressionSyntax`, `MemberBindingExpressionSyntax` and `SimpleNameSyntax`.

Expand All @@ -244,7 +244,7 @@ This class implements:
- `PrepareContext(...)`: signals that this is a `MutantControl.MemberAccess` syntax level.
- `RestoreContext()`: leaves the current context.

####DoNotMutateOrchestrator
#### DoNotMutateOrchestrator
`DoNotMutateOrchestrator<T>` is a generic class that can used to ensure a type of `SyntaxNode` will not be mutated (including the node’s children). Its constructor accepts an optional predicate to confirm if a node should be mutated or not.
This class is used for several constructs:
- `AttributeListSyntax`: Attributes must be defined at compile time.
Expand All @@ -258,56 +258,57 @@ This class implements:
- `CanHandle(...)`: forwards the call to the predicate (if one was provided at construction), returns true otherwise.
- `Mutate(...` : returns the unmodified syntax node.

####InvocationExpressionOrchestrator
#### InvocationExpressionOrchestrator
`InvocationExpressionOrchestrator` is used for methods and functions invocations (`xxxx(...)`).
This class implements:
- `PrepareContext(...)`: enters an `Expression` context, except if the `Expression` is in the form of `.name` (or `.name.otherName...'`). Then it enters a memberAccess expression.
- `RestoreContext()`: leaves the current context.
- `StoreMutations(...)`: Injects mutations at the current level, except if there are declarations in the argument list. Then inject mutations at the next higher block level.

####StaticFieldDeclarationOrchestrator
#### StaticFieldDeclarationOrchestrator
`StaicFieldDeclarationOrchestrator` is used for static fields order to inject static marking logic used for coverage capture. It inherits from `MemberDefinitionOrchestrator`.
This class implements:
- `CanHandle(...)`: returns true for static fields with at least one initializer.
- `PrepareContext(...)`: creates a static context.
- `InjectMutations(...)`: Actually injects mutations (call base implementation) then injects a static marker.

####StaticConstructorOrchestrator
#### StaticConstructorOrchestrator
`StaticConstructorOrchestrator ` is used for class constructors order to inject static marking logic used for coverage capture. It inherits from `BasMethodOrchestrator`.
This class implements:
- `CanHandle(...)`: returns true for class constructor.
- `PrepareContext(...)`: creates a static context.
- `InjectMutations(...)`: Actually injects mutations (call base implementation) then injects a static marker.

####ExpressionBodiedPropertyOrchestrator
#### ExpressionBodiedPropertyOrchestrator
`ExpressionBodiedPropertyOrchestrator` deals with properties expressed as simple expressions, such as
int MyProperty => 4;
or with static properties with an initializer, such as
static int MyProperty { get {...) set{...} = MyInitializer(4);
It inherits from `BaseFunctionOrchestrator`.
This class implements:
- `OrchestrateChildrenMutation(...)`: ensure the node `Initializer` is mutated with a static context (if needed).
####LocalFunctionOrchestrator
#### LocalFunctionOrchestrator
This class handles `LocalFunctionStatementSyntax`. It inherits from `BaseFunctionOrchestration` and implements all required abstract function.
####AnonymousFunctionExpressionOrchestrator
#### AnonymousFunctionExpressionOrchestrator
This class handles `AnonymousFunctionExpressionSyntax `. It inherits from `BaseFunctionOrchestration` and implements all required abstract function.
####BaseMethodDeclarationOrchestrator
#### BaseMethodDeclarationOrchestrator
This class handles `BaseMethodDeclarationSyntax `. It inherits from `BaseFunctionOrchestration` and implements all required abstract function.
####AccessorSyntaxOrchestrator
#### AccessorSyntaxOrchestrator
This class handles `AccessorDeclarationSyntax `. It inherits from `BaseFunctionOrchestration` and implements all required abstract function.
####LocalDeclarationOrchestrator
#### LocalDeclarationOrchestrator
This class handles `LocalDeclarationStatement` (i.e. local variables declaration). It implements:
- `InjectMutations(...)`: it does not inject mutations, as they should be controlled at the scope level: using an `if` statement here would change the scope of the variable.
####InvocationExpressionSyntax
#### InvocationExpressionSyntax
This class handles method/function invocation. It implements the following methods:
- `InjectMutation(...)`: it injects mutations normally in the current expression.
- `PrepareContext(...)`: declares an expression context, unless the invoked name is in the `MemberBindingExpression` form, I.e. starts with a dot `.` as those cannot be used in a condition expression. Then it declares a member access context.
- `RestoreContext()`: restores the context.
####BlockOrchestrator
#### BlockOrchestrator
This class handles `BlockStatementSyntax` nodes. It implements:
- `PrepareContext(...)`: declares a **new** block context. Having a new block is used for scoped mutant filtering via Stryker comments
- `RestoreContext()`: restore the context
- `InjectMutations(...)`: injects mutations at the block level. That is the block is mutated and mutation switching is done via one or more `if` statements.
####SyntaxNodeOrchestrator
#### SyntaxNodeOrchestrator
This class handles any `SyntaxNode`. As such it provides the default behavior, that is no mutation. It implements:
- `GenerateMutationForNode(...)` : it does not try to generate mutations for this node and returns an empty list.
- `GenerateMutationForNode(...)` : it does not try to generate mutations for this node and returns an empty list.

0 comments on commit 629a8c7

Please sign in to comment.