mutant 0.9.10 → 0.10.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 (254) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mutant +16 -11
  3. data/lib/mutant.rb +8 -4
  4. data/lib/mutant/bootstrap.rb +14 -1
  5. data/lib/mutant/cli.rb +9 -162
  6. data/lib/mutant/cli/command.rb +196 -0
  7. data/lib/mutant/cli/command/root.rb +13 -0
  8. data/lib/mutant/cli/command/run.rb +151 -0
  9. data/lib/mutant/cli/command/subscription.rb +54 -0
  10. data/lib/mutant/expression.rb +0 -1
  11. data/lib/mutant/isolation.rb +1 -1
  12. data/lib/mutant/isolation/fork.rb +2 -2
  13. data/lib/mutant/isolation/none.rb +1 -1
  14. data/lib/mutant/license.rb +9 -35
  15. data/lib/mutant/license/subscription.rb +31 -9
  16. data/lib/mutant/license/subscription/commercial.rb +2 -4
  17. data/lib/mutant/license/subscription/opensource.rb +7 -7
  18. data/lib/mutant/matcher/config.rb +2 -0
  19. data/lib/mutant/meta/example.rb +16 -4
  20. data/lib/mutant/meta/example/dsl.rb +33 -16
  21. data/lib/mutant/meta/example/verification.rb +70 -28
  22. data/lib/mutant/minitest/coverage.rb +53 -0
  23. data/lib/mutant/mutator/node.rb +2 -2
  24. data/lib/mutant/mutator/node/block_pass.rb +29 -0
  25. data/lib/mutant/mutator/node/{dstr.rb → dynamic_literal.rb} +7 -5
  26. data/lib/mutant/mutator/node/index.rb +4 -4
  27. data/lib/mutant/mutator/node/literal/range.rb +5 -2
  28. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +1 -1
  29. data/lib/mutant/mutator/node/noop.rb +1 -1
  30. data/lib/mutant/mutator/node/op_asgn.rb +15 -1
  31. data/lib/mutant/mutator/node/send.rb +25 -1
  32. data/lib/mutant/mutator/node/send/attribute_assignment.rb +1 -0
  33. data/lib/mutant/reporter/cli/printer/isolation_result.rb +9 -3
  34. data/lib/mutant/subject/method/instance.rb +1 -1
  35. data/lib/mutant/version.rb +1 -1
  36. metadata +18 -336
  37. data/.github/workflows/ci.yml +0 -121
  38. data/.gitignore +0 -38
  39. data/.rspec +0 -5
  40. data/.rubocop.yml +0 -210
  41. data/Changelog.md +0 -87
  42. data/Gemfile +0 -7
  43. data/Gemfile.lock +0 -107
  44. data/Gemfile.shared +0 -10
  45. data/README.md +0 -199
  46. data/Rakefile +0 -5
  47. data/config/devtools.yml +0 -2
  48. data/config/reek.yml +0 -139
  49. data/config/yardstick.yml +0 -2
  50. data/docs/commercial-support.md +0 -14
  51. data/docs/concurrency.md +0 -39
  52. data/docs/incremental.md +0 -76
  53. data/docs/known-problems.md +0 -30
  54. data/docs/limitations.md +0 -50
  55. data/docs/mutant-minitest.md +0 -149
  56. data/docs/mutant-rspec.md +0 -130
  57. data/docs/nomenclature.md +0 -82
  58. data/docs/reading-reports.md +0 -74
  59. data/lib/mutant/mutator/node/dsym.rb +0 -22
  60. data/meta/and.rb +0 -13
  61. data/meta/and_asgn.rb +0 -14
  62. data/meta/array.rb +0 -27
  63. data/meta/begin.rb +0 -20
  64. data/meta/block.rb +0 -199
  65. data/meta/block_pass.rb +0 -8
  66. data/meta/blockarg.rb +0 -10
  67. data/meta/break.rb +0 -9
  68. data/meta/case.rb +0 -217
  69. data/meta/casgn.rb +0 -25
  70. data/meta/cbase.rb +0 -8
  71. data/meta/class.rb +0 -12
  72. data/meta/const.rb +0 -17
  73. data/meta/csend.rb +0 -10
  74. data/meta/cvar.rb +0 -7
  75. data/meta/cvasgn.rb +0 -9
  76. data/meta/date.rb +0 -59
  77. data/meta/def.rb +0 -196
  78. data/meta/defined.rb +0 -9
  79. data/meta/dstr.rb +0 -13
  80. data/meta/dsym.rb +0 -14
  81. data/meta/ensure.rb +0 -8
  82. data/meta/false.rb +0 -7
  83. data/meta/file.rb +0 -5
  84. data/meta/float.rb +0 -37
  85. data/meta/gvar.rb +0 -7
  86. data/meta/gvasgn.rb +0 -9
  87. data/meta/hash.rb +0 -20
  88. data/meta/if.rb +0 -72
  89. data/meta/index.rb +0 -133
  90. data/meta/indexasgn.rb +0 -31
  91. data/meta/int.rb +0 -18
  92. data/meta/ivar.rb +0 -8
  93. data/meta/ivasgn.rb +0 -22
  94. data/meta/kwarg.rb +0 -10
  95. data/meta/kwbegin.rb +0 -8
  96. data/meta/kwoptarg.rb +0 -13
  97. data/meta/lambda.rb +0 -23
  98. data/meta/line.rb +0 -5
  99. data/meta/lvar.rb +0 -16
  100. data/meta/lvasgn.rb +0 -24
  101. data/meta/masgn.rb +0 -7
  102. data/meta/match_current_line.rb +0 -14
  103. data/meta/next.rb +0 -10
  104. data/meta/nil.rb +0 -5
  105. data/meta/nthref.rb +0 -14
  106. data/meta/op_assgn.rb +0 -17
  107. data/meta/or.rb +0 -13
  108. data/meta/or_asgn.rb +0 -50
  109. data/meta/range.rb +0 -65
  110. data/meta/redo.rb +0 -5
  111. data/meta/regexp.rb +0 -80
  112. data/meta/regopt.rb +0 -10
  113. data/meta/rescue.rb +0 -84
  114. data/meta/return.rb +0 -16
  115. data/meta/sclass.rb +0 -12
  116. data/meta/self.rb +0 -7
  117. data/meta/send.rb +0 -600
  118. data/meta/str.rb +0 -7
  119. data/meta/super.rb +0 -27
  120. data/meta/sym.rb +0 -8
  121. data/meta/true.rb +0 -7
  122. data/meta/until.rb +0 -16
  123. data/meta/while.rb +0 -24
  124. data/meta/yield.rb +0 -9
  125. data/mutant-minitest.gemspec +0 -22
  126. data/mutant-rspec.gemspec +0 -22
  127. data/mutant.gemspec +0 -44
  128. data/mutant.sh +0 -12
  129. data/mutant.yml +0 -6
  130. data/spec/integration/mutant/corpus_spec.rb +0 -15
  131. data/spec/integration/mutant/isolation/fork_spec.rb +0 -28
  132. data/spec/integration/mutant/minitest_spec.rb +0 -11
  133. data/spec/integration/mutant/null_spec.rb +0 -16
  134. data/spec/integration/mutant/rspec_spec.rb +0 -15
  135. data/spec/integration/mutant/test_mutator_handles_types_spec.rb +0 -9
  136. data/spec/integrations.yml +0 -63
  137. data/spec/shared/framework_integration_behavior.rb +0 -70
  138. data/spec/shared/method_matcher_behavior.rb +0 -47
  139. data/spec/spec_helper.rb +0 -90
  140. data/spec/support/corpus.rb +0 -318
  141. data/spec/support/file_system.rb +0 -62
  142. data/spec/support/ice_nine_config.rb +0 -10
  143. data/spec/support/ruby_vm.rb +0 -84
  144. data/spec/support/shared_context.rb +0 -169
  145. data/spec/support/xspec.rb +0 -183
  146. data/spec/unit/mutant/ast/find_metaclass_containing_spec.rb +0 -64
  147. data/spec/unit/mutant/ast/meta/optarg_spec.rb +0 -24
  148. data/spec/unit/mutant/ast/meta/send/proc_predicate_spec.rb +0 -30
  149. data/spec/unit/mutant/ast/meta/send/receiver_possible_top_level_const_predicate_spec.rb +0 -39
  150. data/spec/unit/mutant/ast/meta/send_spec.rb +0 -42
  151. data/spec/unit/mutant/ast/named_children_spec.rb +0 -89
  152. data/spec/unit/mutant/ast/sexp_spec.rb +0 -38
  153. data/spec/unit/mutant/ast_spec.rb +0 -57
  154. data/spec/unit/mutant/bootstrap_spec.rb +0 -216
  155. data/spec/unit/mutant/cli_spec.rb +0 -305
  156. data/spec/unit/mutant/clock_monotonic_spec.rb +0 -52
  157. data/spec/unit/mutant/config_spec.rb +0 -126
  158. data/spec/unit/mutant/context_spec.rb +0 -111
  159. data/spec/unit/mutant/env_spec.rb +0 -229
  160. data/spec/unit/mutant/expression/method_spec.rb +0 -62
  161. data/spec/unit/mutant/expression/methods_spec.rb +0 -66
  162. data/spec/unit/mutant/expression/namespace/exact_spec.rb +0 -28
  163. data/spec/unit/mutant/expression/namespace/recursive_spec.rb +0 -66
  164. data/spec/unit/mutant/expression/parser_spec.rb +0 -65
  165. data/spec/unit/mutant/expression_spec.rb +0 -45
  166. data/spec/unit/mutant/integration/rspec_spec.rb +0 -201
  167. data/spec/unit/mutant/integration_spec.rb +0 -150
  168. data/spec/unit/mutant/isolation/fork_spec.rb +0 -309
  169. data/spec/unit/mutant/isolation/none_spec.rb +0 -23
  170. data/spec/unit/mutant/isolation/result_spec.rb +0 -73
  171. data/spec/unit/mutant/license_spec.rb +0 -305
  172. data/spec/unit/mutant/loader_spec.rb +0 -79
  173. data/spec/unit/mutant/matcher/chain_spec.rb +0 -26
  174. data/spec/unit/mutant/matcher/compiler_spec.rb +0 -0
  175. data/spec/unit/mutant/matcher/config_spec.rb +0 -47
  176. data/spec/unit/mutant/matcher/filter_spec.rb +0 -22
  177. data/spec/unit/mutant/matcher/method/instance_spec.rb +0 -164
  178. data/spec/unit/mutant/matcher/method/metaclass_spec.rb +0 -108
  179. data/spec/unit/mutant/matcher/method/singleton_spec.rb +0 -90
  180. data/spec/unit/mutant/matcher/methods/instance_spec.rb +0 -54
  181. data/spec/unit/mutant/matcher/methods/metaclass_spec.rb +0 -62
  182. data/spec/unit/mutant/matcher/methods/singleton_spec.rb +0 -51
  183. data/spec/unit/mutant/matcher/namespace_spec.rb +0 -39
  184. data/spec/unit/mutant/matcher/null_spec.rb +0 -12
  185. data/spec/unit/mutant/matcher/scope_spec.rb +0 -45
  186. data/spec/unit/mutant/matcher/static_spec.rb +0 -13
  187. data/spec/unit/mutant/matcher_spec.rb +0 -102
  188. data/spec/unit/mutant/meta/example/dsl_spec.rb +0 -108
  189. data/spec/unit/mutant/meta/example/verification_spec.rb +0 -154
  190. data/spec/unit/mutant/meta/example_spec.rb +0 -34
  191. data/spec/unit/mutant/mutation_spec.rb +0 -140
  192. data/spec/unit/mutant/mutator/node_spec.rb +0 -47
  193. data/spec/unit/mutant/mutator_spec.rb +0 -21
  194. data/spec/unit/mutant/parallel/driver_spec.rb +0 -126
  195. data/spec/unit/mutant/parallel/source/array_spec.rb +0 -57
  196. data/spec/unit/mutant/parallel/worker_spec.rb +0 -206
  197. data/spec/unit/mutant/parallel_spec.rb +0 -115
  198. data/spec/unit/mutant/parser_spec.rb +0 -26
  199. data/spec/unit/mutant/range_spec.rb +0 -141
  200. data/spec/unit/mutant/registry_spec.rb +0 -74
  201. data/spec/unit/mutant/reporter/cli/printer/config_spec.rb +0 -17
  202. data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +0 -85
  203. data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +0 -45
  204. data/spec/unit/mutant/reporter/cli/printer/isolation_result_spec.rb +0 -132
  205. data/spec/unit/mutant/reporter/cli/printer/mutation_progress_result_spec.rb +0 -25
  206. data/spec/unit/mutant/reporter/cli/printer/mutation_result_spec.rb +0 -153
  207. data/spec/unit/mutant/reporter/cli/printer/status_progressive_spec.rb +0 -45
  208. data/spec/unit/mutant/reporter/cli/printer/subject_progress_spec.rb +0 -36
  209. data/spec/unit/mutant/reporter/cli/printer/subject_result_spec.rb +0 -44
  210. data/spec/unit/mutant/reporter/cli/printer/test_result_spec.rb +0 -16
  211. data/spec/unit/mutant/reporter/cli/printer_spec.rb +0 -163
  212. data/spec/unit/mutant/reporter/cli_spec.rb +0 -137
  213. data/spec/unit/mutant/reporter/null_spec.rb +0 -14
  214. data/spec/unit/mutant/reporter/sequence_spec.rb +0 -31
  215. data/spec/unit/mutant/repository/diff/ranges_spec.rb +0 -180
  216. data/spec/unit/mutant/repository/diff_spec.rb +0 -122
  217. data/spec/unit/mutant/repository/subject_filter_spec.rb +0 -30
  218. data/spec/unit/mutant/require_highjack_spec.rb +0 -73
  219. data/spec/unit/mutant/result/class_methods_spec.rb +0 -51
  220. data/spec/unit/mutant/result/env_spec.rb +0 -161
  221. data/spec/unit/mutant/result/mutation_spec.rb +0 -70
  222. data/spec/unit/mutant/result/subject_spec.rb +0 -111
  223. data/spec/unit/mutant/result/test_spec.rb +0 -14
  224. data/spec/unit/mutant/result_spec.rb +0 -33
  225. data/spec/unit/mutant/runner/sink_spec.rb +0 -174
  226. data/spec/unit/mutant/runner_spec.rb +0 -121
  227. data/spec/unit/mutant/selector/expression_spec.rb +0 -62
  228. data/spec/unit/mutant/selector/null_spec.rb +0 -17
  229. data/spec/unit/mutant/subject/method/instance_spec.rb +0 -276
  230. data/spec/unit/mutant/subject/method/metaclass_spec.rb +0 -63
  231. data/spec/unit/mutant/subject/method/singleton_spec.rb +0 -61
  232. data/spec/unit/mutant/subject_spec.rb +0 -93
  233. data/spec/unit/mutant/transform/array_spec.rb +0 -92
  234. data/spec/unit/mutant/transform/bool_spec.rb +0 -63
  235. data/spec/unit/mutant/transform/error_spec.rb +0 -132
  236. data/spec/unit/mutant/transform/exception_spec.rb +0 -44
  237. data/spec/unit/mutant/transform/hash_spec.rb +0 -236
  238. data/spec/unit/mutant/transform/index_spec.rb +0 -92
  239. data/spec/unit/mutant/transform/named_spec.rb +0 -49
  240. data/spec/unit/mutant/transform/primitive_spec.rb +0 -56
  241. data/spec/unit/mutant/transform/sequence_spec.rb +0 -98
  242. data/spec/unit/mutant/util/one_spec.rb +0 -22
  243. data/spec/unit/mutant/warnings_spec.rb +0 -89
  244. data/spec/unit/mutant/world_spec.rb +0 -63
  245. data/spec/unit/mutant/zombifier_spec.rb +0 -122
  246. data/test_app/.rspec +0 -1
  247. data/test_app/Gemfile.minitest +0 -6
  248. data/test_app/Gemfile.rspec3.8 +0 -7
  249. data/test_app/lib/test_app.rb +0 -114
  250. data/test_app/lib/test_app/literal.rb +0 -35
  251. data/test_app/lib/test_app/metaclasses.rb +0 -108
  252. data/test_app/spec/spec_helper.rb +0 -9
  253. data/test_app/spec/unit/test_app/literal_spec.rb +0 -20
  254. data/test_app/test/unit/test_app/literal_test.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45484c558fad8e5115eb2b4c45712b762eb1d13f12fe7e7a29d9ec0696a16e24
4
- data.tar.gz: 5354afd8cf7491687dfef3af68157e37cc549a0190d09bb8755c8bcf33d24051
3
+ metadata.gz: fd38b14761b4836ca3ce54a9bd8ea7a25d1f4dac6506b08ab6f6c64227a0f71d
4
+ data.tar.gz: dbed2538feb3240195e76b8b10895ea01ba7dc827820a6106d00fa4a55da97d1
5
5
  SHA512:
6
- metadata.gz: 1546be8318d6135d2834120bcc3052b879d6568560a9ec5dd056448a46cdd240fede609a4336fb5e64937526aa2db64627285c70dc45b09aa0175d6abc471749
7
- data.tar.gz: b8705cb2eb82b286d77ea87a17af903661747cfad61d3e51fcc70b1f906bcf47b07e33305810f1d59cc4f3d2e802b5eae20e0ce4ecf37b686c2fd73e1bc41933
6
+ metadata.gz: c70d6881956ace0d359b13dcfd41ef4eb92dbabcf5ee90d913790b8a09bb1b12877e32c85453d3be97d9fd2a1523a0af307ebf7318550cd0e3413fc8d9ecb765
7
+ data.tar.gz: 27b59ee208993bfc5b128269658fc4465782052613b0641c9647ace34cc10b956c72c8e05a5b8a6f86af77112589295e3289e53192d973500fe43ceed84180fc
data/bin/mutant CHANGED
@@ -8,8 +8,14 @@ end
8
8
 
9
9
  require 'mutant'
10
10
 
11
- namespace =
12
- if ARGV.include?('--zombie')
11
+ command = Mutant::CLI.parse(
12
+ arguments: ARGV,
13
+ config: Mutant::Config::DEFAULT,
14
+ world: Mutant::WORLD
15
+ )
16
+
17
+ status =
18
+ if command.zombie?
13
19
  $stderr.puts('Running mutant zombified!')
14
20
  Mutant::Zombifier.call(
15
21
  namespace: :Zombie,
@@ -31,15 +37,14 @@ namespace =
31
37
  concord
32
38
  ]
33
39
  )
34
- Zombie::Mutant
40
+
41
+ Zombie::Mutant::CLI.parse(
42
+ arguments: ARGV,
43
+ config: Zombie::Mutant::Config::DEFAULT,
44
+ world: Zombie::Mutant::WORLD
45
+ ).call
35
46
  else
36
- Mutant
47
+ command.call
37
48
  end
38
49
 
39
- Kernel.exit(
40
- namespace::CLI.run(
41
- namespace::WORLD,
42
- namespace::Config::DEFAULT,
43
- ARGV
44
- )
45
- )
50
+ Kernel.exit(status)
@@ -90,8 +90,7 @@ require 'mutant/mutator/node/arguments'
90
90
  require 'mutant/mutator/node/begin'
91
91
  require 'mutant/mutator/node/binary'
92
92
  require 'mutant/mutator/node/const'
93
- require 'mutant/mutator/node/dstr'
94
- require 'mutant/mutator/node/dsym'
93
+ require 'mutant/mutator/node/dynamic_literal'
95
94
  require 'mutant/mutator/node/kwbegin'
96
95
  require 'mutant/mutator/node/named_value/access'
97
96
  require 'mutant/mutator/node/named_value/constant_assignment'
@@ -120,6 +119,7 @@ require 'mutant/mutator/node/nthref'
120
119
  require 'mutant/mutator/node/masgn'
121
120
  require 'mutant/mutator/node/return'
122
121
  require 'mutant/mutator/node/block'
122
+ require 'mutant/mutator/node/block_pass'
123
123
  require 'mutant/mutator/node/if'
124
124
  require 'mutant/mutator/node/case'
125
125
  require 'mutant/mutator/node/splat'
@@ -165,6 +165,10 @@ require 'mutant/selector/expression'
165
165
  require 'mutant/selector/null'
166
166
  require 'mutant/config'
167
167
  require 'mutant/cli'
168
+ require 'mutant/cli/command'
169
+ require 'mutant/cli/command/run'
170
+ require 'mutant/cli/command/subscription'
171
+ require 'mutant/cli/command/root'
168
172
  require 'mutant/runner'
169
173
  require 'mutant/runner/sink'
170
174
  require 'mutant/result'
@@ -211,8 +215,8 @@ module Mutant
211
215
  open3: Open3,
212
216
  pathname: Pathname,
213
217
  process: Process,
214
- stderr: STDERR,
215
- stdout: STDOUT,
218
+ stderr: $stderr,
219
+ stdout: $stdout,
216
220
  thread: Thread,
217
221
  warnings: Warnings.new(Warning)
218
222
  )
@@ -36,7 +36,7 @@ module Mutant
36
36
  .tap(&method(:infect))
37
37
  .with(matchable_scopes: matchable_scopes(world, config))
38
38
 
39
- subjects = Matcher.from_config(env.config.matcher).call(env)
39
+ subjects = start_subject(env, Matcher.from_config(env.config.matcher).call(env))
40
40
 
41
41
  Integration.setup(env).fmap do |integration|
42
42
  env.with(
@@ -49,6 +49,19 @@ module Mutant
49
49
  end
50
50
  # rubocop:enable Metrics/MethodLength
51
51
 
52
+ def self.start_subject(env, subjects)
53
+ start_expressions = env.config.matcher.start_expressions
54
+
55
+ return subjects if start_expressions.empty?
56
+
57
+ subjects.drop_while do |subject|
58
+ start_expressions.none? do |expression|
59
+ expression.prefix?(subject.expression)
60
+ end
61
+ end
62
+ end
63
+ private_class_method :start_subject
64
+
52
65
  def self.infect(env)
53
66
  config, world = env.config, env.world
54
67
 
@@ -1,168 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mutant
4
- # Commandline parser / runner
5
- class CLI
6
- include Concord.new(:world, :config)
7
-
8
- private_class_method :new
9
-
10
- OPTIONS =
11
- %i[
12
- add_environment_options
13
- add_mutation_options
14
- add_filter_options
15
- add_debug_options
16
- ].freeze
17
-
18
- private_constant(*constants(false))
19
-
20
- # Run cli with arguments
21
- #
22
- # @param [World] world
23
- # the outside world
24
- #
25
- # @param [Config] default_config
26
- # the default config
27
- #
28
- # @param [Array<String>]
29
- # the user provided arguments
30
- #
31
- # @return [Boolean]
32
- #
33
- # rubocop:disable Style/Semicolon
34
- #
35
- # ignore :reek:LongParameterList
36
- def self.run(world, default_config, arguments)
37
- License
38
- .apply(world)
39
- .bind { Config.load_config_file(world, default_config) }
40
- .bind { |file_config| apply(world, file_config, arguments) }
41
- .bind { |cli_config| Bootstrap.apply(world, cli_config) }
42
- .bind(&Runner.method(:apply))
43
- .from_right { |error| world.stderr.puts(error); return false }
44
- .success?
45
- end
46
- # rubocop:enable Style/Semicolon
47
-
48
- # Parse arguments into config
49
- #
50
- # @param [World] world
51
- # @param [Config] config
52
- # @param [Array<String>] arguments
53
- #
54
- # @return [Either<OptionParser::ParseError, Config>]
55
- #
56
- # ignore :reek:LongParameterList
57
- def self.apply(world, config, arguments)
58
- Either
59
- .wrap_error(OptionParser::ParseError) { new(world, config).parse(arguments) }
60
- .lmap(&:message)
61
- end
62
-
63
- # Local opt out of option parser defaults
64
- class OptionParser < ::OptionParser
65
- # Kill defaults added by option parser that
66
- # inference with ours under mutation testing.
67
- define_method(:add_officious) {}
68
- end # OptionParser
69
-
70
- # Parse the command-line options
71
- #
72
- # @param [Array<String>] arguments
73
- # Command-line options and arguments to be parsed.
74
- #
75
- # @return [Config]
76
- def parse(arguments)
77
- opts = OptionParser.new do |builder|
78
- builder.banner = 'usage: mutant [options] MATCH_EXPRESSION ...'
79
- OPTIONS.each do |name|
80
- __send__(name, builder)
81
- end
82
- end
83
-
84
- parse_match_expressions(opts.parse!(arguments.dup))
85
-
86
- config
87
- end
88
-
89
- private
90
-
91
- def parse_match_expressions(expressions)
92
- expressions.each do |expression|
93
- add_matcher(:match_expressions, config.expression_parser.apply(expression).from_right)
94
- end
95
- end
96
-
97
- # rubocop:disable Metrics/MethodLength
98
- def add_environment_options(opts)
99
- opts.separator('Environment:')
100
- opts.on('--zombie', 'Run mutant zombified') do
101
- with(zombie: true)
102
- end
103
- opts.on('-I', '--include DIRECTORY', 'Add DIRECTORY to $LOAD_PATH') do |directory|
104
- add(:includes, directory)
105
- end
106
- opts.on('-r', '--require NAME', 'Require file with NAME') do |name|
107
- add(:requires, name)
108
- end
109
- opts.on('-j', '--jobs NUMBER', 'Number of kill jobs. Defaults to number of processors.') do |number|
110
- with(jobs: Integer(number))
111
- end
112
- end
113
- # rubocop:enable Metrics/MethodLength
114
-
115
- def add_mutation_options(opts)
116
- opts.separator(nil)
117
- opts.separator('Options:')
118
-
119
- opts.on('--use INTEGRATION', 'Use INTEGRATION to kill mutations') do |name|
120
- with(integration: name)
121
- end
122
- end
123
-
124
- # rubocop:disable Metrics/MethodLength
125
- def add_filter_options(opts)
126
- opts.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
127
- add_matcher(:ignore_expressions, config.expression_parser.apply(pattern).from_right)
128
- end
129
- opts.on('--since REVISION', 'Only select subjects touched since REVISION') do |revision|
130
- add_matcher(
131
- :subject_filters,
132
- Repository::SubjectFilter.new(
133
- Repository::Diff.new(to: revision, world: world)
134
- )
135
- )
136
- end
137
- end
138
- # rubocop:enable Metrics/MethodLength
139
-
140
- # rubocop:disable Metrics/MethodLength
141
- def add_debug_options(opts)
142
- opts.on('--fail-fast', 'Fail fast') do
143
- with(fail_fast: true)
144
- end
145
- opts.on('--version', 'Print mutants version') do
146
- world.stdout.puts("mutant-#{VERSION}")
147
- world.kernel.exit
148
- end
149
- opts.on_tail('-h', '--help', 'Show this message') do
150
- world.stdout.puts(opts.to_s)
151
- world.kernel.exit
152
- end
153
- end
154
- # rubocop:enable Metrics/MethodLength
155
-
156
- def with(attributes)
157
- @config = config.with(attributes)
158
- end
159
-
160
- def add(attribute, value)
161
- with(attribute => config.public_send(attribute) + [value])
162
- end
163
-
164
- def add_matcher(attribute, value)
165
- with(matcher: config.matcher.add(attribute, value))
4
+ # Commandline interface
5
+ module CLI
6
+ # Parse command
7
+ #
8
+ # @return [Command]
9
+ def self.parse(world:, **attributes)
10
+ Command::Root
11
+ .parse(world: world, **attributes)
12
+ .from_right { |message| Command::FailParse.new(world, message) }
166
13
  end
167
14
  end # CLI
168
15
  end # Mutant
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module CLI
5
+ # rubocop:disable Metrics/ClassLength
6
+ class Command
7
+ include AbstractType, Anima.new(:world, :config, :main, :parent, :arguments)
8
+
9
+ include Equalizer.new(:parent, :arguments)
10
+
11
+ OPTIONS = [].freeze
12
+ SUBCOMMANDS = [].freeze
13
+
14
+ # Local opt out of option parser defaults
15
+ class OptionParser < ::OptionParser
16
+ # Kill defaults added by option parser that
17
+ # inference with ours under mutation testing.
18
+ define_method(:add_officious) {}
19
+ end # OptionParser
20
+
21
+ class FailParse < self
22
+ include Concord.new(:world, :message)
23
+
24
+ def call
25
+ world.stderr.puts(message)
26
+ false
27
+ end
28
+ end
29
+
30
+ # Parse command
31
+ #
32
+ # @return [Command]
33
+ def self.parse(**attributes)
34
+ new(main: nil, parent: nil, **attributes).__send__(:parse)
35
+ end
36
+
37
+ # Command name
38
+ #
39
+ # @return [String]
40
+ def self.command_name
41
+ self::NAME
42
+ end
43
+
44
+ # Command short description
45
+ #
46
+ # @return [String]
47
+ def self.short_description
48
+ self::SHORT_DESCRIPTION
49
+ end
50
+
51
+ # Execute the command, invoke its side effects
52
+ #
53
+ # @return [Bool]
54
+ def call
55
+ main ? main.call : execute
56
+ end
57
+
58
+ # Commands full name
59
+ #
60
+ # @return [String]
61
+ def full_name
62
+ [*parent&.full_name, self.class.command_name].join(' ')
63
+ end
64
+
65
+ # Test if command needs to be executed in zombie environment
66
+ #
67
+ # @return [Bool]
68
+ def zombie?
69
+ instance_of?(Run)
70
+ end
71
+
72
+ private
73
+
74
+ def subcommands
75
+ self.class::SUBCOMMANDS
76
+ end
77
+
78
+ def parser
79
+ OptionParser.new do |parser|
80
+ parser.banner = "usage: #{banner}"
81
+
82
+ add_summary(parser)
83
+ add_global_options(parser)
84
+ add_subcommands(parser)
85
+
86
+ self.class::OPTIONS.each do |method_name|
87
+ 2.times { parser.separator(nil) }
88
+ __send__(method_name, parser)
89
+ end
90
+ end
91
+ end
92
+
93
+ def capture_main(&block)
94
+ @main = block
95
+ end
96
+
97
+ def banner
98
+ if subcommands.any?
99
+ "#{full_name} <#{subcommands.map(&:command_name).join('|')}> [options]"
100
+ else
101
+ "#{full_name} [options]"
102
+ end
103
+ end
104
+
105
+ def parse
106
+ Either
107
+ .wrap_error(OptionParser::InvalidOption) { parser.order(arguments) }
108
+ .lmap { |error| "#{full_name}: #{error}" }
109
+ .bind(&method(:parse_remaining))
110
+ end
111
+
112
+ def add_summary(parser)
113
+ parser.separator(nil)
114
+ parser.separator("Summary: #{self.class.short_description}")
115
+ parser.separator(nil)
116
+ end
117
+
118
+ def add_global_options(parser)
119
+ parser.separator('Global Options:')
120
+ parser.separator(nil)
121
+
122
+ parser.on('--help', 'Print help') do
123
+ capture_main { world.stdout.puts(parser.help); true }
124
+ end
125
+
126
+ parser.on('--version', 'Print mutants version') do
127
+ capture_main { world.stdout.puts("mutant-#{VERSION}"); true }
128
+ end
129
+ end
130
+
131
+ def add_subcommands(parser)
132
+ return unless subcommands.any?
133
+
134
+ parser.separator(nil)
135
+ parser.separator('Available subcommands:')
136
+ parser.separator(nil)
137
+ parser.separator(format_subcommands)
138
+ end
139
+
140
+ def parse_remaining(remaining)
141
+ return Either::Right.new(self) if main
142
+
143
+ if subcommands.any?
144
+ parse_subcommand(remaining)
145
+ else
146
+ parse_remaining_arguments(remaining)
147
+ end
148
+ end
149
+
150
+ def parse_remaining_arguments(remaining)
151
+ if remaining.any?
152
+ Either::Left.new("#{full_name}: Does not expect extra arguments")
153
+ else
154
+ Either::Right.new(self)
155
+ end
156
+ end
157
+
158
+ def parse_subcommand(arguments)
159
+ command_name, *arguments = arguments
160
+
161
+ if command_name.nil?
162
+ Either::Left.new(
163
+ "Missing required subcommand!\n\n#{parser}"
164
+ )
165
+ else
166
+ find_command(command_name).bind do |command|
167
+ command.parse(**to_h, parent: self, arguments: arguments)
168
+ end
169
+ end
170
+ end
171
+
172
+ def format_subcommands
173
+ commands = subcommands.map do |subcommand|
174
+ [subcommand.command_name, subcommand.short_description]
175
+ end.to_h
176
+
177
+ width = commands.each_key.map(&:length).max
178
+
179
+ commands.each_key.map do |name|
180
+ '%-*s - %s' % [width, name, commands.fetch(name)] # rubocop:disable Style/FormatStringToken
181
+ end
182
+ end
183
+
184
+ def find_command(name)
185
+ subcommand = subcommands.detect { |command| command.command_name.eql?(name) }
186
+
187
+ if subcommand
188
+ Either::Right.new(subcommand)
189
+ else
190
+ Either::Left.new("#{full_name}: Cannot find subcommand #{name.inspect}")
191
+ end
192
+ end
193
+ end # Command
194
+ # rubocop:enable Metrics/ClassLength
195
+ end # CLI
196
+ end # Mutant