mutant 0.7.9 → 0.8.0

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +11 -1
  3. data/README.md +5 -12
  4. data/bin/mutant +17 -1
  5. data/config/flay.yml +1 -1
  6. data/config/flog.yml +1 -1
  7. data/config/reek.yml +4 -4
  8. data/config/rubocop.yml +21 -3
  9. data/lib/mutant.rb +15 -61
  10. data/lib/mutant/cli.rb +1 -7
  11. data/lib/mutant/color.rb +2 -2
  12. data/lib/mutant/config.rb +1 -1
  13. data/lib/mutant/expression/method.rb +1 -1
  14. data/lib/mutant/expression/methods.rb +1 -1
  15. data/lib/mutant/expression/namespace.rb +1 -1
  16. data/lib/mutant/mutator/node/send.rb +18 -18
  17. data/lib/mutant/reporter/cli.rb +1 -6
  18. data/lib/mutant/reporter/cli/format.rb +2 -2
  19. data/lib/mutant/reporter/cli/printer.rb +5 -500
  20. data/lib/mutant/reporter/cli/printer/config.rb +32 -0
  21. data/lib/mutant/reporter/cli/printer/env_progress.rb +66 -0
  22. data/lib/mutant/reporter/cli/printer/env_result.rb +23 -0
  23. data/lib/mutant/reporter/cli/printer/mutation_progress_result.rb +37 -0
  24. data/lib/mutant/reporter/cli/printer/mutation_result.rb +151 -0
  25. data/lib/mutant/reporter/cli/printer/status.rb +60 -0
  26. data/lib/mutant/reporter/cli/printer/status_progressive.rb +52 -0
  27. data/lib/mutant/reporter/cli/printer/subject_progress.rb +90 -0
  28. data/lib/mutant/reporter/cli/printer/subject_result.rb +28 -0
  29. data/lib/mutant/reporter/cli/printer/test_result.rb +33 -0
  30. data/lib/mutant/require_highjack.rb +11 -50
  31. data/lib/mutant/version.rb +1 -1
  32. data/lib/mutant/zombifier.rb +79 -37
  33. data/meta/send.rb +1 -1
  34. data/mutant-rspec.gemspec +1 -1
  35. data/mutant.gemspec +3 -3
  36. data/spec/integration/mutant/corpus_spec.rb +2 -2
  37. data/spec/integration/mutant/rspec_spec.rb +5 -15
  38. data/spec/integrations.yml +3 -3
  39. data/spec/spec_helper.rb +1 -0
  40. data/spec/support/corpus.rb +233 -220
  41. data/spec/support/file_system.rb +60 -0
  42. data/spec/support/rb_bug.rb +1 -1
  43. data/spec/support/ruby_vm.rb +82 -0
  44. data/spec/support/shared_context.rb +19 -10
  45. data/spec/unit/mutant/ast_spec.rb +2 -2
  46. data/spec/unit/mutant/cache_spec.rb +22 -0
  47. data/spec/unit/mutant/cli_spec.rb +1 -30
  48. data/spec/unit/mutant/context_spec.rb +1 -0
  49. data/spec/unit/mutant/expression/method_spec.rb +6 -4
  50. data/spec/unit/mutant/parallel/master_spec.rb +1 -1
  51. data/spec/unit/mutant/reporter/cli/printer/config_spec.rb +33 -0
  52. data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +76 -0
  53. data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +35 -0
  54. data/spec/unit/mutant/reporter/cli/printer/mutation_progress_result_spec.rb +23 -0
  55. data/spec/unit/mutant/reporter/cli/printer/mutation_result_spec.rb +110 -0
  56. data/spec/unit/mutant/reporter/cli/printer/status_progressive_spec.rb +51 -0
  57. data/spec/unit/mutant/reporter/cli/printer/status_spec.rb +145 -0
  58. data/spec/unit/mutant/reporter/cli/printer/subject_progress_spec.rb +37 -0
  59. data/spec/unit/mutant/reporter/cli/printer/subject_result_spec.rb +37 -0
  60. data/spec/unit/mutant/reporter/cli/printer/test_result_spec.rb +14 -0
  61. data/spec/unit/mutant/reporter/cli/printer_spec.rb +140 -0
  62. data/spec/unit/mutant/reporter/cli_spec.rb +69 -313
  63. data/spec/unit/mutant/reporter/trace_spec.rb +12 -0
  64. data/spec/unit/mutant/require_highjack_spec.rb +25 -28
  65. data/spec/unit/mutant/warning_filter_spec.rb +7 -0
  66. data/spec/unit/mutant/zombifier_spec.rb +120 -0
  67. data/spec/unit/mutant_spec.rb +0 -43
  68. data/test_app/Gemfile.rspec3.3 +6 -0
  69. metadata +46 -17
  70. data/.travis.yml +0 -20
  71. data/lib/mutant/zombifier/file.rb +0 -100
  72. data/spec/integration/mutant/zombie_spec.rb +0 -6
@@ -0,0 +1,60 @@
1
+ module MutantSpec
2
+ class FileState
3
+ DEFAULTS = IceNine.deep_freeze(
4
+ file: false,
5
+ contents: nil,
6
+ requires: []
7
+ )
8
+
9
+ include Adamantium, Anima.new(*DEFAULTS.keys)
10
+
11
+ def self.new(attributes = DEFAULTS)
12
+ super(DEFAULTS.merge(attributes))
13
+ end
14
+
15
+ DOES_NOT_EXIST = new
16
+
17
+ alias_method :file?, :file
18
+ end # FileState
19
+
20
+ class FakePathname
21
+ include Adamantium, Concord.new(:file_system, :pathname)
22
+
23
+ def join(*arguments)
24
+ self.class.new(
25
+ file_system,
26
+ pathname.join(*arguments)
27
+ )
28
+ end
29
+
30
+ def read
31
+ state.contents
32
+ end
33
+
34
+ def to_s
35
+ pathname.to_s
36
+ end
37
+
38
+ def file?
39
+ state.file?
40
+ end
41
+
42
+ private
43
+
44
+ def state
45
+ file_system.state(pathname.to_s)
46
+ end
47
+ end # Pathname
48
+
49
+ class FileSystem
50
+ include Adamantium, Concord.new(:file_states)
51
+
52
+ def state(filename)
53
+ file_states.fetch(filename, FileState::DOES_NOT_EXIST)
54
+ end
55
+
56
+ def path(filename)
57
+ FakePathname.new(self, Pathname.new(filename))
58
+ end
59
+ end # FileSystem
60
+ end # MutantSpec
@@ -3,7 +3,7 @@ require 'ffi'
3
3
  module RbBug
4
4
  extend FFI::Library
5
5
  ffi_lib 'ruby'
6
- attach_function :rb_bug, [:string, :varargs], :void
6
+ attach_function :rb_bug, %i[string varargs], :void
7
7
 
8
8
  # Call the test bug
9
9
  #
@@ -0,0 +1,82 @@
1
+ module MutantSpec
2
+ # Not a real VM, just kidding. It connects the require / eval triggers
3
+ # require semantics Zombifier relies on in a way we can avoid having to
4
+ # mock around everywhere to test every detail.
5
+ #
6
+ # rubocop:disable LineLength
7
+ class RubyVM
8
+ include Concord.new(:expected_events)
9
+
10
+ # An event being observed by the VM handlers
11
+ class EventObservation
12
+ include Concord::Public.new(:type, :payload)
13
+ end
14
+
15
+ # An event being expected, can advance the VM
16
+ class EventExpectation
17
+ include AbstractType, Anima.new(:expected_payload, :trigger_requires)
18
+
19
+ DEFAULTS = IceNine.deep_freeze(trigger_requires: [])
20
+
21
+ def initialize(attributes)
22
+ super(DEFAULTS.merge(attributes))
23
+ end
24
+
25
+ def handle(vm, observation)
26
+ unless match?(observation)
27
+ fail "Unexpected event observation: #{observation.inspect}, expected #{inspect}"
28
+ end
29
+
30
+ trigger_requires.each(&vm.method(:require))
31
+ end
32
+
33
+ private
34
+
35
+ abstract_method :advance_vm
36
+
37
+ def match?(observation)
38
+ observation.type.eql?(self.class) && observation.payload.eql?(expected_payload)
39
+ end
40
+
41
+ # Expectation and advance on require calls
42
+ class Require < self
43
+ end
44
+
45
+ # Expectation and advance on eval calls
46
+ class Eval < self
47
+ end
48
+ end
49
+
50
+ # A fake implementation of Kernel#require
51
+ def require(logical_name)
52
+ handle_event(EventObservation.new(EventExpectation::Require, logical_name: logical_name))
53
+ self
54
+ end
55
+
56
+ # A fake implementation of Kernel#eval
57
+ def eval(source, binding, location)
58
+ handle_event(
59
+ EventObservation.new(
60
+ EventExpectation::Eval,
61
+ binding: binding,
62
+ source: source,
63
+ source_location: location
64
+ )
65
+ )
66
+ self
67
+ end
68
+
69
+ # Test if VM events where fully processed
70
+ def done?
71
+ expected_events.empty?
72
+ end
73
+
74
+ private
75
+
76
+ def handle_event(observation)
77
+ fail "Unexpected event: #{observation.type} / #{observation.payload}" if expected_events.empty?
78
+
79
+ expected_events.slice!(0).handle(self, observation)
80
+ end
81
+ end
82
+ end # MutantSpec
@@ -1,3 +1,4 @@
1
+ # rubocop:disable ModuleLength
1
2
  module SharedContext
2
3
  def update(name, &block)
3
4
  define_method(name) do
@@ -13,6 +14,14 @@ module SharedContext
13
14
  end
14
15
  end
15
16
 
17
+ def it_reports(expected_content)
18
+ it 'writes expected report to output' do
19
+ described_class.call(output, reportable)
20
+ output.rewind
21
+ expect(output.read).to eql(strip_indent(expected_content))
22
+ end
23
+ end
24
+
16
25
  # rubocop:disable MethodLength
17
26
  # rubocop:disable AbcSize
18
27
  def setup_shared_context
@@ -21,11 +30,17 @@ module SharedContext
21
30
  let(:job_b) { Mutant::Parallel::Job.new(index: 1, payload: mutation_b) }
22
31
  let(:job_a_result) { Mutant::Runner::JobResult.new(job: job_a, result: mutation_a_result) }
23
32
  let(:job_b_result) { Mutant::Runner::JobResult.new(job: job_b, result: mutation_b_result) }
24
- let(:mutations) { [mutation_a, mutation_b] }
25
- let(:matchable_scopes) { double('matchable scopes', length: 10) }
26
33
  let(:test_a) { double('test a', identification: 'test-a') }
27
34
  let(:test_b) { double('test b', identification: 'test-b') }
35
+ let(:output) { StringIO.new }
36
+ let(:matchable_scopes) { double('matchable scopes', length: 10) }
28
37
  let(:message_sequence) { FakeActor::MessageSequence.new }
38
+ let(:mutations) { [mutation_a, mutation_b] }
39
+ let(:mutation_a_node) { s(:false) }
40
+ let(:mutation_b_node) { s(:nil) }
41
+ let(:mutation_b) { Mutant::Mutation::Evil.new(subject_a, mutation_b_node) }
42
+ let(:mutation_a) { Mutant::Mutation::Evil.new(subject_a, mutation_a_node) }
43
+ let(:subject_a_node) { s(:true) }
29
44
 
30
45
  let(:status) do
31
46
  Mutant::Parallel::Status.new(
@@ -45,8 +60,8 @@ module SharedContext
45
60
  let(:subject_a) do
46
61
  double(
47
62
  'subject a',
48
- node: s(:true),
49
- source: 'true',
63
+ node: subject_a_node,
64
+ source: Unparser.unparse(subject_a_node),
50
65
  tests: [test_a],
51
66
  identification: 'subject-a'
52
67
  )
@@ -64,12 +79,6 @@ module SharedContext
64
79
  )
65
80
  end
66
81
 
67
- let(:mutation_a_node) { s(:false) }
68
- let(:mutation_b_node) { s(:nil) }
69
-
70
- let(:mutation_b) { Mutant::Mutation::Evil.new(subject_a, mutation_b_node) }
71
- let(:mutation_a) { Mutant::Mutation::Evil.new(subject_a, mutation_a_node) }
72
-
73
82
  let(:mutation_a_result) do
74
83
  Mutant::Result::Mutation.new(
75
84
  mutation: mutation_a,
@@ -23,7 +23,7 @@ RSpec.describe Mutant::AST do
23
23
  let(:block) { ->(node) { node.equal?(child_a) } }
24
24
 
25
25
  it 'returns the full path' do
26
- expect(path).to eql([:root, :parent, :child_a])
26
+ expect(path).to eql(%i[root parent child_a])
27
27
  end
28
28
  end
29
29
 
@@ -31,7 +31,7 @@ RSpec.describe Mutant::AST do
31
31
  let(:block) { ->(node) { node.equal?(child_a) || node.equal?(child_b) } }
32
32
 
33
33
  it 'returns the last full path' do
34
- expect(path).to eql([:root, :parent, :child_b])
34
+ expect(path).to eql(%i[root parent child_b])
35
35
  end
36
36
  end
37
37
  end
@@ -0,0 +1,22 @@
1
+ RSpec.describe Mutant::Cache do
2
+ let(:object) { described_class.new }
3
+
4
+ describe '#parse' do
5
+ let(:path) { double('Path') }
6
+
7
+ subject { object.parse(path) }
8
+
9
+ before do
10
+ allow(File).to receive(:read).with(path).and_return(':source')
11
+ end
12
+
13
+ it 'returns parsed source' do
14
+ expect(subject).to eql(s(:sym, :source))
15
+ end
16
+
17
+ it 'returns cached parsed source' do
18
+ source = object.parse(path)
19
+ expect(subject).to be(source)
20
+ end
21
+ end
22
+ end
@@ -105,13 +105,6 @@ RSpec.describe Mutant::CLI do
105
105
  it_should_behave_like 'an invalid cli run'
106
106
  end
107
107
 
108
- context 'with code filter and missing argument' do
109
- let(:arguments) { %w[--code] }
110
- let(:expected_message) { 'missing argument: --code' }
111
-
112
- it_should_behave_like 'an invalid cli run'
113
- end
114
-
115
108
  context 'with include help flag' do
116
109
  let(:flags) { %w[--help] }
117
110
 
@@ -133,10 +126,8 @@ Environment:
133
126
 
134
127
  Options:
135
128
  --expected-coverage COVERAGE Fail unless COVERAGE is not reached exactly, parsed via Rational()
136
- --score COVERAGE Fail unless COVERAGE is not reached exactly [deprecated]
137
- --use STRATEGY Use STRATEGY for killing mutations
129
+ --use INTEGRATION Use INTEGRATION to kill mutations
138
130
  --ignore-subject PATTERN Ignore subjects that match PATTERN
139
- --code CODE Scope execution to subjects with CODE
140
131
  --fail-fast Fail fast
141
132
  --version Print mutants version
142
133
  -d, --debug Enable debugging output
@@ -229,16 +220,6 @@ Options:
229
220
  end
230
221
  end
231
222
 
232
- context 'with score flag' do
233
- let(:flags) { %w[--score 50.0] }
234
-
235
- it_should_behave_like 'a cli parser'
236
-
237
- it 'configures expected coverage' do
238
- expect(subject.config.expected_coverage).to eql(Rational(1, 2))
239
- end
240
- end
241
-
242
223
  context 'with require flag' do
243
224
  let(:flags) { %w[--require foo] }
244
225
 
@@ -288,15 +269,5 @@ Options:
288
269
  expect(subject.config.zombie).to be(true)
289
270
  end
290
271
  end
291
-
292
- context 'with subject code filter' do
293
- let(:flags) { %w[--code faa --code bbb] }
294
-
295
- let(:expected_matcher_config) do
296
- default_matcher_config.update(subject_selects: [[:code, 'faa'], [:code, 'bbb']])
297
- end
298
-
299
- it_should_behave_like 'a cli parser'
300
- end
301
272
  end
302
273
  end
@@ -1,3 +1,4 @@
1
+ # rubocop:disable ClosingParenthesisIndentation
1
2
  RSpec.describe Mutant::Context::Scope do
2
3
  describe '.wrap' do
3
4
  subject { described_class.wrap(scope, node) }
@@ -30,10 +30,12 @@ RSpec.describe Mutant::Expression::Method do
30
30
  let(:input) { instance_method }
31
31
 
32
32
  it 'returns correct matcher' do
33
- should eql(Mutant::Matcher::Method::Instance.new(
34
- env,
35
- TestApp::Literal, TestApp::Literal.instance_method(:string)
36
- ))
33
+ should eql(
34
+ Mutant::Matcher::Method::Instance.new(
35
+ env,
36
+ TestApp::Literal, TestApp::Literal.instance_method(:string)
37
+ )
38
+ )
37
39
  end
38
40
  end
39
41
 
@@ -1,6 +1,6 @@
1
1
  RSpec.describe Mutant::Parallel::Master do
2
2
  let(:message_sequence) { FakeActor::MessageSequence.new }
3
- let(:actor_names) { [:master, :worker_a, :worker_b] }
3
+ let(:actor_names) { %i[master worker_a worker_b] }
4
4
  let(:status) { double('Status') }
5
5
  let(:sink) { FakeSink.new }
6
6
  let(:processor) { double('Processor') }
@@ -0,0 +1,33 @@
1
+ RSpec.describe Mutant::Reporter::CLI::Printer::Config do
2
+ setup_shared_context
3
+
4
+ let(:reportable) { config }
5
+
6
+ describe '.call' do
7
+ context 'on default config' do
8
+ it_reports(<<-REPORT)
9
+ Mutant configuration:
10
+ Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
11
+ Integration: null
12
+ Expect Coverage: 100.00%
13
+ Jobs: 1
14
+ Includes: []
15
+ Requires: []
16
+ REPORT
17
+ end
18
+
19
+ context 'with non default coverage expectation' do
20
+ update(:config) { { expected_coverage: 0.1r } }
21
+
22
+ it_reports(<<-REPORT)
23
+ Mutant configuration:
24
+ Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
25
+ Integration: null
26
+ Expect Coverage: 10.00%
27
+ Jobs: 1
28
+ Includes: []
29
+ Requires: []
30
+ REPORT
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,76 @@
1
+ RSpec.describe Mutant::Reporter::CLI::Printer::EnvProgress do
2
+ setup_shared_context
3
+
4
+ update(:config) { { expected_coverage: 0.1r } }
5
+
6
+ let(:reportable) { env_result }
7
+
8
+ describe '.call' do
9
+ context 'without progress' do
10
+ update(:subject_a_result) { { mutation_results: [] } }
11
+
12
+ it_reports <<-'STR'
13
+ Mutant configuration:
14
+ Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
15
+ Integration: null
16
+ Expect Coverage: 10.00%
17
+ Jobs: 1
18
+ Includes: []
19
+ Requires: []
20
+ Subjects: 1
21
+ Mutations: 2
22
+ Kills: 0
23
+ Alive: 0
24
+ Runtime: 4.00s
25
+ Killtime: 0.00s
26
+ Overhead: Inf%
27
+ Coverage: 0.00%
28
+ Expected: 10.00%
29
+ STR
30
+ end
31
+
32
+ context 'on full coverage' do
33
+ it_reports <<-'STR'
34
+ Mutant configuration:
35
+ Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
36
+ Integration: null
37
+ Expect Coverage: 10.00%
38
+ Jobs: 1
39
+ Includes: []
40
+ Requires: []
41
+ Subjects: 1
42
+ Mutations: 2
43
+ Kills: 2
44
+ Alive: 0
45
+ Runtime: 4.00s
46
+ Killtime: 2.00s
47
+ Overhead: 100.00%
48
+ Coverage: 100.00%
49
+ Expected: 10.00%
50
+ STR
51
+ end
52
+
53
+ context 'on partial coverage' do
54
+ update(:mutation_a_test_result) { { passed: true } }
55
+
56
+ it_reports <<-'STR'
57
+ Mutant configuration:
58
+ Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
59
+ Integration: null
60
+ Expect Coverage: 10.00%
61
+ Jobs: 1
62
+ Includes: []
63
+ Requires: []
64
+ Subjects: 1
65
+ Mutations: 2
66
+ Kills: 1
67
+ Alive: 1
68
+ Runtime: 4.00s
69
+ Killtime: 2.00s
70
+ Overhead: 100.00%
71
+ Coverage: 50.00%
72
+ Expected: 10.00%
73
+ STR
74
+ end
75
+ end
76
+ end