mutant 0.10.6 → 0.10.11

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +7 -4
  3. data/lib/mutant/cli/command.rb +1 -1
  4. data/lib/mutant/cli/command/{run.rb → environment.rb} +10 -45
  5. data/lib/mutant/cli/command/environment/run.rb +56 -0
  6. data/lib/mutant/cli/command/environment/show.rb +29 -0
  7. data/lib/mutant/cli/command/root.rb +7 -3
  8. data/lib/mutant/config.rb +61 -34
  9. data/lib/mutant/env.rb +14 -4
  10. data/lib/mutant/integration.rb +16 -10
  11. data/lib/mutant/integration/null.rb +0 -1
  12. data/lib/mutant/isolation.rb +11 -48
  13. data/lib/mutant/isolation/fork.rb +107 -40
  14. data/lib/mutant/isolation/none.rb +18 -5
  15. data/lib/mutant/license/subscription/commercial.rb +2 -3
  16. data/lib/mutant/license/subscription/opensource.rb +0 -1
  17. data/lib/mutant/matcher/method/instance.rb +0 -2
  18. data/lib/mutant/mutator/node/send.rb +1 -1
  19. data/lib/mutant/parallel.rb +0 -1
  20. data/lib/mutant/parallel/worker.rb +0 -2
  21. data/lib/mutant/reporter/cli.rb +0 -2
  22. data/lib/mutant/reporter/cli/printer/config.rb +9 -5
  23. data/lib/mutant/reporter/cli/printer/coverage_result.rb +19 -0
  24. data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -0
  25. data/lib/mutant/reporter/cli/printer/isolation_result.rb +19 -35
  26. data/lib/mutant/reporter/cli/printer/mutation_result.rb +4 -9
  27. data/lib/mutant/reporter/cli/printer/subject_result.rb +2 -2
  28. data/lib/mutant/result.rb +91 -30
  29. data/lib/mutant/runner/sink.rb +12 -5
  30. data/lib/mutant/timer.rb +60 -11
  31. data/lib/mutant/transform.rb +25 -21
  32. data/lib/mutant/version.rb +1 -1
  33. data/lib/mutant/warnings.rb +0 -1
  34. data/lib/mutant/world.rb +15 -0
  35. metadata +6 -6
  36. data/lib/mutant/reporter/cli/printer/mutation_progress_result.rb +0 -28
  37. data/lib/mutant/reporter/cli/printer/subject_progress.rb +0 -58
  38. data/lib/mutant/reporter/cli/printer/test_result.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b5a7d1620b6792fa1cef90ead01124cf50287a165a0bdd415fbc9aab9ecfde8
4
- data.tar.gz: 758609938988d4d8de30a6f95a47c603fc4d5365ba98ad1a113a6de11f8e074f
3
+ metadata.gz: f812d15b5c09f8681cc6c75eb01bd00fffe412bc53397b9e13c29c7cad69db9a
4
+ data.tar.gz: 45c24afcbfbef29a50b90b81946e75391c58ecea7ac32b9545a5e7fdc22fd063
5
5
  SHA512:
6
- metadata.gz: 3ecd6aea62e2d01cb68cc9c9c651190305459b7f1b796f4360ce872ce0dbb1871cb40821744d1c09a029a80f39a93cbda9a242591ceae405e47ed971d3d861f9
7
- data.tar.gz: '09e132e435163308309c0185abdd39ea9b6be0d2bd0efa0956049c366f0b521df866a3b7fd04d2e13550bd4f2b09993c659530f86b00142adff69ebc14af3a16'
6
+ metadata.gz: fcb16a15ec71fe5dc51d07e08b426e8dc68e2f4bad2bd47f95a19b136cd06dbefdc9be44f4a7e0723b8a9450b4b924ab2744f8be05b09fbf1d6e9774431f2e97
7
+ data.tar.gz: b9213ce07ce70908f072b23d92bd86c86492ed8f348ae2c7419ed625989a36b7b2d1e110048e14364e044790e7e086894c124f081043d989844de02adcc81684
@@ -167,8 +167,10 @@ require 'mutant/world'
167
167
  require 'mutant/config'
168
168
  require 'mutant/cli'
169
169
  require 'mutant/cli/command'
170
- require 'mutant/cli/command/run'
171
170
  require 'mutant/cli/command/subscription'
171
+ require 'mutant/cli/command/environment'
172
+ require 'mutant/cli/command/environment/run'
173
+ require 'mutant/cli/command/environment/show'
172
174
  require 'mutant/cli/command/root'
173
175
  require 'mutant/runner'
174
176
  require 'mutant/runner/sink'
@@ -179,16 +181,14 @@ require 'mutant/reporter/sequence'
179
181
  require 'mutant/reporter/cli'
180
182
  require 'mutant/reporter/cli/printer'
181
183
  require 'mutant/reporter/cli/printer/config'
184
+ require 'mutant/reporter/cli/printer/coverage_result'
182
185
  require 'mutant/reporter/cli/printer/env'
183
186
  require 'mutant/reporter/cli/printer/env_progress'
184
187
  require 'mutant/reporter/cli/printer/env_result'
185
188
  require 'mutant/reporter/cli/printer/isolation_result'
186
- require 'mutant/reporter/cli/printer/mutation_progress_result'
187
189
  require 'mutant/reporter/cli/printer/mutation_result'
188
190
  require 'mutant/reporter/cli/printer/status_progressive'
189
- require 'mutant/reporter/cli/printer/subject_progress'
190
191
  require 'mutant/reporter/cli/printer/subject_result'
191
- require 'mutant/reporter/cli/printer/test_result'
192
192
  require 'mutant/reporter/cli/format'
193
193
  require 'mutant/repository'
194
194
  require 'mutant/repository/diff'
@@ -219,12 +219,14 @@ module Mutant
219
219
  stderr: $stderr,
220
220
  stdout: $stdout,
221
221
  thread: Thread,
222
+ timer: Timer.new(Process),
222
223
  warnings: Warnings.new(Warning)
223
224
  )
224
225
 
225
226
  # Reopen class to initialize constant to avoid dep circle
226
227
  class Config
227
228
  DEFAULT = new(
229
+ coverage_criteria: Config::CoverageCriteria::DEFAULT,
228
230
  expression_parser: Expression::Parser.new([
229
231
  Expression::Method,
230
232
  Expression::Methods,
@@ -237,6 +239,7 @@ module Mutant
237
239
  isolation: Mutant::Isolation::Fork.new(WORLD),
238
240
  jobs: nil,
239
241
  matcher: Matcher::Config::DEFAULT,
242
+ mutation_timeout: nil,
240
243
  reporter: Reporter::CLI.build(WORLD.stdout),
241
244
  requires: EMPTY_ARRAY,
242
245
  zombie: false
@@ -66,7 +66,7 @@ module Mutant
66
66
  #
67
67
  # @return [Bool]
68
68
  def zombie?
69
- instance_of?(Run)
69
+ false
70
70
  end
71
71
 
72
72
  private
@@ -3,10 +3,9 @@
3
3
  module Mutant
4
4
  module CLI
5
5
  class Command
6
- # rubocop:disable Metrics/ClassLength
7
- class Run < self
8
- NAME = 'run'
9
- SHORT_DESCRIPTION = 'Run code analysis'
6
+ class Environment < self
7
+ NAME = 'environment'
8
+ SHORT_DESCRIPTION = 'Environment subcommands'
10
9
 
11
10
  OPTIONS =
12
11
  %i[
@@ -16,57 +15,21 @@ module Mutant
16
15
  add_matcher_options
17
16
  ].freeze
18
17
 
19
- SLEEP = 40
20
-
21
- UNLICENSED = <<~MESSAGE.lines.freeze
22
- Soft fail, continuing in #{SLEEP} seconds
23
- Next major version will enforce the license
24
- See https://github.com/mbj/mutant#licensing
25
- MESSAGE
26
-
27
- # Test if command needs to be executed in zombie environment
28
- #
29
- # @return [Bool]
30
- def zombie?
31
- @config.zombie
32
- end
33
-
34
18
  private
35
19
 
36
20
  def initialize(attributes)
37
21
  super(attributes)
38
- @config = Config::DEFAULT
22
+ @config = Config.env
39
23
  end
40
24
 
41
- def execute
42
- soft_fail(License.apply(world))
43
- .bind { Config.load_config_file(world) }
25
+ def bootstrap
26
+ Config.load_config_file(world)
44
27
  .fmap(&method(:expand))
45
28
  .bind { Bootstrap.apply(world, @config) }
46
- .bind(&Runner.public_method(:apply))
47
- .from_right { |error| world.stderr.puts(error); return false }
48
- .success?
49
29
  end
50
30
 
51
31
  def expand(file_config)
52
- @config = Config.env.merge(file_config).merge(@config)
53
- end
54
-
55
- def soft_fail(result)
56
- result.either(
57
- lambda do |message|
58
- stderr = world.stderr
59
- stderr.puts(message)
60
- UNLICENSED.each { |line| stderr.puts(unlicensed(line)) }
61
- world.kernel.sleep(SLEEP)
62
- Either::Right.new(nil)
63
- end,
64
- ->(_subscription) { Either::Right.new(nil) }
65
- )
66
- end
67
-
68
- def unlicensed(message)
69
- "[Mutant-License-Error]: #{message}"
32
+ @config = file_config.merge(@config)
70
33
  end
71
34
 
72
35
  def parse_remaining_arguments(arguments)
@@ -153,9 +116,11 @@ module Mutant
153
116
  parser.on('-j', '--jobs NUMBER', 'Number of kill jobs. Defaults to number of processors.') do |number|
154
117
  set(jobs: Integer(number))
155
118
  end
119
+ parser.on('-t', '--mutation-timeout NUMBER', 'Per mutation analysis timeout') do |number|
120
+ set(mutation_timeout: Float(number))
121
+ end
156
122
  end
157
123
  end # Run
158
- # rubocop:enable Metrics/ClassLength
159
124
  end # Command
160
125
  end # CLI
161
126
  end # Mutant
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module CLI
5
+ class Command
6
+ class Environment
7
+ class Run < self
8
+ NAME = 'run'
9
+ SHORT_DESCRIPTION = 'Run code analysis'
10
+ SLEEP = 40
11
+ SUBCOMMANDS = EMPTY_ARRAY
12
+
13
+ UNLICENSED = <<~MESSAGE.lines.freeze
14
+ Soft fail, continuing in #{SLEEP} seconds
15
+ Next major version will enforce the license
16
+ See https://github.com/mbj/mutant#licensing
17
+ MESSAGE
18
+
19
+ # Test if command needs to be executed in zombie environment
20
+ #
21
+ # @return [Bool]
22
+ def zombie?
23
+ @config.zombie
24
+ end
25
+
26
+ private
27
+
28
+ def execute
29
+ soft_fail(License.apply(world))
30
+ .bind { bootstrap }
31
+ .bind(&Runner.public_method(:apply))
32
+ .from_right { |error| world.stderr.puts(error); return false }
33
+ .success?
34
+ end
35
+
36
+ def soft_fail(result)
37
+ result.either(
38
+ lambda do |message|
39
+ stderr = world.stderr
40
+ stderr.puts(message)
41
+ UNLICENSED.each { |line| stderr.puts(unlicensed(line)) }
42
+ world.kernel.sleep(SLEEP)
43
+ Either::Right.new(nil)
44
+ end,
45
+ ->(_subscription) { Either::Right.new(nil) }
46
+ )
47
+ end
48
+
49
+ def unlicensed(message)
50
+ "[Mutant-License-Error]: #{message}"
51
+ end
52
+ end # Run
53
+ end # Environment
54
+ end # Command
55
+ end # CLI
56
+ end # Mutant
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module CLI
5
+ class Command
6
+ class Environment
7
+ class Show < self
8
+ NAME = 'show'
9
+ SHORT_DESCRIPTION = 'Display environment without coverage analysis'
10
+ SUBCOMMANDS = EMPTY_ARRAY
11
+
12
+ private
13
+
14
+ def execute
15
+ Config.load_config_file(world)
16
+ .fmap(&method(:expand))
17
+ .bind { Bootstrap.apply(world, @config) }
18
+ .fmap(&method(:report_env))
19
+ .right?
20
+ end
21
+
22
+ def report_env(env)
23
+ env.config.reporter.start(env)
24
+ end
25
+ end # Show
26
+ end # Environment
27
+ end # Command
28
+ end # CLI
29
+ end # Mutant
@@ -3,11 +3,15 @@
3
3
  module Mutant
4
4
  module CLI
5
5
  class Command
6
+ class Environment < self
7
+ SUBCOMMANDS = [Environment::Show].freeze
8
+ end # Environment
9
+
6
10
  class Root < self
7
- SUBCOMMANDS = [Run, Subscription].freeze
8
- SHORT_DESCRIPTION = 'mutation testing engine main command'
9
11
  NAME = 'mutant'
10
- end
12
+ SHORT_DESCRIPTION = 'mutation testing engine main command'
13
+ SUBCOMMANDS = [Environment::Run, Environment, Subscription].freeze
14
+ end # Root
11
15
  end # Command
12
16
  end # CLI
13
17
  end # Mutant
@@ -7,6 +7,7 @@ module Mutant
7
7
  # to current environment is being represented by the Mutant::Env object.
8
8
  class Config
9
9
  include Adamantium::Flat, Anima.new(
10
+ :coverage_criteria,
10
11
  :expression_parser,
11
12
  :fail_fast,
12
13
  :includes,
@@ -14,6 +15,7 @@ module Mutant
14
15
  :isolation,
15
16
  :jobs,
16
17
  :matcher,
18
+ :mutation_timeout,
17
19
  :reporter,
18
20
  :requires,
19
21
  :zombie
@@ -23,30 +25,6 @@ module Mutant
23
25
  define_method(:"#{name}?") { public_send(name) }
24
26
  end
25
27
 
26
- boolean = Transform::Boolean.new
27
- integer = Transform::Primitive.new(Integer)
28
- string = Transform::Primitive.new(String)
29
-
30
- string_array = Transform::Array.new(string)
31
-
32
- TRANSFORM = Transform::Sequence.new(
33
- [
34
- Transform::Exception.new(SystemCallError, :read.to_proc),
35
- Transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
36
- Transform::Hash.new(
37
- optional: [
38
- Transform::Hash::Key.new('fail_fast', boolean),
39
- Transform::Hash::Key.new('includes', string_array),
40
- Transform::Hash::Key.new('integration', string),
41
- Transform::Hash::Key.new('jobs', integer),
42
- Transform::Hash::Key.new('requires', string_array)
43
- ],
44
- required: []
45
- ),
46
- Transform::Hash::Symbolize.new
47
- ]
48
- )
49
-
50
28
  MORE_THAN_ONE_CONFIG_FILE = <<~'MESSAGE'
51
29
  Found more than one candidate for use as implicit config file: %s
52
30
  MESSAGE
@@ -57,6 +35,34 @@ module Mutant
57
35
  mutant.yml
58
36
  ].freeze
59
37
 
38
+ private_constant(*constants(false))
39
+
40
+ class CoverageCriteria
41
+ include Anima.new(:process_abort, :test_result, :timeout)
42
+
43
+ DEFAULT = new(
44
+ process_abort: false,
45
+ test_result: true,
46
+ timeout: false
47
+ )
48
+
49
+ TRANSFORM =
50
+ Transform::Sequence.new(
51
+ [
52
+ Transform::Hash.new(
53
+ optional: [
54
+ Transform::Hash::Key.new('process_abort', Transform::BOOLEAN),
55
+ Transform::Hash::Key.new('test_result', Transform::BOOLEAN),
56
+ Transform::Hash::Key.new('timeout', Transform::BOOLEAN)
57
+ ],
58
+ required: []
59
+ ),
60
+ Transform::Hash::Symbolize.new,
61
+ ->(value) { Either::Right.new(DEFAULT.with(**value)) }
62
+ ]
63
+ )
64
+ end # CoverageCriteria
65
+
60
66
  # Merge with other config
61
67
  #
62
68
  # @param [Config] other
@@ -64,18 +70,17 @@ module Mutant
64
70
  # @return [Config]
65
71
  def merge(other)
66
72
  other.with(
67
- fail_fast: fail_fast || other.fail_fast,
68
- includes: includes + other.includes,
69
- jobs: other.jobs || jobs,
70
- integration: other.integration || integration,
71
- matcher: matcher.merge(other.matcher),
72
- requires: requires + other.requires,
73
- zombie: zombie || other.zombie
73
+ fail_fast: fail_fast || other.fail_fast,
74
+ includes: includes + other.includes,
75
+ jobs: other.jobs || jobs,
76
+ integration: other.integration || integration,
77
+ mutation_timeout: other.mutation_timeout || mutation_timeout,
78
+ matcher: matcher.merge(other.matcher),
79
+ requires: requires + other.requires,
80
+ zombie: zombie || other.zombie
74
81
  )
75
82
  end
76
83
 
77
- private_constant(*constants(false))
78
-
79
84
  # Load config file
80
85
  #
81
86
  # @param [World] world
@@ -98,7 +103,7 @@ module Mutant
98
103
  def self.load_contents(path)
99
104
  Transform::Named
100
105
  .new(path.to_s, TRANSFORM)
101
- .apply(path)
106
+ .call(path)
102
107
  .lmap(&:compact_message)
103
108
  end
104
109
  private_class_method :load_contents
@@ -109,5 +114,27 @@ module Mutant
109
114
  def self.env
110
115
  DEFAULT.with(jobs: Etc.nprocessors)
111
116
  end
117
+
118
+ TRANSFORM = Transform::Sequence.new(
119
+ [
120
+ Transform::Exception.new(SystemCallError, :read.to_proc),
121
+ Transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
122
+ Transform::Hash.new(
123
+ optional: [
124
+ Transform::Hash::Key.new('coverage_criteria', CoverageCriteria::TRANSFORM),
125
+ Transform::Hash::Key.new('fail_fast', Transform::BOOLEAN),
126
+ Transform::Hash::Key.new('includes', Transform::STRING_ARRAY),
127
+ Transform::Hash::Key.new('integration', Transform::STRING),
128
+ Transform::Hash::Key.new('jobs', Transform::INTEGER),
129
+ Transform::Hash::Key.new('mutation_timeout', Transform::FLOAT),
130
+ Transform::Hash::Key.new('requires', Transform::STRING_ARRAY)
131
+ ],
132
+ required: []
133
+ ),
134
+ Transform::Hash::Symbolize.new
135
+ ]
136
+ )
137
+
138
+ private_constant(:TRANSFORM)
112
139
  end # Config
113
140
  end # Mutant
@@ -24,10 +24,15 @@ module Mutant
24
24
  # @param [Config] config
25
25
  #
26
26
  # @return [Env]
27
+ #
28
+ # rubocop:disable Metrics/MethodLength
27
29
  def self.empty(world, config)
28
30
  new(
29
31
  config: config,
30
- integration: Integration::Null.new(config),
32
+ integration: Integration::Null.new(
33
+ expression_parser: config.expression_parser,
34
+ timer: world.timer
35
+ ),
31
36
  matchable_scopes: EMPTY_ARRAY,
32
37
  mutations: EMPTY_ARRAY,
33
38
  parser: Parser.new,
@@ -36,6 +41,7 @@ module Mutant
36
41
  world: world
37
42
  )
38
43
  end
44
+ # rubocop:enable Metrics/MethodLength
39
45
 
40
46
  # Kill mutation
41
47
  #
@@ -43,14 +49,14 @@ module Mutant
43
49
  #
44
50
  # @return [Result::Mutation]
45
51
  def kill(mutation)
46
- start = Timer.now
52
+ start = timer.now
47
53
 
48
54
  tests = selections.fetch(mutation.subject)
49
55
 
50
56
  Result::Mutation.new(
51
57
  isolation_result: run_mutation_tests(mutation, tests),
52
58
  mutation: mutation,
53
- runtime: Timer.now - start
59
+ runtime: timer.now - start
54
60
  )
55
61
  end
56
62
 
@@ -127,7 +133,7 @@ module Mutant
127
133
  private
128
134
 
129
135
  def run_mutation_tests(mutation, tests)
130
- config.isolation.call do
136
+ config.isolation.call(config.mutation_timeout) do
131
137
  result = mutation.insert(world.kernel)
132
138
 
133
139
  if result.equal?(Loader::Result::VoidValue.instance)
@@ -138,5 +144,9 @@ module Mutant
138
144
  end
139
145
  end
140
146
 
147
+ def timer
148
+ world.timer
149
+ end
150
+
141
151
  end # Env
142
152
  end # Mutant