-
Notifications
You must be signed in to change notification settings - Fork 1
Mutation Testing Elements - Add support for frameworks that export in MTE #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
bfff83f
feat: mutation displays operation if no description provided
SecretSheppy 2eacbff
chore: add cpp language
SecretSheppy acbc65a
fix: resolved issue with conflict calculation
SecretSheppy 9161c82
feat: add LessThan method to Range
SecretSheppy af41dc3
feat: add early version of mtelib and mull fw
SecretSheppy fc65bf2
refactor: add GetDescription() method to Mutation
SecretSheppy 2f96171
feat: add description generation
SecretSheppy c14d640
refactor: add more precise message for deletion mutations
SecretSheppy 0b6fe92
feat: add broken mutant extractor
SecretSheppy fba30ea
test: add testing for mutations package
SecretSheppy 0f96216
fix: removed automatically failing incomplete test
SecretSheppy e1bfbe5
feat: add conflict merging methods
SecretSheppy 4eb6ae7
feat: add Pending and Ignored statuses
SecretSheppy 186b23f
refactor: wrapped filters to be half width creating a table like look
SecretSheppy 5752292
refactor: made mtelib internal
SecretSheppy 028f1ab
refactor: attached lineSpan and columnSpan to MutantResult
SecretSheppy edc9bd3
refactor: dropped themeing package
SecretSheppy b5536dc
refactor: swap language icons and add licenses
SecretSheppy 9627d6f
fix: add merge conflicts that conflict with each other
SecretSheppy 7b18f70
refactor: moved progressbar initialization into fwlib
SecretSheppy 35859b8
feat: add progressbar to mull framework
SecretSheppy 3045601
refactor: swapped script import order
SecretSheppy 22360ca
refactor: now uses fwlib.NewProgressbar
SecretSheppy 33edb45
feat: add stryker-net, stryker-js and stryker4s frameworks
SecretSheppy b9697a1
feat: add infection php framework
SecretSheppy 22173e4
feat: remove conflicts that have no mutations
SecretSheppy 79464e1
feat: switches over operation to attempt fixing broken mutations
SecretSheppy e27f7f7
docs: update supported frameworks list and gallery
SecretSheppy ab656e2
refactor: made list primary name of listCmd
SecretSheppy 02fdf60
docs: improved installation instructions
SecretSheppy 50f4a4a
docs: update pitest docs with mvn run command
SecretSheppy e7c779a
test: add mutation sorting by range method
SecretSheppy ebc68cb
fix: corrected errors with broken mutation fixing
SecretSheppy 390917c
fix: getFuncName can no longer return empty string
SecretSheppy 3f2bd7b
docs: add source for mutation descriptions
SecretSheppy ea24a6e
docs: add definition of a broken mutation
SecretSheppy 5bb5167
chore: bump version number
SecretSheppy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| ## Important Information About Infection Support in Marv | ||
|
|
||
| > [!NOTE] | ||
| > See [infection example MTE report](https://dashboard.stryker-mutator.io/reports/github.com/infection/infection/master#mutant) for the mutant dataset used to in examples. | ||
|
|
||
| The Infection framework often exports what Marv views as "broken" mutations. This is because the value of the | ||
| mutations end column exceeds the length of the end line, for example: | ||
|
|
||
| ```json | ||
| { | ||
| "id": "adf6cacd35a4867054da054ee6a6b4a0", | ||
| "mutatorName": "ConcatOperandRemoval", | ||
| "replacement": "'Cannot access to the command application if the command has not been ',", | ||
| "description": "Removes an operand from a string concatenation.\n\n```php\n$x = 'foo' . 'bar';\n```\n\nWill be mutated to:\n\n```php\n$x = 'foo';\n```\n\nAnd:\n\n```php\n$x = 'bar';\n", | ||
| "location": { | ||
| "start": { | ||
| "line": 61, | ||
| "column": 13 | ||
| }, | ||
| "end": { | ||
| "line": 62, | ||
| "column": 84 | ||
| } | ||
| }, | ||
| "status": "Survived" | ||
| } | ||
| ``` | ||
|
|
||
| **Caption:** Mutant taken from [`Command/BaseCommand.php`](https://dashboard.stryker-mutator.io/reports/github.com/infection/infection/master#mutant/Command/BaseCommand.php) that Marv would view as "broken". | ||
|
|
||
| If Marv tries to render a "broken" mutation, like the one shown above, it will panic and fail to render the file. The | ||
| reason this occurs in Marv and not in [Stryker Mutation Testing Elements (MTE)](https://github.com/stryker-mutator/mutation-testing-elements/tree/master) | ||
| is because of the different ways the two system parse files. MTE never splits the source file into lines, and instead | ||
| makes indexes of where newline characters occur, and uses those to read and replace bits of the source. This means that | ||
| when a "broken" mutation is shown by MTE, it will not fail to render the file. However, it will render the mutation | ||
| incorrectly, as shown below. | ||
|
|
||
|  | ||
|
|
||
| As previously stated, if Marv tried to render the above mutation as is, it would panic and fail to show the file. This | ||
| is because Marv does split the source file into a slice of lines, and then performs the replacement substitution on | ||
| the individual lines. | ||
|
|
||
| To correct for this issue with Infection, the [Marv Infection Framework](infection.go) instance will iterate over all | ||
| mutations, and for any mutations where `mutation.End.Char > len(line)`, it then sets `mutation.End.Char = len(line)`. | ||
| In practise, this seems to solve the formatting issues for most cases. The Marv formatted version of the same mutation | ||
| is shown below. | ||
|
|
||
|  |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| package infection | ||
|
|
||
| import ( | ||
| "github.com/SecretSheppy/marv/fwlib" | ||
| "github.com/SecretSheppy/marv/internal/languages" | ||
| "github.com/SecretSheppy/marv/internal/mtelib" | ||
| "github.com/SecretSheppy/marv/internal/mutations" | ||
| "github.com/rs/zerolog/log" | ||
| "gopkg.in/yaml.v3" | ||
| ) | ||
|
|
||
| var meta = fwlib.Meta{ | ||
| Name: "infection", | ||
| Language: languages.Php, | ||
| URL: "https://https://infection.github.io/", | ||
| } | ||
|
|
||
| type YamlConfig struct { | ||
| MTEJson string `yaml:"mte-json"` | ||
| } | ||
|
|
||
| type YamlWrapper struct { | ||
| Cfg *YamlConfig `yaml:"infection"` | ||
| } | ||
|
|
||
| func (y *YamlWrapper) Init() interface{} { | ||
| return &YamlWrapper{Cfg: &YamlConfig{}} | ||
| } | ||
|
|
||
| func (y *YamlWrapper) Load(yml []byte) (bool, error) { | ||
| if err := yaml.Unmarshal(yml, y); err != nil { | ||
| return false, err | ||
| } | ||
| if y.Cfg == nil { | ||
| return false, nil | ||
| } | ||
| return y.Cfg.MTEJson != "", nil | ||
| } | ||
|
|
||
| type Infection struct { | ||
| yml *YamlWrapper | ||
| mte *mtelib.MTE | ||
| } | ||
|
|
||
| func NewInfection() *Infection { | ||
| return &Infection{yml: &YamlWrapper{}} | ||
| } | ||
|
|
||
| func (i *Infection) Meta() *fwlib.Meta { | ||
| return &meta | ||
| } | ||
|
|
||
| func (i *Infection) Yaml() fwlib.FWConfig { | ||
| return i.yml | ||
| } | ||
|
|
||
| func (i *Infection) LoadResults() error { | ||
| log.Info().Msgf("%s - loading results", i.Meta().Name) | ||
| var err error | ||
| i.mte, err = mtelib.NewMTE(i.yml.Cfg.MTEJson) | ||
| return err | ||
| } | ||
|
|
||
| func (i *Infection) TransformResults() error { | ||
| log.Info().Msgf("%s - transforming results", i.Meta().Name) | ||
|
|
||
| bar := fwlib.NewProgressbar(i.mte.RawMutationsCount(), "transforming") | ||
| i.mte.Transform(bar) | ||
| fwlib.FinishProgressbar(bar) | ||
|
|
||
| i.correctLineLengthOverhangs() | ||
| return nil | ||
| } | ||
|
|
||
| // see infection/README.md as to why this method is necessary and for visual examples of it in practise. | ||
| func (i *Infection) correctLineLengthOverhangs() { | ||
| for file, conflicts := range i.mte.Mutations() { | ||
| lines := i.mte.ReadLines(file) | ||
| for _, conflict := range conflicts { | ||
| for _, mutation := range conflict.Mutations { | ||
| endLineLength := len(lines[mutation.End.Line]) | ||
| if mutation.End.Char > endLineLength { | ||
| mutation.End.Char = endLineLength | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func (i *Infection) Mutations() mutations.Mutations { | ||
| return i.mte.Mutations() | ||
| } | ||
|
|
||
| func (i *Infection) ReadLines(file string) ([]string, error) { | ||
| return i.mte.ReadLines(file), nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| package mull | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "regexp" | ||
| "slices" | ||
|
|
||
| "github.com/SecretSheppy/marv/fwlib" | ||
| "github.com/SecretSheppy/marv/internal/languages" | ||
| "github.com/SecretSheppy/marv/internal/mtelib" | ||
| "github.com/SecretSheppy/marv/internal/mutations" | ||
| "github.com/rs/zerolog/log" | ||
| "gopkg.in/yaml.v3" | ||
| ) | ||
|
|
||
| var ( | ||
| meta = fwlib.Meta{ | ||
| Name: "Mull", | ||
| Language: languages.Cpp, | ||
| URL: "https://mull-project.com/", | ||
| } | ||
| function = regexp.MustCompile(`\s*([A-Za-z_0-9]*)\s*\(`) | ||
|
|
||
| // cpp keywords according to https://en.cppreference.com/cpp/keyword | ||
| cppKeyWords = []string{ | ||
| "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto", | ||
| "bitand", "bitor", "bool", "break", "case", "catch", "char", "char8_t", "char16_t", "char32_t", "class", | ||
| "compl", "concept", "const", "consteval", "constexpr", "constinit", "const_cast", "continue", "contract_assert", | ||
| "co_await", "co_return", "co_yield", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", | ||
| "enum", "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if", "inline", "int", | ||
| "long", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", | ||
| "private", "protected", "public", "reflexpr", "register", "reinterpret_cast", "requires", "return", "short", | ||
| "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", | ||
| "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", | ||
| "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq", | ||
| } | ||
| ) | ||
|
|
||
| type YamlConfig struct { | ||
| MTEJson string `yaml:"mte-json"` | ||
| } | ||
|
|
||
| type YamlWrapper struct { | ||
| Cfg *YamlConfig `yaml:"mull"` | ||
| } | ||
|
|
||
| func (y *YamlWrapper) Init() interface{} { | ||
| return &YamlWrapper{Cfg: &YamlConfig{}} | ||
| } | ||
|
|
||
| func (y *YamlWrapper) Load(yml []byte) (bool, error) { | ||
| if err := yaml.Unmarshal(yml, y); err != nil { | ||
| return false, err | ||
| } | ||
| if y.Cfg == nil { | ||
| return false, nil | ||
| } | ||
| return y.Cfg.MTEJson != "", nil | ||
| } | ||
|
|
||
| type Mull struct { | ||
| yml *YamlWrapper | ||
| mte *mtelib.MTE | ||
| } | ||
|
|
||
| func NewMull() *Mull { | ||
| return &Mull{yml: &YamlWrapper{}} | ||
| } | ||
|
|
||
| func (m *Mull) Meta() *fwlib.Meta { | ||
| return &meta | ||
| } | ||
|
|
||
| func (m *Mull) Yaml() fwlib.FWConfig { | ||
| return m.yml | ||
| } | ||
|
|
||
| func (m *Mull) LoadResults() error { | ||
| log.Info().Msgf("%s - loading results", m.Meta().Name) | ||
| var err error | ||
| m.mte, err = mtelib.NewMTE(m.yml.Cfg.MTEJson) | ||
| return err | ||
| } | ||
|
|
||
| func (m *Mull) TransformResults() error { | ||
| log.Info().Msgf("%s - transforming results", m.Meta().Name) | ||
|
|
||
| bar := fwlib.NewProgressbar(m.mte.RawMutationsCount(), "transforming") | ||
| m.mte.Transform(bar) | ||
| fwlib.FinishProgressbar(bar) | ||
|
|
||
| fixed := 0 | ||
| for file, conflicts := range m.mte.Mutations() { | ||
| lines := m.mte.ReadLines(file) | ||
| for _, conflict := range conflicts { | ||
| for _, mutation := range conflict.Mutations { | ||
| if mutation.IsBroken() { | ||
| fixed += attemptBrokenMutationFix(mutation) | ||
| conflict.ResizeToInclude(mutation) | ||
| } | ||
| m.generateDescription(lines, mutation) | ||
| } | ||
| } | ||
| } | ||
| log.Info().Msgf("%s - fixed %d broken mutations", m.Meta().Name, fixed) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // generates a description based off of the descriptions give on the supported mutation operators page of the mull | ||
| // documentation: https://mull.readthedocs.io/en/latest/SupportedMutations.html. | ||
| func (m *Mull) generateDescription(lines []string, mutation *mutations.Mutation) { | ||
| switch mutation.Operation { | ||
| case "cxx_remove_void_call": | ||
| mutation.Description = fmt.Sprintf("Removed call to void function `%s`", getFuncName(lines, mutation)) | ||
| case "cxx_replace_scalar_call": | ||
| mutation.Description = fmt.Sprintf("Replaced call to function `%s` with `42`", getFuncName(lines, mutation)) | ||
| case "negate_mutator": | ||
| mutation.Description = "Negated conditionals" | ||
| case "scalar_value_mutator": | ||
| mutation.Description = "Replaced zeros with `42` and non-zeros with `0`" | ||
| default: | ||
| line := lines[mutation.Start.Line] | ||
| endChar := len(line) - 1 | ||
| if mutation.Start.Line == mutation.End.Line { | ||
| endChar = mutation.End.Char | ||
| } | ||
| original := line[mutation.Start.Char:endChar] | ||
| if mutation.Replacement != "" { | ||
| mutation.Description = fmt.Sprintf("Replaced `%s` with `%s`", original, mutation.Replacement) | ||
| } else { | ||
| mutation.Description = fmt.Sprintf("Removed `%s`", original) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func attemptBrokenMutationFix(mutation *mutations.Mutation) int { | ||
| switch mutation.Operation { | ||
| case "cxx_assign_const", "cxx_init_const", "cxx_remove_void_call", "cxx_replace_scalar_void", "negate_mutator": | ||
| // Operators marv does not currently fix. | ||
| return 0 | ||
| case "cxx_bitwise_not_to_noop", "cxx_minus_to_noop", "cxx_post_dec_to_post_inc", "cxx_pre_dec_to_pre_inc", "cxx_remove_negation", "cxx_gt_to_ge", "cxx_gt_to_le", "cxx_lt_to_ge", "cxx_lt_to_le": | ||
| // Operators that replace a source string length of 1 (~, !, -) | ||
| mutation.End.Line = mutation.Start.Line | ||
| mutation.End.Char = mutation.Start.Char + 1 | ||
| case "cxx_ge_to_gt", "cxx_ge_to_lt", "cxx_le_to_gt", "cxx_le_to_lt", "cxx_post_inc_to_post_dec", "cxx_pre_inc_to_pre_dec": | ||
| // Operators that replace a source string length of 2 (==, <=, /=, ...) | ||
| mutation.End.Line = mutation.Start.Line | ||
| mutation.End.Char = mutation.Start.Char + 2 | ||
| default: | ||
| // Operators that replace a source string of length equal to its replacement string | ||
| mutation.End.Line = mutation.Start.Line | ||
| mutation.End.Char = mutation.Start.Char + len(mutation.Replacement) | ||
| } | ||
| return 1 | ||
| } | ||
|
|
||
| func getFuncName(lines []string, mutation *mutations.Mutation) string { | ||
| match := function.FindAllStringSubmatch(lines[mutation.Start.Line], -1) | ||
| funcStr := "??" | ||
| for _, str := range match { | ||
| // NOTE: takes first non keyword in the replacements string that matches the regex as the function name. | ||
| if !slices.Contains(cppKeyWords, str[1]) && str[1] != "" { | ||
| funcStr = str[1] | ||
| break | ||
| } | ||
| } | ||
| return funcStr | ||
| } | ||
|
|
||
| func (m *Mull) Mutations() mutations.Mutations { | ||
| return m.mte.Mutations() | ||
| } | ||
|
|
||
| func (m *Mull) ReadLines(file string) ([]string, error) { | ||
| return m.mte.ReadLines(file), nil | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.