mutant 0.8.19 → 0.8.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +46 -8
  3. data/Changelog.md +4 -0
  4. data/Gemfile.lock +10 -12
  5. data/README.md +22 -250
  6. data/config/flay.yml +1 -1
  7. data/docs/concurrency.md +39 -0
  8. data/docs/known-problems.md +44 -0
  9. data/docs/limitations.md +50 -0
  10. data/docs/mutant-minitest.md +147 -0
  11. data/docs/mutant-rspec.md +62 -0
  12. data/docs/nomenclature.md +82 -0
  13. data/docs/reading-reports.md +74 -0
  14. data/lib/mutant.rb +4 -3
  15. data/lib/mutant/env.rb +2 -2
  16. data/lib/mutant/expression/namespace.rb +3 -1
  17. data/lib/mutant/runner/sink.rb +2 -2
  18. data/lib/mutant/timer.rb +21 -0
  19. data/lib/mutant/version.rb +1 -1
  20. data/mutant-minitest.gemspec +22 -0
  21. data/mutant.gemspec +4 -5
  22. data/spec/integration/mutant/corpus_spec.rb +1 -7
  23. data/spec/integration/mutant/minitest_spec.rb +10 -0
  24. data/spec/integration/mutant/rspec_spec.rb +1 -1
  25. data/spec/integrations.yml +14 -0
  26. data/spec/shared/framework_integration_behavior.rb +8 -5
  27. data/spec/support/corpus.rb +20 -14
  28. data/spec/unit/mutant/clock_monotonic_spec.rb +52 -0
  29. data/spec/unit/mutant/env_spec.rb +2 -2
  30. data/spec/unit/mutant/expression/namespace/recursive_spec.rb +1 -1
  31. data/spec/unit/mutant/integration/rspec_spec.rb +1 -1
  32. data/spec/unit/mutant/reporter/cli_spec.rb +1 -1
  33. data/spec/unit/mutant/runner/sink_spec.rb +1 -1
  34. data/test_app/Gemfile.minitest +6 -0
  35. data/test_app/{Gemfile.rspec3.6 → Gemfile.rspec3.8} +2 -2
  36. data/test_app/test/unit/test_app/literal_test.rb +16 -0
  37. metadata +23 -26
  38. data/spec/rcov.opts +0 -7
  39. data/spec/support/rb_bug.rb +0 -18
  40. data/test_app/Gemfile.rspec3.4 +0 -7
  41. data/test_app/Gemfile.rspec3.5 +0 -7
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  threshold: 16
3
- total_score: 1364
3
+ total_score: 1395
@@ -0,0 +1,39 @@
1
+ Concurrency
2
+ ===========
3
+
4
+ By default, mutant will test mutations in parallel by running up
5
+ to one process for each core on your system. You can control the
6
+ number of processes created using the `-j/--jobs` argument.
7
+
8
+ Mutant forks a new process for each mutation to be tested to prevent side
9
+ affects in your specs and the lack of thread safety in integrations from
10
+ impacting the results.
11
+
12
+ Database
13
+ --------
14
+
15
+ If the code under test relies on a database, you may experience problems
16
+ when running mutant because of conflicting data in the database. For
17
+ example, if you have a test like this:
18
+
19
+ ```ruby
20
+ resource = MyModel.create!(...)
21
+ expect(MyModel.first.name).to eql(resource.name)
22
+ ```
23
+
24
+ It might fail if some other test wrote a record to the `MyModel` table
25
+ at the same time as this test was executed. (It would find the MyModel
26
+ record created by the other test.) Most of these issues can be fixed
27
+ by writing more specific tests. Here is a concurrent safe version of
28
+ the same test:
29
+
30
+ ```
31
+ resource = MyModel.create!(...)
32
+ expect(MyModel.find_by_id(m.id).name).to eql(resource.name)
33
+ ```
34
+
35
+ An alternative is to try wrapping each test into an enclosing transaction.
36
+
37
+ Note that some databases, SQLite in particular, are not designed for
38
+ concurrent access and will fail if used in this manner. If you are
39
+ using SQLite, you should set the `--jobs` to 1.
@@ -0,0 +1,44 @@
1
+ Known Problems
2
+ ==============
3
+
4
+ Mutations with Infinite Runtimes
5
+ ---------------------------------
6
+
7
+ Occasionally mutant will produce a mutation with an infinite runtime. When this happens
8
+ mutant will look like it is running indefinitely without killing a remaining mutation. To
9
+ avoid mutations like this, consider adding a timeout around your tests. For example, in
10
+ RSpec you can add the following to your `spec_helper`:
11
+ ```ruby
12
+ config.around(:each) do |example|
13
+ Timeout.timeout(5, &example)
14
+ end
15
+ ```
16
+ which will fail specs which run for longer than 5 seconds.
17
+
18
+ The Crash / Stuck Problem (MRI)
19
+ -------------------------------
20
+
21
+ Mutations generated by mutant can cause MRI to enter VM states its not prepared for.
22
+ All MRI versions > 1.9 and < 2.2.1 are affected by this depending on your compiler flags,
23
+ compiler version, and OS scheduling behavior.
24
+
25
+ This can have the following unintended effects:
26
+
27
+ * MRI crashes with a segfault. Mutant kills each mutation in a dedicated fork to isolate
28
+ the mutations side effects when this fork terminates abnormally (segfault) mutant
29
+ counts the mutation as killed.
30
+
31
+ * MRI crashes with a segfault and gets stuck when handling the segfault.
32
+ Depending on the number of active kill jobs mutant might appear to continue normally until
33
+ all workers are stuck into this state when it begins to hang.
34
+ Currently mutant must assume that your test suite simply not terminated yet as from the outside
35
+ (parent process) the difference between a long running test and a stuck MRI is not observable.
36
+ Its planned to implement a timeout enforced from the parent process, but ideally MRI simply gets fixed.
37
+
38
+ References:
39
+
40
+ * [MRI fix](https://github.com/ruby/ruby/commit/8fe95fea9d238a6deb70c8953ceb3a28a67f4636)
41
+ * [MRI backport to 2.2.1](https://github.com/ruby/ruby/commit/8fe95fea9d238a6deb70c8953ceb3a28a67f4636)
42
+ * [Mutant issue](https://github.com/mbj/mutant/issues/265)
43
+ * [Upstream bug redmine](https://bugs.ruby-lang.org/issues/10460)
44
+ * [Upstream bug github](https://github.com/ruby/ruby/pull/822)
@@ -0,0 +1,50 @@
1
+ Limitations
2
+ ===========
3
+
4
+ Subject
5
+ -------
6
+
7
+ Mutant cannot emit mutations for some subjects.
8
+
9
+ * methods defined within a closure. For example, methods defined using `module_eval`, `class_eval`,
10
+ `define_method`, or `define_singleton_method`:
11
+
12
+ ```ruby
13
+ class Example
14
+ class_eval do
15
+ def example1
16
+ end
17
+ end
18
+
19
+ module_eval do
20
+ def example2
21
+ end
22
+ end
23
+
24
+ define_method(:example3) do
25
+ end
26
+
27
+ define_singleton_method(:example4) do
28
+ end
29
+ end
30
+ ```
31
+
32
+ * singleton methods not defined on a constant or `self`
33
+
34
+ ```ruby
35
+ class Foo
36
+ def self.bar; end # ok
37
+ def Foo.baz; end # ok
38
+
39
+ myself = self
40
+ def myself.qux; end # cannot mutate
41
+ end
42
+ ```
43
+
44
+ * methods defined with eval:
45
+
46
+ ```ruby
47
+ class Foo
48
+ class_eval('def bar; end') # cannot mutate
49
+ end
50
+ ```
@@ -0,0 +1,147 @@
1
+ mutant-minitest
2
+ ===============
3
+
4
+ Before starting with mutant its recommended to understand the
5
+ [nomenclature](/docs/nomenclature.md).
6
+
7
+ ## Setup
8
+
9
+ To add mutant to your minitest code base you need to:
10
+
11
+ 1. Add `mutant-minitest` as development dependency to your `Gemfile` or `.gemspec`
12
+
13
+ This may look like:
14
+
15
+ ```ruby
16
+ # A gemfile
17
+ gem 'mutant-minitest'
18
+ ```
19
+
20
+ 2. Add `require 'mutant/minitest/coverage'` to your test environment (example to your `test/test_helper.rb`)
21
+
22
+ Example:
23
+
24
+ ```ruby
25
+ require 'minitest/autorun'
26
+ require 'mutant/minitest/coverage'
27
+
28
+ class YourTestBaseClass < MiniTest::Test
29
+ # ...
30
+ ```
31
+
32
+ 3. Add `.cover` call sides to your test suite to mark them as eligible for killing mutations in subjects.
33
+
34
+ Example:
35
+
36
+ ```ruby
37
+ class YourLibrarySomeClassTest < YourTestBaseClass
38
+ cover 'YourLibrary::SomeClass*' # tells mutant which subjects this tests should cover
39
+ # ...
40
+ ```
41
+
42
+ 4. Run mutant against the minitest integration
43
+
44
+ ```sh
45
+ bundle exec mutant --include lib --require 'your_library.rb' --use minitest -- 'YourLibrary*'
46
+ ```
47
+
48
+ ## Run through example
49
+
50
+ This uses [mbj/auom](https://github.com/mbj/auom) a small library that
51
+ has 100% mutation coverage. Its tests execute very fast and do not have any IO
52
+ so its a good playground example to interact with.
53
+
54
+ All the setup described above is already done.
55
+
56
+ ```sh
57
+ git clone https://github.com/mbj/auom
58
+ bundle install # gemfile references mutant-minitest already
59
+ bundle exec mutant --include lib --require auom --use minitest -- 'AUOM*'
60
+ ```
61
+
62
+ This prints a report like:
63
+
64
+ ```sh
65
+ Mutant configuration:
66
+ Matcher: #<Mutant::Matcher::Config match_expressions: [AUOM*]>
67
+ Integration: Mutant::Integration::Minitest
68
+ Jobs: 8
69
+ Includes: ["lib"]
70
+ Requires: ["auom"]
71
+ Subjects: 23
72
+ Mutations: 1003
73
+ Results: 1003
74
+ Kills: 1003
75
+ Alive: 0
76
+ Runtime: 9.68s
77
+ Killtime: 3.80s
78
+ Overhead: 154.30%
79
+ Mutations/s: 103.67
80
+ Coverage: 100.00%
81
+ ```
82
+
83
+ Now lets try adding some redundant (or unspecified) code:
84
+
85
+ ```sh
86
+ patch -p1 <<'PATCH'
87
+ --- a/lib/auom/unit.rb
88
+ +++ b/lib/auom/unit.rb
89
+ @@ -170,7 +170,7 @@ module AUOM
90
+ # TODO: Move defaults coercions etc to .build method
91
+ #
92
+ def self.new(scalar, numerators = nil, denominators = nil)
93
+ - scalar = rational(scalar)
94
+ + scalar = rational(scalar) if true
95
+
96
+ scalar, numerators = resolve([*numerators], scalar, :*)
97
+ scalar, denominators = resolve([*denominators], scalar, :/)
98
+ PATCH
99
+ ```
100
+
101
+ Running mutant again prints the following:
102
+
103
+ ```
104
+ AUOM::Unit.new:/home/mrh-dev/auom/lib/auom/unit.rb:172
105
+ - minitest:AUOMTest::ClassMethods::New#test_reduced_unit
106
+ - minitest:AUOMTest::ClassMethods::New#test_normalized_denominator_scalar
107
+ - minitest:AUOMTest::ClassMethods::New#test_normalized_numerator_unit
108
+ - minitest:AUOMTest::ClassMethods::New#test_incompatible_scalar
109
+ - minitest:AUOMTest::ClassMethods::New#test_integer
110
+ - minitest:AUOMTest::ClassMethods::New#test_sorted_numerator
111
+ - minitest:AUOMTest::ClassMethods::New#test_unknown_unit
112
+ - minitest:AUOMTest::ClassMethods::New#test_rational
113
+ - minitest:AUOMTest::ClassMethods::New#test_normalized_numerator_scalar
114
+ - minitest:AUOMTest::ClassMethods::New#test_sorted_denominator
115
+ - minitest:AUOMTest::ClassMethods::New#test_normalized_denominator_unit
116
+ evil:AUOM::Unit.new:/home/mrh-dev/auom/lib/auom/unit.rb:172:cd9ee
117
+ @@ -1,9 +1,7 @@
118
+ def self.new(scalar, numerators = nil, denominators = nil)
119
+ - if true
120
+ - scalar = rational(scalar)
121
+ - end
122
+ + scalar = rational(scalar)
123
+ scalar, numerators = resolve([*numerators], scalar, :*)
124
+ scalar, denominators = resolve([*denominators], scalar, :/)
125
+ super(scalar, *[numerators, denominators].map(&:sort)).freeze
126
+ end
127
+ -----------------------
128
+ Mutant configuration:
129
+ Matcher: #<Mutant::Matcher::Config match_expressions: [AUOM*]>
130
+ Integration: Mutant::Integration::Minitest
131
+ Jobs: 8
132
+ Includes: ["lib"]
133
+ Requires: ["auom"]
134
+ Subjects: 23
135
+ Mutations: 1009
136
+ Results: 1009
137
+ Kills: 1008
138
+ Alive: 1
139
+ Runtime: 9.38s
140
+ Killtime: 3.47s
141
+ Overhead: 170.06%
142
+ Mutations/s: 107.60
143
+ Coverage: 99.90%
144
+ ```
145
+
146
+ This shows mutant detected the redundant alive conditional.
147
+ Feel free to also remove some tests. Or do other modifications to either tests or code.
@@ -0,0 +1,62 @@
1
+ mutant-rspec
2
+ ============
3
+
4
+ The integration into rspec.
5
+
6
+ Install `mutant-rspec` and use the `--use rspec` switch in your mutant command line.
7
+
8
+ ```sh
9
+ bundle exec mutant --include lib --require 'your_code' --use rspec -- 'YourCode*'
10
+ ```
11
+
12
+ Examples
13
+ --------
14
+
15
+ ```
16
+ cd virtus
17
+ # Run mutant on virtus namespace
18
+ bundle exec mutant --include lib --require virtus --use rspec Virtus*
19
+ # Run mutant on specific virtus class
20
+ bundle exec mutant --include lib --require virtus --use rspec Virtus::Attribute
21
+ # Run mutant on specific virtus class method
22
+ bundle exec mutant --include lib --require virtus --use rspec Virtus::Attribute.build
23
+ # Run mutant on specific virtus instance method
24
+ bundle exec mutant --include lib --require virtus --use rspec Virtus::Attribute#type
25
+ ```
26
+
27
+ Test-Selection
28
+ --------------
29
+
30
+ Mutation testing is slow. The key to making it fast is selecting the correct
31
+ set of tests to run. Mutant currently supports the following built-in
32
+ strategy for selecting tests/specs:
33
+
34
+ Mutant uses the "longest rspec example group descriptions prefix match" to
35
+ select the tests to run.
36
+
37
+ Example for a subject like `Foo::Bar#baz` it will run all example groups with
38
+ description prefixes in `Foo::Bar#baz`, `Foo::Bar` and `Foo`. The order is
39
+ important, so if mutant finds example groups in the current prefix level,
40
+ these example groups *must* kill the mutation.
41
+
42
+ Rails
43
+ -------
44
+
45
+ To mutation test Rails models with rspec, comment out `require 'rspec/autorun'`
46
+ from your `spec_helper.rb` file. Having done so you should be able to use
47
+ commands like the following:
48
+
49
+ ```sh
50
+ RAILS_ENV=test bundle exec mutant -r ./config/environment --use rspec User
51
+ ```
52
+
53
+ Passing in RSpec Options
54
+ ------------------------
55
+
56
+ **NOTE: Experimental**
57
+
58
+ You can control some aspects of RSpec using the `SPEC_OPTS` environment variable as usual. If you want mutant to only pay attention to specs in a certain directory, you can run
59
+
60
+ ```sh
61
+ SPEC_OPTS="--pattern spec/subdir_only/**/*_spec.rb" bundle exec mutant --use rspec SomeClass
62
+ ```
@@ -0,0 +1,82 @@
1
+ Nomenclature
2
+ ============
3
+
4
+ The following explains several nouns you may experience in mutant's documentation.
5
+ It's a good idea to familiarize yourself before moving on.
6
+
7
+ ## AST
8
+
9
+ Acronym for [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree)
10
+ and the level of abstraction mutant operates on.
11
+
12
+ ## Subject
13
+
14
+ An addressable piece of code to be targeted for mutation testing.
15
+
16
+ Mutant currently supports the following subjects:
17
+
18
+ * Instance methods
19
+ * Singleton (class) methods
20
+
21
+ Other subjects (constants, class bodies for DSLs, ...) are possible but aren't
22
+ implemented in the OSS version.
23
+
24
+ ## Mutation operator
25
+
26
+ A transformation applied to the AST of a subject. Mutant knows the following high level operator
27
+ classes:
28
+
29
+ * Semantic Reduction
30
+ * Orthogonal Replacement
31
+ * [Noop](#neutral-noop-tests)
32
+
33
+ An exhaustive list can be found in the [mutant-meta](https://github.com/mbj/mutant/tree/master/meta)
34
+ subdirectory of the source.
35
+
36
+ ## Mutation
37
+
38
+ The result of applying a mutation operator to the AST of a subject. A mutation represents a
39
+ hypothesis that ideally gets falsified by the tests.
40
+
41
+ ## Insertion
42
+
43
+ The process of inserting a mutation into the runtime environment.
44
+ Mutant currently supports insertion via dynamically created monkeypatches.
45
+
46
+ Other insertion strategies (such as "boot time") are possible but aren't implemented
47
+ in the OSS version.
48
+
49
+ ## Isolation
50
+
51
+ The attempt to isolate the (side) effects of killing a mutation via an integration
52
+ to prevent a mutation leaking into adjacent concurrent, or future mutations.
53
+
54
+ Examples of sources for leaks are
55
+
56
+ * Global variable writes
57
+ * Thread local writes
58
+ * DB State
59
+ * File system
60
+
61
+ Natively, mutant offers fork isolation. This works for any state within the executing
62
+ Ruby process. For all state reachable via IO, it's the test author's responsibility to
63
+ provide proper isolation.
64
+
65
+ ## Integration
66
+
67
+ The method used to determine if a specific inserted mutation is covered by tests.
68
+
69
+ Currently mutant supports integrations for:
70
+
71
+ * [mutant-rspec](/docs/mutant-rspec.md) for [rspec](https://rspec.info)
72
+ * [mutant-minitest](/docs/mutant-minitest.md) for [minitest](https://github.com/seattlerb/minitest)
73
+
74
+ ## Report
75
+
76
+ Mutant currently provides two different reporters:
77
+
78
+ * Progress (printed during mutation testing).
79
+ * Summary (printed at the end of a finished analysis run)
80
+
81
+ A reporter producing a machine readable report does not exist in the OSS version
82
+ at the time of writing this documentation.
@@ -0,0 +1,74 @@
1
+ Reading Reports
2
+ ===============
3
+
4
+ Mutation output is grouped by selection groups. Each group contains three sections:
5
+
6
+ 1. An identifier for the current group.
7
+
8
+ **Format**:
9
+
10
+ ```text
11
+ [SUBJECT EXPRESSION]:[SOURCE LOCATION]:[LINENO]
12
+ ```
13
+
14
+ **Example**:
15
+
16
+ ```text
17
+ Book#add_page:Book#add_page:/home/dev/mutant-examples/lib/book.rb:18
18
+ ```
19
+
20
+ 2. A list of specs that mutant ran to try to kill mutations for the current group.
21
+
22
+ **Format**:
23
+
24
+ ```text
25
+ - [INTEGRATION]:0:[SPEC LOCATION]:[SPEC DESCRIPTION]
26
+ - [INTEGRATION]:1:[SPEC LOCATION]:[SPEC DESCRIPTION]
27
+ ```
28
+
29
+ **Example**:
30
+
31
+ ```text
32
+ - rspec:0:./spec/unit/book_spec.rb:9/Book#add_page should return self
33
+ - rspec:1:./spec/unit/book_spec.rb:13/Book#add_page should add page to book
34
+ ```
35
+
36
+ 3. A list of unkilled mutations diffed against the original unparsed source
37
+
38
+ **Format**:
39
+
40
+ ```text
41
+ [MUTATION TYPE]:[SUBJECT EXPRESSION]:[SOURCE LOCATION]:[SOURCE LINENO]:[IDENTIFIER]
42
+ [DIFF]
43
+ -----------------------
44
+ ```
45
+
46
+ - `[MUTATION TYPE]` will be one of the following:
47
+ - `evil` - a mutation of your source was not killed by your tests
48
+ - `neutral` your original source was injected and one or more tests failed
49
+ - `[IDENTIFIER]` - Unique identifier for this mutation
50
+
51
+ **Example**:
52
+
53
+ ```diff
54
+ evil:Book#add_page:Book#add_page:/home/dev/mutant-examples/lib/book.rb:18:01f69
55
+ @@ -1,6 +1,6 @@
56
+ def add_page(page)
57
+ - @pages << page
58
+ + @pages
59
+ @index[page.number] = page
60
+ self
61
+ end
62
+ -----------------------
63
+ evil:Book#add_page:Book#add_page:/home/dev/mutant-examples/lib/book.rb:18:b1ff2
64
+ @@ -1,6 +1,6 @@
65
+ def add_page(page)
66
+ - @pages << page
67
+ + self
68
+ @index[page.number] = page
69
+ self
70
+ end
71
+ -----------------------
72
+ ```
73
+
74
+ At this time no machine readable output exists in the opensourced versions of mutant.