mutant 0.7.9 → 0.8.0

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