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
@@ -16,10 +16,6 @@ if ENV['COVERAGE'] == 'true'
16
16
  end
17
17
  end
18
18
 
19
- # Require warning support first in order to catch any warnings emitted during boot
20
- require_relative './support/warning'
21
- $stderr = MutantSpec::Warning::EXTRACTOR
22
-
23
19
  require 'tempfile'
24
20
  require 'concord'
25
21
  require 'anima'
@@ -34,8 +30,12 @@ $LOAD_PATH << File.join(TestApp.root, 'lib')
34
30
  require 'test_app'
35
31
 
36
32
  module Fixtures
37
- TEST_CONFIG = Mutant::Config::DEFAULT.with(reporter: Mutant::Reporter::Null.new)
38
- TEST_ENV = Mutant::Env::Bootstrap.(TEST_CONFIG)
33
+ TEST_CONFIG = Mutant::Config::DEFAULT
34
+ .with(reporter: Mutant::Reporter::Null.new)
35
+
36
+ TEST_ENV = Mutant::Bootstrap
37
+ .apply(Mutant::WORLD, TEST_CONFIG).from_right
38
+
39
39
  end # Fixtures
40
40
 
41
41
  module ParserHelper
@@ -48,24 +48,28 @@ module ParserHelper
48
48
  end
49
49
 
50
50
  def parse_expression(string)
51
- Mutant::Config::DEFAULT.expression_parser.(string)
51
+ Mutant::Config::DEFAULT.expression_parser.apply(string).from_right
52
52
  end
53
53
  end # ParserHelper
54
54
 
55
- module MessageHelper
56
- def message(*arguments)
57
- Mutant::Actor::Message.new(*arguments)
55
+ module XSpecHelper
56
+ def verify_events
57
+ expectations = raw_expectations
58
+ .map(&XSpec::MessageExpectation.method(:parse))
59
+
60
+ XSpec::ExpectationVerifier.verify(self, expectations) do
61
+ yield
62
+ end
58
63
  end
59
- end # MessageHelper
64
+
65
+ def undefined
66
+ double('undefined')
67
+ end
68
+ end # XSpecHelper
60
69
 
61
70
  RSpec.configure do |config|
62
71
  config.extend(SharedContext)
63
- config.include(MessageHelper)
64
72
  config.include(ParserHelper)
65
73
  config.include(Mutant::AST::Sexp)
66
-
67
- config.after(:suite) do
68
- $stderr = STDERR
69
- MutantSpec::Warning.assert_no_warnings
70
- end
74
+ config.include(XSpecHelper)
71
75
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'anima'
4
- require 'morpher'
5
4
  require 'mutant'
6
5
  require 'parallel'
7
6
 
@@ -30,11 +29,11 @@ module MutantSpec
30
29
  MUTATION_GENERATION_MESSAGE = 'Total Mutations/Time/Parse-Errors: %s/%0.2fs - %0.2f/s'
31
30
  START_MESSAGE = 'Starting - %s'
32
31
  FINISH_MESSAGE = 'Mutations - %4i - %s'
32
+ RUBY_GLOB_PATTERN = '**/*.rb'
33
33
 
34
34
  DEFAULT_MUTATION_COUNT = 0
35
35
 
36
36
  include Adamantium, Anima.new(
37
- :expected_errors,
38
37
  :mutation_coverage,
39
38
  :mutation_generation,
40
39
  :integration,
@@ -42,7 +41,6 @@ module MutantSpec
42
41
  :namespace,
43
42
  :repo_uri,
44
43
  :repo_ref,
45
- :ruby_glob_pattern,
46
44
  :exclude
47
45
  )
48
46
 
@@ -64,12 +62,23 @@ module MutantSpec
64
62
  --include lib
65
63
  --require #{name}
66
64
  #{namespace}*
67
- ]
65
+ ] + concurrency_limits
68
66
  )
69
67
  end
70
68
  end
71
69
  end
72
70
 
71
+ # The concurrency limits, if any
72
+ #
73
+ # @return [Array<String>]
74
+ def concurrency_limits
75
+ if ENV.key?('MUTANT_JOBS')
76
+ %W[--jobs #{ENV.fetch('MUTANT_JOBS')}]
77
+ else
78
+ []
79
+ end
80
+ end
81
+
73
82
  # Verify mutation generation
74
83
  #
75
84
  # @return [self]
@@ -130,8 +139,6 @@ module MutantSpec
130
139
  #
131
140
  # @return [Integer] mutations generated
132
141
  def check_generation(path)
133
- relative_path = path.relative_path_from(repo_path)
134
-
135
142
  node = Parser::CurrentRuby.parse(path.read)
136
143
  fail "Cannot parse: #{path}" unless node
137
144
 
@@ -141,13 +148,7 @@ module MutantSpec
141
148
  check_generation_invariants(node, mutation)
142
149
  end
143
150
 
144
- expected_errors.assert_success(relative_path)
145
-
146
151
  mutations.length
147
- rescue Exception => exception # rubocop:disable Lint/RescueException
148
- expected_errors.assert_error(relative_path, exception)
149
-
150
- DEFAULT_MUTATION_COUNT
151
152
  end
152
153
 
153
154
  # Check generation invariants
@@ -196,7 +197,7 @@ module MutantSpec
196
197
  # @return [Array<Pathname>]
197
198
  def effective_ruby_paths
198
199
  Pathname
199
- .glob(repo_path.join(ruby_glob_pattern))
200
+ .glob(repo_path.join(RUBY_GLOB_PATTERN))
200
201
  .sort_by(&:size)
201
202
  .reverse
202
203
  .reject { |path| exclude.include?(path.relative_path_from(repo_path).to_s) }
@@ -269,89 +270,49 @@ module MutantSpec
269
270
  end
270
271
  end
271
272
 
272
- # Mapping of files which we expect to cause errors during mutation generation
273
- class ErrorWhitelist
274
- class UnnecessaryExpectation < StandardError
275
- MESSAGE = 'Expected to encounter %s while mutating "%s"'
276
-
277
- def initialize(*error_info)
278
- super(MESSAGE % error_info)
279
- end
280
- end # UnnecessaryExpectation
281
-
282
- include Concord.new(:map), Adamantium
283
-
284
- # Assert that we expect to encounter the provided exception for this path
285
- #
286
- # @param path [Pathname]
287
- # @param exception [Exception]
288
- #
289
- # @raise provided exception if we are not expecting this error
290
- #
291
- # This method is reraising exceptions but rubocop can't tell
292
- # rubocop:disable Style/SignalException
293
- #
294
- # @return [undefined]
295
- def assert_error(path, exception)
296
- original_error = exception.cause || exception
297
-
298
- raise exception unless map.fetch(original_error.inspect, []).include?(path)
299
- end
300
-
301
- # Assert that we expect to not encounter an error for the specified path
302
- #
303
- # @param path [Pathname]
304
- #
305
- # @raise [UnnecessaryExpectation] if we are expecting an exception for this path
306
- #
307
- # @return [undefined]
308
- def assert_success(path)
309
- map.each do |error, paths|
310
- fail UnnecessaryExpectation.new(error, path) if paths.include?(path)
311
- end
312
- end
313
-
314
- # Return representation as hash
315
- #
316
- # @note this method is necessary for morpher loader to be invertible
317
- #
318
- # @return [Hash{Pathname => String}]
319
- def to_h
320
- map
321
- end
322
- end # ErrorWhitelist
323
-
324
- LOADER = Morpher.build do
325
- s(:block,
326
- s(:guard, s(:primitive, Array)),
327
- s(:map,
328
- s(:block,
329
- s(:guard, s(:primitive, Hash)),
330
- s(:hash_transform,
331
- s(:key_symbolize, :repo_uri, s(:guard, s(:primitive, String))),
332
- s(:key_symbolize, :repo_ref, s(:guard, s(:primitive, String))),
333
- s(:key_symbolize, :ruby_glob_pattern, s(:guard, s(:primitive, String))),
334
- s(:key_symbolize, :name, s(:guard, s(:primitive, String))),
335
- s(:key_symbolize, :namespace, s(:guard, s(:primitive, String))),
336
- s(:key_symbolize, :integration, s(:guard, s(:primitive, String))),
337
- s(:key_symbolize, :mutation_coverage,
338
- s(:guard, s(:or, s(:primitive, TrueClass), s(:primitive, FalseClass)))),
339
- s(:key_symbolize, :mutation_generation,
340
- s(:guard, s(:or, s(:primitive, TrueClass), s(:primitive, FalseClass)))),
341
- s(:key_symbolize, :expected_errors,
342
- s(:block,
343
- s(:guard, s(:primitive, Hash)),
344
- s(:custom,
345
- [
346
- ->(hash) { hash.map { |key, values| [key, values.map(&Pathname.method(:new))] }.to_h },
347
- ->(hash) { hash.map { |key, values| [key, values.map(&:to_s)] }.to_h }
348
- ]),
349
- s(:load_attribute_hash, s(:param, ErrorWhitelist)))),
350
- s(:key_symbolize, :exclude, s(:map, s(:guard, s(:primitive, String))))),
351
- s(:anima_load, Project))))
352
- end
273
+ Transform = Mutant::Transform
274
+
275
+ boolean = Transform::Boolean.new
276
+ string = Transform::Primitive.new(String)
277
+ string_array = Transform::Array.new(string)
278
+
279
+ integration = Transform::Sequence.new(
280
+ [
281
+ Transform::Hash.new(
282
+ optional: [],
283
+ required: [
284
+ Transform::Hash::Key.new('exclude', string_array),
285
+ Transform::Hash::Key.new('integration', string),
286
+ Transform::Hash::Key.new('mutation_coverage', boolean),
287
+ Transform::Hash::Key.new('mutation_generation', boolean),
288
+ Transform::Hash::Key.new('name', string),
289
+ Transform::Hash::Key.new('namespace', string),
290
+ Transform::Hash::Key.new('repo_ref', string),
291
+ Transform::Hash::Key.new('repo_uri', string)
292
+ ]
293
+ ),
294
+ Transform::Hash::Symbolize.new,
295
+ Transform::Exception.new(RuntimeError, Project.method(:new))
296
+ ]
297
+ )
353
298
 
354
- ALL = LOADER.call(YAML.load_file(ROOT.join('spec', 'integrations.yml')))
299
+ transform =
300
+ Transform::Sequence.new(
301
+ [
302
+ Transform::Exception.new(SystemCallError, :read.to_proc),
303
+ Transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
304
+ Transform::Array.new(integration)
305
+ ]
306
+ )
307
+
308
+ path = ROOT.join('spec', 'integrations.yml')
309
+
310
+ ALL = Transform::Named
311
+ .new(path, transform)
312
+ .apply(path)
313
+ .lmap(&:compact_message)
314
+ .lmap(&method(:fail))
315
+ .from_right
355
316
  end # Project
356
317
  end # Corpus
357
318
  end # MutantSpec
@@ -17,14 +17,6 @@ module SharedContext
17
17
  prepend(new_definition)
18
18
  end
19
19
 
20
- def messages(&block)
21
- let(:message_sequence) do
22
- FakeActor::MessageSequence.new.tap do |sequence|
23
- sequence.instance_eval(&block)
24
- end
25
- end
26
- end
27
-
28
20
  def it_reports(expected_content)
29
21
  it 'writes expected report to output' do
30
22
  described_class.call(output, reportable)
@@ -36,8 +28,6 @@ module SharedContext
36
28
  # rubocop:disable MethodLength
37
29
  # rubocop:disable AbcSize
38
30
  def setup_shared_context
39
- let(:job_a) { Mutant::Parallel::Job.new(index: 0, payload: mutation_a) }
40
- let(:job_b) { Mutant::Parallel::Job.new(index: 1, payload: mutation_b) }
41
31
  let(:mutation_a) { Mutant::Mutation::Evil.new(subject_a, mutation_a_node) }
42
32
  let(:mutation_a_node) { s(:false) }
43
33
  let(:mutation_b) { Mutant::Mutation::Evil.new(subject_a, mutation_b_node) }
@@ -46,14 +36,47 @@ module SharedContext
46
36
  let(:output) { StringIO.new }
47
37
  let(:subject_a_node) { s(:true) }
48
38
  let(:test_a) { instance_double(Mutant::Test, identification: 'test-a') }
39
+ let(:subjects) { [subject_a] }
40
+
41
+ let(:job_a) do
42
+ Mutant::Parallel::Source::Job.new(
43
+ index: 0,
44
+ payload: mutation_a
45
+ )
46
+ end
47
+
48
+ let(:job_b) do
49
+ Mutant::Parallel::Source::Job.new(
50
+ index: 1,
51
+ payload: mutation_b
52
+ )
53
+ end
49
54
 
50
55
  let(:env) do
51
56
  instance_double(
52
57
  Mutant::Env,
53
- config: config,
54
- mutations: mutations,
55
- selections: { subject_a => [test_a] },
56
- subjects: [subject_a]
58
+ amount_mutations: mutations.length,
59
+ amount_selected_tests: selections.values.flatten.to_set.length,
60
+ amount_subjects: subjects.length,
61
+ amount_total_tests: integration.all_tests.length,
62
+ config: config,
63
+ integration: integration,
64
+ mutations: mutations,
65
+ selected_tests: [test_a].to_set,
66
+ selections: selections,
67
+ subjects: subjects,
68
+ test_subject_ratio: Rational(1)
69
+ )
70
+ end
71
+
72
+ let(:selections) do
73
+ { subject_a => [test_a] }
74
+ end
75
+
76
+ let(:integration) do
77
+ instance_double(
78
+ Mutant::Integration,
79
+ all_tests: [test_a]
57
80
  )
58
81
  end
59
82
 
@@ -5,7 +5,7 @@ module XSpec
5
5
  include Concord.new(:event_list)
6
6
 
7
7
  TERMINATE_EVENTS = IceNine.deep_freeze(%i[return exception].to_set)
8
- VALID_EVENTS = IceNine.deep_freeze(%i[return exception yields].to_set)
8
+ VALID_EVENTS = IceNine.deep_freeze(%i[return execute exception yields].to_set)
9
9
 
10
10
  private_constant(*constants(false))
11
11
 
@@ -32,6 +32,10 @@ module XSpec
32
32
  value
33
33
  end
34
34
 
35
+ def execute(_event, block)
36
+ block.call
37
+ end
38
+
35
39
  def exception(_event, exception)
36
40
  fail exception
37
41
  end
@@ -140,12 +144,12 @@ module XSpec
140
144
  include Concord.new(:expectations)
141
145
 
142
146
  def call(observation)
143
- expectation = expectations.shift or fail "No expected message but observed #{observation}"
147
+ expectation = expectations.shift or fail "No expected message but observed #{observation.inspect}"
144
148
  expectation.call(observation)
145
149
  end
146
150
 
147
151
  def assert_done
148
- expectations.empty? or fail "unconsumed expectations:\n#{expectations.map(&:inspect).join}"
152
+ expectations.empty? or fail "unconsumed expectations:\n#{expectations.map(&:inspect).join("\n")}"
149
153
  end
150
154
 
151
155
  # rubocop:disable MethodLength
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Mutant::Bootstrap do
4
+ let(:integration) { instance_double(Mutant::Integration) }
5
+ let(:integration_name) { instance_double(String) }
6
+ let(:integration_result) { Mutant::Either::Right.new(integration) }
7
+ let(:kernel) { instance_double(Object, 'kernel') }
8
+ let(:load_path) { %w[original] }
9
+ let(:matcher_config) { Mutant::Matcher::Config::DEFAULT }
10
+ let(:object_space) { class_double(ObjectSpace) }
11
+ let(:object_space_modules) { [] }
12
+ let(:warnings) { instance_double(Mutant::Warnings) }
13
+
14
+ let(:world) do
15
+ instance_double(
16
+ Mutant::World,
17
+ kernel: kernel,
18
+ load_path: load_path,
19
+ object_space: object_space,
20
+ pathname: Pathname,
21
+ warnings: warnings
22
+ )
23
+ end
24
+
25
+ let(:config) do
26
+ Mutant::Config::DEFAULT.with(
27
+ includes: [],
28
+ integration: integration,
29
+ jobs: 1,
30
+ matcher: matcher_config,
31
+ reporter: instance_double(Mutant::Reporter),
32
+ requires: []
33
+ )
34
+ end
35
+
36
+ let(:expected_env) do
37
+ env_with_scopes.with(
38
+ integration: integration,
39
+ selector: Mutant::Selector::Expression.new(integration)
40
+ )
41
+ end
42
+
43
+ let(:env_empty) do
44
+ Mutant::Env.empty(world, config)
45
+ end
46
+
47
+ let(:env_with_scopes) { env_empty }
48
+
49
+ shared_examples 'expected warning' do
50
+ let(:warns) { [] }
51
+
52
+ before do
53
+ allow(config.reporter).to receive(:warn, &warns.method(:<<))
54
+ end
55
+
56
+ it 'warns with expected warning' do
57
+ expect { apply }.to change(warns, :to_a).from([]).to([expected_warning])
58
+ end
59
+ end
60
+
61
+ shared_examples_for 'bootstrap call' do
62
+ it 'returns expected env' do
63
+ expect(apply).to eql(Mutant::Either::Right.new(expected_env))
64
+ end
65
+
66
+ it 'performs IO in expected sequence' do
67
+ apply
68
+
69
+ expect(object_space)
70
+ .to have_received(:each_object)
71
+ .ordered
72
+
73
+ expect(Mutant::Integration)
74
+ .to have_received(:setup)
75
+ .with(env_with_scopes)
76
+ .ordered
77
+ end
78
+ end
79
+
80
+ before do
81
+ allow(Mutant::Integration).to receive_messages(setup: integration_result)
82
+
83
+ allow(object_space).to receive(:each_object) do |argument|
84
+ expect(argument).to be(Module)
85
+ object_space_modules.each
86
+ end
87
+ end
88
+
89
+ describe '.apply' do
90
+ def apply
91
+ described_class.apply(world, config)
92
+ end
93
+
94
+ context 'when Module#name calls result in exceptions' do
95
+ let(:object_space_modules) { [invalid_class] }
96
+
97
+ let(:expected_warning) do
98
+ "Object#name from: #{invalid_class} raised an error: " \
99
+ "RuntimeError. #{Mutant::Env::SEMANTICS_MESSAGE}"
100
+ end
101
+
102
+ # Not really a class, but does not leak a "wrong" class
103
+ # into later specs.
104
+ let(:invalid_class) do
105
+ Object.new.tap do |object|
106
+ def object.name
107
+ fail
108
+ end
109
+ end
110
+ end
111
+
112
+ include_examples 'expected warning'
113
+ include_examples 'bootstrap call'
114
+ end
115
+
116
+ context 'when Module#name calls return nil' do
117
+ let(:anonymous_class) { Class.new }
118
+ let(:object_space_modules) { [anonymous_class] }
119
+
120
+ include_examples 'bootstrap call'
121
+ end
122
+
123
+ context 'when requires are configured' do
124
+ let(:config) { super().with(requires: %w[foo bar]) }
125
+ let(:requires) { [] }
126
+
127
+ before do
128
+ allow(kernel).to receive(:require, &requires.method(:<<))
129
+ end
130
+
131
+ it 'executes requires' do
132
+ expect { apply }.to change(requires, :to_a).from([]).to(%w[foo bar])
133
+ end
134
+
135
+ include_examples 'bootstrap call'
136
+ end
137
+
138
+ context 'when includes are configured' do
139
+ let(:config) { super().with(includes: %w[foo bar]) }
140
+
141
+ it 'appends to load path' do
142
+ expect { apply }
143
+ .to change(load_path, :to_a)
144
+ .from(load_path.dup)
145
+ .to(%w[original foo bar])
146
+ end
147
+
148
+ include_examples 'bootstrap call'
149
+ end
150
+
151
+ context 'when Module#name does not return a String or nil' do
152
+ let(:object_space_modules) { [invalid_class] }
153
+
154
+ let(:invalid_class) do
155
+ # intentionally an object to not actually pollute object space
156
+ Object.new.tap do |object|
157
+ def object.name
158
+ Object
159
+ end
160
+ end
161
+ end
162
+
163
+ let(:expected_warning) do
164
+ "Object#name from: #{invalid_class} " \
165
+ "returned Object. #{Mutant::Env::SEMANTICS_MESSAGE}"
166
+ end
167
+
168
+ include_examples 'expected warning'
169
+ include_examples 'bootstrap call'
170
+ end
171
+
172
+ context 'when object name cannot be parsed as expression' do
173
+ let(:object_space_modules) { [invalid_class] }
174
+
175
+ let(:invalid_class) do
176
+ # intentionally an object to not actually pollute object space
177
+ Object.new.tap do |object|
178
+ def object.name
179
+ 'invalid expression'
180
+ end
181
+ end
182
+ end
183
+
184
+ include_examples 'bootstrap call'
185
+ end
186
+
187
+ context 'when scope matches expression' do
188
+ let(:object_space_modules) { [TestApp::Literal, TestApp::Empty] }
189
+ let(:match_expressions) { object_space_modules.map(&:name).map(&method(:parse_expression)) }
190
+
191
+ let(:matcher_config) do
192
+ super().with(match_expressions: match_expressions)
193
+ end
194
+
195
+ let(:env_with_scopes) do
196
+ env_empty.with(
197
+ matchable_scopes: [
198
+ Mutant::Scope.new(TestApp::Empty, match_expressions.last),
199
+ Mutant::Scope.new(TestApp::Literal, match_expressions.first)
200
+ ]
201
+ )
202
+ end
203
+
204
+ let(:expected_env) do
205
+ subjects = Mutant::Matcher::Scope.new(TestApp::Literal).call(env_empty)
206
+
207
+ super().with(
208
+ mutations: subjects.flat_map(&:mutations),
209
+ subjects: subjects
210
+ )
211
+ end
212
+
213
+ include_examples 'bootstrap call'
214
+ end
215
+ end
216
+ end