mutant 0.8.24 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (218) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +3 -3
  3. data/Changelog.md +14 -654
  4. data/Gemfile +13 -0
  5. data/Gemfile.lock +59 -64
  6. data/LICENSE +271 -20
  7. data/README.md +73 -140
  8. data/Rakefile +0 -21
  9. data/bin/mutant +7 -2
  10. data/config/reek.yml +2 -1
  11. data/config/rubocop.yml +5 -9
  12. data/docs/incremental.md +76 -0
  13. data/docs/known-problems.md +0 -14
  14. data/docs/mutant-minitest.md +1 -1
  15. data/docs/mutant-rspec.md +2 -24
  16. data/lib/mutant.rb +45 -53
  17. data/lib/mutant/ast/nodes.rb +0 -2
  18. data/lib/mutant/ast/types.rb +1 -117
  19. data/lib/mutant/base.rb +192 -0
  20. data/lib/mutant/bootstrap.rb +145 -0
  21. data/lib/mutant/cli.rb +68 -54
  22. data/lib/mutant/config.rb +119 -6
  23. data/lib/mutant/env.rb +94 -8
  24. data/lib/mutant/expression.rb +6 -1
  25. data/lib/mutant/expression/parser.rb +9 -31
  26. data/lib/mutant/integration.rb +64 -36
  27. data/lib/mutant/isolation.rb +16 -1
  28. data/lib/mutant/isolation/fork.rb +105 -40
  29. data/lib/mutant/license.rb +34 -0
  30. data/lib/mutant/license/subscription.rb +47 -0
  31. data/lib/mutant/license/subscription/commercial.rb +57 -0
  32. data/lib/mutant/license/subscription/opensource.rb +77 -0
  33. data/lib/mutant/loader.rb +27 -4
  34. data/lib/mutant/matcher.rb +48 -1
  35. data/lib/mutant/matcher/chain.rb +1 -1
  36. data/lib/mutant/matcher/config.rb +0 -2
  37. data/lib/mutant/matcher/filter.rb +1 -1
  38. data/lib/mutant/matcher/method.rb +11 -7
  39. data/lib/mutant/matcher/methods.rb +1 -1
  40. data/lib/mutant/matcher/namespace.rb +1 -1
  41. data/lib/mutant/matcher/null.rb +1 -1
  42. data/lib/mutant/matcher/scope.rb +1 -1
  43. data/lib/mutant/meta/example/dsl.rb +0 -8
  44. data/lib/mutant/mutation.rb +1 -2
  45. data/lib/mutant/mutator/node.rb +2 -9
  46. data/lib/mutant/mutator/node/arguments.rb +1 -1
  47. data/lib/mutant/mutator/node/class.rb +0 -8
  48. data/lib/mutant/mutator/node/define.rb +0 -12
  49. data/lib/mutant/mutator/node/generic.rb +30 -44
  50. data/lib/mutant/mutator/node/index.rb +4 -4
  51. data/lib/mutant/mutator/node/literal/regex.rb +0 -39
  52. data/lib/mutant/mutator/node/send.rb +13 -12
  53. data/lib/mutant/parallel.rb +61 -40
  54. data/lib/mutant/parallel/driver.rb +59 -0
  55. data/lib/mutant/parallel/source.rb +6 -2
  56. data/lib/mutant/parallel/worker.rb +63 -45
  57. data/lib/mutant/range.rb +15 -0
  58. data/lib/mutant/reporter/cli.rb +5 -11
  59. data/lib/mutant/reporter/cli/format.rb +3 -46
  60. data/lib/mutant/reporter/cli/printer/config.rb +5 -6
  61. data/lib/mutant/reporter/cli/printer/env.rb +40 -0
  62. data/lib/mutant/reporter/cli/printer/env_progress.rb +13 -17
  63. data/lib/mutant/reporter/cli/printer/isolation_result.rb +17 -3
  64. data/lib/mutant/reporter/cli/printer/mutation_result.rb +2 -3
  65. data/lib/mutant/reporter/cli/printer/status_progressive.rb +19 -10
  66. data/lib/mutant/repository.rb +0 -65
  67. data/lib/mutant/repository/diff.rb +104 -0
  68. data/lib/mutant/repository/diff/ranges.rb +52 -0
  69. data/lib/mutant/result.rb +16 -7
  70. data/lib/mutant/runner.rb +38 -47
  71. data/lib/mutant/runner/sink.rb +1 -1
  72. data/lib/mutant/selector/null.rb +19 -0
  73. data/lib/mutant/subject.rb +3 -1
  74. data/lib/mutant/subject/method/instance.rb +3 -1
  75. data/lib/mutant/transform.rb +511 -0
  76. data/lib/mutant/variable.rb +282 -0
  77. data/lib/mutant/version.rb +1 -1
  78. data/lib/mutant/warnings.rb +113 -0
  79. data/meta/case.rb +1 -0
  80. data/meta/class.rb +0 -9
  81. data/meta/def.rb +1 -26
  82. data/meta/regexp.rb +10 -20
  83. data/meta/send.rb +14 -46
  84. data/mutant-minitest.gemspec +1 -1
  85. data/mutant-rspec.gemspec +2 -2
  86. data/mutant.gemspec +15 -16
  87. data/mutant.yml +6 -0
  88. data/spec/integration/mutant/isolation/fork_spec.rb +22 -5
  89. data/spec/integration/mutant/minitest_spec.rb +3 -2
  90. data/spec/integration/mutant/rspec_spec.rb +4 -3
  91. data/spec/integrations.yml +16 -13
  92. data/spec/shared/base_behavior.rb +45 -0
  93. data/spec/shared/framework_integration_behavior.rb +43 -14
  94. data/spec/spec_helper.rb +21 -17
  95. data/spec/support/corpus.rb +56 -95
  96. data/spec/support/shared_context.rb +37 -14
  97. data/spec/support/xspec.rb +7 -3
  98. data/spec/unit/mutant/bootstrap_spec.rb +216 -0
  99. data/spec/unit/mutant/cli_spec.rb +173 -117
  100. data/spec/unit/mutant/config_spec.rb +126 -0
  101. data/spec/unit/mutant/either_spec.rb +247 -0
  102. data/spec/unit/mutant/env_spec.rb +162 -40
  103. data/spec/unit/mutant/expression/method_spec.rb +16 -0
  104. data/spec/unit/mutant/expression/parser_spec.rb +29 -33
  105. data/spec/unit/mutant/expression_spec.rb +5 -7
  106. data/spec/unit/mutant/integration_spec.rb +100 -9
  107. data/spec/unit/mutant/isolation/fork_spec.rb +125 -67
  108. data/spec/unit/mutant/isolation/result_spec.rb +33 -1
  109. data/spec/unit/mutant/license_spec.rb +257 -0
  110. data/spec/unit/mutant/loader_spec.rb +50 -11
  111. data/spec/unit/mutant/matcher/compiler_spec.rb +0 -78
  112. data/spec/unit/mutant/matcher/method/instance_spec.rb +55 -11
  113. data/spec/unit/mutant/matcher/method/singleton_spec.rb +12 -2
  114. data/spec/unit/mutant/matcher_spec.rb +102 -0
  115. data/spec/unit/mutant/maybe_spec.rb +60 -0
  116. data/spec/unit/mutant/meta/example/dsl_spec.rb +1 -17
  117. data/spec/unit/mutant/mutation_spec.rb +13 -6
  118. data/spec/unit/mutant/parallel/driver_spec.rb +112 -14
  119. data/spec/unit/mutant/parallel/source/array_spec.rb +25 -17
  120. data/spec/unit/mutant/parallel/worker_spec.rb +182 -44
  121. data/spec/unit/mutant/parallel_spec.rb +105 -8
  122. data/spec/unit/mutant/range_spec.rb +141 -0
  123. data/spec/unit/mutant/reporter/cli/printer/config_spec.rb +7 -21
  124. data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +15 -6
  125. data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +10 -2
  126. data/spec/unit/mutant/reporter/cli/printer/isolation_result_spec.rb +12 -4
  127. data/spec/unit/mutant/reporter/cli/printer/mutation_result_spec.rb +31 -2
  128. data/spec/unit/mutant/reporter/cli/printer/status_progressive_spec.rb +4 -4
  129. data/spec/unit/mutant/reporter/cli/printer/subject_result_spec.rb +5 -0
  130. data/spec/unit/mutant/reporter/cli_spec.rb +46 -123
  131. data/spec/unit/mutant/repository/diff/ranges_spec.rb +180 -0
  132. data/spec/unit/mutant/repository/diff_spec.rb +84 -71
  133. data/spec/unit/mutant/require_highjack_spec.rb +1 -1
  134. data/spec/unit/mutant/result/env_spec.rb +39 -9
  135. data/spec/unit/mutant/result/test_spec.rb +14 -0
  136. data/spec/unit/mutant/runner_spec.rb +88 -41
  137. data/spec/unit/mutant/selector/expression_spec.rb +11 -10
  138. data/spec/unit/mutant/selector/null_spec.rb +17 -0
  139. data/spec/unit/mutant/subject/method/instance_spec.rb +44 -5
  140. data/spec/unit/mutant/subject/method/singleton_spec.rb +9 -2
  141. data/spec/unit/mutant/subject_spec.rb +9 -1
  142. data/spec/unit/mutant/transform/array_spec.rb +92 -0
  143. data/spec/unit/mutant/transform/bool_spec.rb +63 -0
  144. data/spec/unit/mutant/transform/error_spec.rb +132 -0
  145. data/spec/unit/mutant/transform/exception_spec.rb +44 -0
  146. data/spec/unit/mutant/transform/hash_spec.rb +236 -0
  147. data/spec/unit/mutant/transform/index_spec.rb +92 -0
  148. data/spec/unit/mutant/transform/named_spec.rb +49 -0
  149. data/spec/unit/mutant/transform/primitive_spec.rb +56 -0
  150. data/spec/unit/mutant/transform/sequence_spec.rb +98 -0
  151. data/spec/unit/mutant/variable_spec.rb +618 -0
  152. data/spec/unit/mutant/warnings_spec.rb +89 -0
  153. data/spec/unit/mutant/world_spec.rb +63 -0
  154. data/test_app/Gemfile.minitest +0 -2
  155. metadata +79 -113
  156. data/.gitattributes +0 -1
  157. data/.ruby-gemset +0 -1
  158. data/config/triage.yml +0 -2
  159. data/lib/mutant/actor.rb +0 -57
  160. data/lib/mutant/actor/env.rb +0 -31
  161. data/lib/mutant/actor/mailbox.rb +0 -34
  162. data/lib/mutant/actor/receiver.rb +0 -42
  163. data/lib/mutant/actor/sender.rb +0 -26
  164. data/lib/mutant/ast/meta/restarg.rb +0 -19
  165. data/lib/mutant/ast/regexp.rb +0 -42
  166. data/lib/mutant/ast/regexp/transformer.rb +0 -187
  167. data/lib/mutant/ast/regexp/transformer/direct.rb +0 -123
  168. data/lib/mutant/ast/regexp/transformer/named_group.rb +0 -59
  169. data/lib/mutant/ast/regexp/transformer/options_group.rb +0 -83
  170. data/lib/mutant/ast/regexp/transformer/quantifier.rb +0 -114
  171. data/lib/mutant/ast/regexp/transformer/recursive.rb +0 -58
  172. data/lib/mutant/ast/regexp/transformer/root.rb +0 -31
  173. data/lib/mutant/ast/regexp/transformer/text.rb +0 -60
  174. data/lib/mutant/env/bootstrap.rb +0 -160
  175. data/lib/mutant/matcher/compiler.rb +0 -60
  176. data/lib/mutant/mutator/node/regexp.rb +0 -35
  177. data/lib/mutant/mutator/node/regexp/alternation_meta.rb +0 -23
  178. data/lib/mutant/mutator/node/regexp/capture_group.rb +0 -28
  179. data/lib/mutant/mutator/node/regexp/character_type.rb +0 -32
  180. data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +0 -23
  181. data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +0 -23
  182. data/lib/mutant/mutator/node/regexp/greedy_zero_or_more.rb +0 -27
  183. data/lib/mutant/parallel/master.rb +0 -181
  184. data/lib/mutant/reporter/cli/printer/status.rb +0 -53
  185. data/lib/mutant/reporter/cli/tput.rb +0 -46
  186. data/lib/mutant/warning_filter.rb +0 -61
  187. data/meta/regexp/character_types.rb +0 -23
  188. data/meta/regexp/regexp_alternation_meta.rb +0 -13
  189. data/meta/regexp/regexp_bol_anchor.rb +0 -10
  190. data/meta/regexp/regexp_bos_anchor.rb +0 -18
  191. data/meta/regexp/regexp_capture_group.rb +0 -19
  192. data/meta/regexp/regexp_eol_anchor.rb +0 -10
  193. data/meta/regexp/regexp_eos_anchor.rb +0 -8
  194. data/meta/regexp/regexp_eos_ob_eol_anchor.rb +0 -10
  195. data/meta/regexp/regexp_greedy_zero_or_more.rb +0 -12
  196. data/meta/regexp/regexp_root_expression.rb +0 -10
  197. data/meta/restarg.rb +0 -10
  198. data/spec/support/fake_actor.rb +0 -111
  199. data/spec/support/warning.rb +0 -66
  200. data/spec/unit/mutant/actor/binding_spec.rb +0 -34
  201. data/spec/unit/mutant/actor/env_spec.rb +0 -31
  202. data/spec/unit/mutant/actor/mailbox_spec.rb +0 -28
  203. data/spec/unit/mutant/actor/message_spec.rb +0 -25
  204. data/spec/unit/mutant/actor/receiver_spec.rb +0 -58
  205. data/spec/unit/mutant/actor/sender_spec.rb +0 -24
  206. data/spec/unit/mutant/ast/regexp/parse_spec.rb +0 -19
  207. data/spec/unit/mutant/ast/regexp/transformer/lookup_table/table_spec.rb +0 -21
  208. data/spec/unit/mutant/ast/regexp/transformer/lookup_table_spec.rb +0 -35
  209. data/spec/unit/mutant/ast/regexp/transformer_spec.rb +0 -21
  210. data/spec/unit/mutant/ast/regexp_spec.rb +0 -704
  211. data/spec/unit/mutant/env/bootstrap_spec.rb +0 -188
  212. data/spec/unit/mutant/matcher/compiler/subject_prefix_spec.rb +0 -26
  213. data/spec/unit/mutant/parallel/master_spec.rb +0 -338
  214. data/spec/unit/mutant/reporter/cli/printer/status_spec.rb +0 -121
  215. data/spec/unit/mutant/reporter/cli/tput_spec.rb +0 -50
  216. data/spec/unit/mutant/warning_filter_spec.rb +0 -106
  217. data/spec/unit/mutant_spec.rb +0 -17
  218. data/test_app/Gemfile.rspec3.7 +0 -7
@@ -1,41 +1,98 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe Mutant::CLI do
4
- let(:object) { described_class }
4
+ let(:default_config) { Mutant::Config::DEFAULT }
5
+ let(:kernel) { instance_double('kernel', exit: undefined) }
6
+ let(:stderr) { instance_double(IO, 'stderr', puts: undefined) }
7
+ let(:stdout) { instance_double(IO, 'stdout', puts: undefined) }
8
+ let(:target_stream) { stdout }
9
+
10
+ let(:world) do
11
+ instance_double(
12
+ Mutant::World,
13
+ kernel: kernel,
14
+ stderr: stderr,
15
+ stdout: stdout
16
+ )
17
+ end
18
+
19
+ shared_examples 'prints expected message' do
20
+
21
+ it 'prints expected message' do
22
+ apply
5
23
 
6
- shared_examples_for 'an invalid cli run' do
7
- it 'raises error' do
8
- expect do
9
- subject
10
- end.to raise_error(Mutant::CLI::Error, expected_message)
24
+ expect(target_stream).to have_received(:puts).with(expected_message)
11
25
  end
12
26
  end
13
27
 
14
- shared_examples_for 'a cli parser' do
15
- it { expect(subject.config.integration).to eql(expected_integration) }
16
- it { expect(subject.config.reporter).to eql(expected_reporter) }
17
- it { expect(subject.config.matcher).to eql(expected_matcher_config) }
28
+ before do
29
+ allow(stderr).to receive_messages(puts: undefined)
30
+ allow(stdout).to receive_messages(puts: undefined)
18
31
  end
19
32
 
20
33
  describe '.run' do
21
- subject { object.run(arguments) }
34
+ def apply
35
+ described_class.run(world, default_config, arguments)
36
+ end
22
37
 
23
- let(:arguments) { instance_double(Array) }
24
- let(:report) { instance_double(Mutant::Result::Env, success?: report_success) }
25
- let(:config) { instance_double(Mutant::Config) }
26
- let(:env) { instance_double(Mutant::Env) }
38
+ let(:arguments) { instance_double(Array) }
39
+ let(:cli_config) { instance_double(Mutant::Config, 'cli config') }
40
+ let(:cli_result) { Mutant::Either::Right.new(cli_config) }
41
+ let(:env) { instance_double(Mutant::Env) }
42
+ let(:env_result) { Mutant::Either::Right.new(env) }
43
+ let(:file_config) { instance_double(Mutant::Config, 'file config') }
44
+ let(:file_result) { Mutant::Either::Right.new(file_config) }
45
+ let(:license_result) { Mutant::Either::Right.new(true) }
46
+ let(:report_success) { true }
47
+
48
+ let(:runner_result) do
49
+ Mutant::Either::Right.new(
50
+ instance_double(Mutant::Result::Env, success?: report_success)
51
+ )
52
+ end
27
53
 
28
54
  before do
29
- expect(Mutant::CLI).to receive(:call).with(arguments).and_return(config)
30
- expect(Mutant::Env::Bootstrap).to receive(:call).with(config).and_return(env)
31
- expect(Mutant::Runner).to receive(:call).with(env).and_return(report)
55
+ allow(Mutant::License).to receive_messages(apply: license_result)
56
+ allow(Mutant::Config).to receive_messages(load_config_file: file_result)
57
+ allow(Mutant::CLI).to receive_messages(apply: cli_result)
58
+ allow(Mutant::Bootstrap).to receive_messages(apply: env_result)
59
+ allow(Mutant::Runner).to receive_messages(apply: runner_result)
60
+ end
61
+
62
+ it 'performs calls in expected sequence' do
63
+ apply
64
+
65
+ expect(Mutant::License)
66
+ .to have_received(:apply)
67
+ .with(world)
68
+ .ordered
69
+
70
+ expect(Mutant::Config)
71
+ .to have_received(:load_config_file)
72
+ .with(world, default_config)
73
+ .ordered
74
+
75
+ expect(Mutant::CLI)
76
+ .to have_received(:apply)
77
+ .with(world, file_config, arguments)
78
+ .ordered
79
+
80
+ expect(Mutant::Bootstrap)
81
+ .to have_received(:apply)
82
+ .with(world, cli_config)
83
+ .ordered
84
+
85
+ expect(Mutant::Runner)
86
+ .to have_received(:apply)
87
+ .with(env)
88
+ .ordered
32
89
  end
33
90
 
34
91
  context 'when report signals success' do
35
92
  let(:report_success) { true }
36
93
 
37
94
  it 'exits failure' do
38
- expect(subject).to be(true)
95
+ expect(apply).to be(true)
39
96
  end
40
97
  end
41
98
 
@@ -43,70 +100,78 @@ RSpec.describe Mutant::CLI do
43
100
  let(:report_success) { false }
44
101
 
45
102
  it 'exits failure' do
46
- expect(subject).to be(false)
103
+ expect(apply).to be(false)
47
104
  end
48
105
  end
49
106
 
50
- context 'when execution raises an Mutant::CLI::Error' do
51
- let(:exception) { Mutant::CLI::Error.new('test-error') }
52
- let(:report_success) { nil }
107
+ context 'when parts of the chain fail' do
108
+ let(:cli_result) { Mutant::Either::Left.new(expected_message) }
109
+ let(:expected_message) { 'cli-error' }
110
+ let(:target_stream) { stderr }
53
111
 
54
- before do
55
- expect(report).to receive(:success?).and_raise(exception)
56
- end
112
+ include_examples 'prints expected message'
57
113
 
58
114
  it 'exits failure' do
59
- expect($stderr).to receive(:puts).with('test-error')
60
- expect(subject).to be(false)
115
+ expect(apply).to be(false)
61
116
  end
62
117
  end
63
118
  end
64
119
 
65
- describe '.new' do
66
- let(:object) { described_class }
120
+ describe '.apply' do
121
+ def apply
122
+ described_class.apply(world, default_config, arguments)
123
+ end
67
124
 
68
- subject { object.new(arguments) }
125
+ shared_examples 'invalid arguments' do
126
+ it 'returns left error' do
127
+ expect(apply).to eql(Mutant::Either::Left.new(expected_message))
128
+ end
129
+ end
69
130
 
70
- # Defaults
71
- let(:expected_integration) { Mutant::Integration::Null }
72
- let(:expected_reporter) { Mutant::Config::DEFAULT.reporter }
73
- let(:expected_matcher_config) { default_matcher_config }
131
+ shared_examples 'explicit exit' do
132
+ it 'prints explicitly exits' do
133
+ apply
74
134
 
75
- let(:default_matcher_config) do
76
- Mutant::Matcher::Config::DEFAULT
77
- .with(match_expressions: expressions.map(&method(:parse_expression)))
135
+ expect(kernel).to have_received(:exit)
136
+ end
78
137
  end
79
138
 
80
- let(:flags) { [] }
81
- let(:expressions) { %w[TestApp*] }
139
+ shared_examples 'no explicit exit' do
140
+ it 'does not exit' do
141
+ expect(kernel).to_not have_received(:exit)
142
+ end
143
+ end
82
144
 
83
- let(:arguments) { flags + expressions }
145
+ shared_examples_for 'cli parser' do
146
+ it { expect(apply.from_right.integration).to eql(expected_integration) }
147
+ it { expect(apply.from_right.matcher).to eql(expected_matcher_config) }
148
+ end
84
149
 
85
- context 'with unknown flag' do
86
- let(:flags) { %w[--invalid] }
150
+ before do
151
+ allow(kernel).to receive_messages(exit: nil)
152
+ end
87
153
 
88
- let(:expected_message) { 'invalid option: --invalid' }
154
+ let(:arguments) { (options + expressions).freeze }
155
+ let(:expected_integration) { 'null' }
156
+ let(:expected_matcher_config) { default_matcher_config }
157
+ let(:expressions) { %w[TestApp*] }
158
+ let(:options) { [] }
89
159
 
90
- it_should_behave_like 'an invalid cli run'
160
+ let(:default_matcher_config) do
161
+ Mutant::Matcher::Config::DEFAULT
162
+ .with(match_expressions: expressions.map(&method(:parse_expression)))
91
163
  end
92
164
 
93
- context 'with unknown option' do
94
- let(:flags) { %w[--invalid Foo] }
95
-
165
+ context 'with --invalid option' do
166
+ let(:options) { %w[--invalid] }
96
167
  let(:expected_message) { 'invalid option: --invalid' }
97
168
 
98
- it_should_behave_like 'an invalid cli run'
169
+ include_examples 'invalid arguments'
170
+ include_examples 'no explicit exit'
99
171
  end
100
172
 
101
- context 'with include help flag' do
102
- let(:flags) { %w[--help] }
103
-
104
- before do
105
- expect($stdout).to receive(:puts).with(expected_message)
106
- expect(Kernel).to receive(:exit)
107
- end
108
-
109
- it_should_behave_like 'a cli parser'
173
+ context 'with --help option' do
174
+ let(:options) { %w[--help] }
110
175
 
111
176
  let(:expected_message) do
112
177
  <<~MESSAGE
@@ -126,123 +191,114 @@ RSpec.describe Mutant::CLI do
126
191
  -h, --help Show this message
127
192
  MESSAGE
128
193
  end
194
+
195
+ include_examples 'cli parser'
196
+ include_examples 'explicit exit'
197
+ include_examples 'prints expected message'
129
198
  end
130
199
 
131
- context 'with include flag' do
132
- let(:flags) { %w[--include foo] }
200
+ context 'with --include option' do
201
+ let(:options) { %w[--include foo] }
133
202
 
134
- it_should_behave_like 'a cli parser'
203
+ include_examples 'cli parser'
204
+ include_examples 'no explicit exit'
135
205
 
136
206
  it 'configures includes' do
137
- expect(subject.config.includes).to eql(%w[foo])
207
+ expect(apply.from_right.includes).to eql(%w[foo])
138
208
  end
139
209
  end
140
210
 
141
- context 'with use flag' do
211
+ context 'with --use option' do
142
212
  context 'when integration exists' do
143
- let(:flags) { %w[--use rspec] }
144
-
145
- before do
146
- expect(Kernel).to receive(:require)
147
- .with('mutant/integration/rspec')
148
- .and_call_original
149
- end
213
+ let(:expected_integration) { 'some' }
214
+ let(:options) { %w[--use some] }
150
215
 
151
- it_should_behave_like 'a cli parser'
152
-
153
- let(:expected_integration) { Mutant::Integration::Rspec }
154
- end
155
-
156
- context 'when integration does NOT exist' do
157
- let(:flags) { %w[--use other] }
158
-
159
- it 'raises error' do
160
- expect { subject }.to raise_error(
161
- Mutant::CLI::Error,
162
- 'Could not load integration "other" (you may want to try installing the gem mutant-other)'
163
- )
164
- end
216
+ include_examples 'cli parser'
217
+ include_examples 'no explicit exit'
165
218
  end
166
219
  end
167
220
 
168
- context 'with version flag' do
169
- let(:flags) { %w[--version] }
170
-
171
- before do
172
- expect(Kernel).to receive(:exit)
173
- expect($stdout).to receive(:puts).with("mutant-#{Mutant::VERSION}")
174
- end
221
+ context 'with --version option' do
222
+ let(:expected_message) { "mutant-#{Mutant::VERSION}" }
223
+ let(:options) { %w[--version] }
175
224
 
176
- it_should_behave_like 'a cli parser'
225
+ include_examples 'cli parser'
226
+ include_examples 'explicit exit'
227
+ include_examples 'prints expected message'
177
228
  end
178
229
 
179
- context 'with jobs flag' do
180
- let(:flags) { %w[--jobs 0] }
230
+ context 'with --jobs option' do
231
+ let(:options) { %w[--jobs 0] }
181
232
 
182
- it_should_behave_like 'a cli parser'
233
+ include_examples 'cli parser'
234
+ include_examples 'no explicit exit'
183
235
 
184
236
  it 'configures expected coverage' do
185
- expect(subject.config.jobs).to eql(0)
237
+ expect(apply.from_right.jobs).to eql(0)
186
238
  end
187
239
  end
188
240
 
189
- context 'with require flags' do
190
- let(:flags) { %w[--require foo --require bar] }
241
+ context 'with --require options' do
242
+ let(:options) { %w[--require foo --require bar] }
191
243
 
192
- it_should_behave_like 'a cli parser'
244
+ include_examples 'cli parser'
245
+ include_examples 'no explicit exit'
193
246
 
194
247
  it 'configures requires' do
195
- expect(subject.config.requires).to eql(%w[foo bar])
248
+ expect(apply.from_right.requires).to eql(%w[foo bar])
196
249
  end
197
250
  end
198
251
 
199
- context 'with --since flag' do
200
- let(:flags) { %w[--since master] }
252
+ context 'with --since option' do
253
+ let(:options) { %w[--since master] }
201
254
 
202
255
  let(:expected_matcher_config) do
203
256
  default_matcher_config.with(
204
257
  subject_filters: [
205
258
  Mutant::Repository::SubjectFilter.new(
206
259
  Mutant::Repository::Diff.new(
207
- config: Mutant::Config::DEFAULT,
208
- from: 'HEAD',
209
- to: 'master'
260
+ to: 'master',
261
+ world: world
210
262
  )
211
263
  )
212
264
  ]
213
265
  )
214
266
  end
215
267
 
216
- it_should_behave_like 'a cli parser'
268
+ include_examples 'cli parser'
269
+ include_examples 'no explicit exit'
217
270
  end
218
271
 
219
- context 'with subject-ignore flag' do
220
- let(:flags) { %w[--ignore-subject Foo::Bar] }
272
+ context 'with --subject-ignore option' do
273
+ let(:options) { %w[--ignore-subject Foo::Bar] }
221
274
 
222
275
  let(:expected_matcher_config) do
223
276
  default_matcher_config.with(ignore_expressions: [parse_expression('Foo::Bar')])
224
277
  end
225
278
 
226
- it_should_behave_like 'a cli parser'
279
+ include_examples 'cli parser'
280
+ include_examples 'no explicit exit'
227
281
  end
228
282
 
229
- context 'with fail-fast flag' do
230
- let(:flags) { %w[--fail-fast] }
283
+ context 'with --fail-fast option' do
284
+ let(:options) { %w[--fail-fast] }
231
285
 
232
- it_should_behave_like 'a cli parser'
286
+ include_examples 'cli parser'
287
+ include_examples 'no explicit exit'
233
288
 
234
289
  it 'sets the fail fast option' do
235
- expect(subject.config.fail_fast).to be(true)
290
+ expect(apply.from_right.fail_fast).to be(true)
236
291
  end
237
292
  end
238
293
 
239
- context 'with zombie flag' do
240
- let(:flags) { %w[--zombie] }
294
+ context 'with --zombie option' do
295
+ let(:options) { %w[--zombie] }
241
296
 
242
- it_should_behave_like 'a cli parser'
297
+ include_examples 'cli parser'
298
+ include_examples 'no explicit exit'
243
299
 
244
300
  it 'sets the zombie option' do
245
- expect(subject.config.zombie).to be(true)
301
+ expect(apply.from_right.zombie).to be(true)
246
302
  end
247
303
  end
248
304
  end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Mutant::Config do
4
+ describe '.load_config_file' do
5
+ def apply
6
+ described_class.load_config_file(world, config)
7
+ end
8
+
9
+ let(:config) { Mutant::Config::DEFAULT }
10
+ let(:world) { instance_double(Mutant::World, pathname: pathname) }
11
+
12
+ let(:pathname) do
13
+ paths = paths()
14
+ Class.new do
15
+ define_singleton_method(:new, &paths.method(:fetch))
16
+ end
17
+ end
18
+
19
+ let(:config_mutant_yml) do
20
+ instance_double(Pathname, 'config/mutant.yml', readable?: false)
21
+ end
22
+
23
+ let(:dot_mutant_yml) do
24
+ instance_double(Pathname, '.mutant.yml', readable?: false)
25
+ end
26
+
27
+ let(:mutant_yml) do
28
+ instance_double(Pathname, 'mutant.yml', readable?: false)
29
+ end
30
+
31
+ let(:paths) do
32
+ {
33
+ '.mutant.yml' => dot_mutant_yml,
34
+ 'config/mutant.yml' => config_mutant_yml,
35
+ 'mutant.yml' => mutant_yml
36
+ }
37
+ end
38
+
39
+ before do
40
+ allow(Pathname).to receive(:new, &paths.method(:fetch))
41
+ end
42
+
43
+ context 'when no path is readable' do
44
+ it 'returns original config' do
45
+ expect(apply).to eql(Mutant::Either::Right.new(config))
46
+ end
47
+ end
48
+
49
+ context 'when more than one path is readable' do
50
+ before do
51
+ [config_mutant_yml, mutant_yml].each do |path|
52
+ allow(path).to receive_messages(readable?: true)
53
+ end
54
+ end
55
+
56
+ let(:expected_message) do
57
+ <<~MESSAGE
58
+ Found more than one candidate for use as implicit config file: #{config_mutant_yml}, #{mutant_yml}
59
+ MESSAGE
60
+ end
61
+
62
+ it 'returns expected failure' do
63
+ expect(apply).to eql(Mutant::Either::Left.new(expected_message))
64
+ end
65
+ end
66
+
67
+ context 'with one readable path' do
68
+ let(:path_contents) do
69
+ <<~'YAML'
70
+ ---
71
+ integration: rspec
72
+ YAML
73
+ end
74
+
75
+ before do
76
+ allow(mutant_yml).to receive_messages(
77
+ read: path_contents,
78
+ readable?: true,
79
+ to_s: 'mutant.yml'
80
+ )
81
+ end
82
+
83
+ context 'when file can be read' do
84
+ context 'when yaml contents can be transformed' do
85
+ it 'returns expected config' do
86
+ expect(apply)
87
+ .to eql(Mutant::Either::Right.new(config.with(integration: 'rspec')))
88
+ end
89
+ end
90
+
91
+ context 'when yaml contents cannot be transformed' do
92
+ let(:path_contents) do
93
+ <<~'YAML'
94
+ ---
95
+ integration: true
96
+ YAML
97
+ end
98
+
99
+ # rubocop:disable Metrics/LineLength
100
+ let(:expected_message) do
101
+ 'mutant.yml/Mutant::Transform::Sequence/2/Mutant::Transform::Hash/["integration"]/String: Expected: String but got: TrueClass'
102
+ end
103
+ # rubocop:enable Metrics/LineLength
104
+
105
+ it 'returns expected error' do
106
+ expect(apply) .to eql(Mutant::Either::Left.new(expected_message))
107
+ end
108
+ end
109
+ end
110
+
111
+ context 'when file cannot be read' do
112
+ before do
113
+ allow(mutant_yml).to receive(:read).and_raise(SystemCallError, 'some error')
114
+ end
115
+
116
+ let(:expected_message) do
117
+ 'mutant.yml/Mutant::Transform::Sequence/0/Mutant::Transform::Exception: unknown error - some error'
118
+ end
119
+
120
+ it 'returns expected error' do
121
+ expect(apply) .to eql(Mutant::Either::Left.new(expected_message))
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end