mutant 0.8.19 → 0.8.20

Sign up to get free protection for your applications and to get access to all the features.
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.