mutant 0.10.7 → 0.10.12

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: defe26105634d28a81b10e876713ab4e29a53a5d78ac8fe7e2d8a44d32b644c6
4
- data.tar.gz: fad9645931798ed165a249e8e8d4a1c032896ceba6b823a1464ff2f60a7cadbb
3
+ metadata.gz: 4c573735f4e77ea0dd9313412e82bbca47771e8fd379c5d53393e855248b7792
4
+ data.tar.gz: 7c59c273789c9fb3d85dd858a7ed7d6bec85b8440ad05736f70541325d214947
5
5
  SHA512:
6
- metadata.gz: 79da9f75f18510bbf81e43e2b98e43c4b92ddf2e6cb68b66828f9f4240f7d3e8747d6e475130f83a2d1edec8c1dda43a34f48524bd7439372e545a046099c28d
7
- data.tar.gz: 7d8aac2776eca84aff051b7a146863bb271c5df4ec7dc344f041d3a22a17b4d87f9f48d5201e2dceec30f548adc7135d443ee78bc00764a9399fb677f2cf0aa5
6
+ metadata.gz: ff3a349908f6f3f83ace0ce4de9555b981f6a82515954e635a7539b2d8852d9bfa740a2c33afd2b3b49a34e0358920be9d0c22b8c05c2903356d75b9c4a4ccc6
7
+ data.tar.gz: 2b3cc3f947b50bc407c8c5a3239ad5bbddf2ff2adc773e661f8279d650297accaad05cdcdbce5e64ae04e419dd63740e0de493542f44464bb5d7d7ee60ab37c1
@@ -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'
@@ -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.env
22
+ @config = Config::DEFAULT
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.merge(file_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 = Config.env.merge(file_config).merge(@config)
70
33
  end
71
34
 
72
35
  def parse_remaining_arguments(arguments)
@@ -158,7 +121,6 @@ module Mutant
158
121
  end
159
122
  end
160
123
  end # Run
161
- # rubocop:enable Metrics/ClassLength
162
124
  end # Command
163
125
  end # CLI
164
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
@@ -38,11 +38,12 @@ module Mutant
38
38
  private_constant(*constants(false))
39
39
 
40
40
  class CoverageCriteria
41
- include Anima.new(:timeout, :test_result)
41
+ include Anima.new(:process_abort, :test_result, :timeout)
42
42
 
43
43
  DEFAULT = new(
44
- timeout: false,
45
- test_result: true
44
+ process_abort: false,
45
+ test_result: true,
46
+ timeout: false
46
47
  )
47
48
 
48
49
  TRANSFORM =
@@ -50,8 +51,9 @@ module Mutant
50
51
  [
51
52
  Transform::Hash.new(
52
53
  optional: [
53
- Transform::Hash::Key.new('timeout', Transform::BOOLEAN),
54
- Transform::Hash::Key.new('test_result', Transform::BOOLEAN)
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)
55
57
  ],
56
58
  required: []
57
59
  ),
@@ -69,12 +71,12 @@ module Mutant
69
71
  def merge(other)
70
72
  other.with(
71
73
  fail_fast: fail_fast || other.fail_fast,
72
- includes: other.includes + includes,
74
+ includes: includes + other.includes,
73
75
  jobs: other.jobs || jobs,
74
76
  integration: other.integration || integration,
75
77
  mutation_timeout: other.mutation_timeout || mutation_timeout,
76
78
  matcher: matcher.merge(other.matcher),
77
- requires: other.requires + requires,
79
+ requires: requires + other.requires,
78
80
  zombie: zombie || other.zombie
79
81
  )
80
82
  end
@@ -19,6 +19,11 @@ module Mutant
19
19
  The integration is supposed to define %<constant_name>s!
20
20
  MESSAGE
21
21
 
22
+ INTEGRATION_MISSING = <<~'MESSAGE'
23
+ No test framework integration configured.
24
+ See https://github.com/mbj/mutant/blob/master/docs/configuration.md#integration
25
+ MESSAGE
26
+
22
27
  private_constant(*constants(false))
23
28
 
24
29
  # Setup integration
@@ -27,6 +32,10 @@ module Mutant
27
32
  #
28
33
  # @return [Either<String, Integration>]
29
34
  def self.setup(env)
35
+ unless env.config.integration
36
+ return Either::Left.new(INTEGRATION_MISSING)
37
+ end
38
+
30
39
  attempt_require(env).bind { attempt_const_get(env) }.fmap do |klass|
31
40
  klass.new(
32
41
  expression_parser: env.config.expression_parser,
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  module License
5
5
  NAME = 'mutant-license'
6
- VERSION = '~> 0.1.0'
6
+ VERSION = ['>= 0.1', '< 0.3'].freeze
7
7
 
8
8
  # Load license
9
9
  #
@@ -20,7 +20,7 @@ module Mutant
20
20
 
21
21
  def self.load_mutant_license(world)
22
22
  Either
23
- .wrap_error(LoadError) { world.gem_method.call(NAME, VERSION) }
23
+ .wrap_error(LoadError) { world.gem_method.call(NAME, *VERSION) }
24
24
  .lmap(&:message)
25
25
  .lmap(&method(:check_for_rubygems_mutant_license))
26
26
  end
@@ -17,8 +17,7 @@ module Mutant
17
17
  #
18
18
  # @return [undefined]
19
19
  def self.add(*types, &block)
20
- file = caller.first.split(':in', 2).first
21
- ALL << DSL.call(file, Set.new(types), block)
20
+ ALL << DSL.call(caller_locations(1).first, Set.new(types), block)
22
21
  end
23
22
 
24
23
  Pathname.glob(Pathname.new(__dir__).parent.parent.join('meta', '*.rb'))
@@ -3,10 +3,11 @@
3
3
  module Mutant
4
4
  module Meta
5
5
  class Example
6
- include Adamantium
6
+ include Adamantium::Flat
7
+
7
8
  include Anima.new(
8
9
  :expected,
9
- :file,
10
+ :location,
10
11
  :lvars,
11
12
  :node,
12
13
  :original_source,
@@ -25,6 +26,23 @@ module Mutant
25
26
  end
26
27
  memoize :verification
27
28
 
29
+ # Example identification
30
+ #
31
+ # @return [String]
32
+ def identification
33
+ location.to_s
34
+ end
35
+
36
+ # Context of mutation
37
+ #
38
+ # @return [Context]
39
+ def context
40
+ Context.new(
41
+ Object,
42
+ location.path
43
+ )
44
+ end
45
+
28
46
  # Original source as generated by unparser
29
47
  #
30
48
  # @return [String]
@@ -9,12 +9,12 @@ module Mutant
9
9
 
10
10
  # Run DSL on block
11
11
  #
12
- # @param [Pathname] file
12
+ # @param [Thread::Backtrace::Location] location
13
13
  # @param [Set<Symbol>] types
14
14
  #
15
15
  # @return [Example]
16
- def self.call(file, types, block)
17
- instance = new(file, types)
16
+ def self.call(location, types, block)
17
+ instance = new(location, types)
18
18
  instance.instance_eval(&block)
19
19
  instance.example
20
20
  end
@@ -24,12 +24,12 @@ module Mutant
24
24
  # Initialize object
25
25
  #
26
26
  # @return [undefined]
27
- def initialize(file, types)
28
- @expected = []
29
- @file = file
30
- @lvars = []
31
- @source = nil
32
- @types = types
27
+ def initialize(location, types)
28
+ @expected = []
29
+ @location = location
30
+ @lvars = []
31
+ @source = nil
32
+ @types = types
33
33
  end
34
34
 
35
35
  # Example captured by DSL
@@ -40,9 +40,10 @@ module Mutant
40
40
  # in case example cannot be build
41
41
  def example
42
42
  fail 'source not defined' unless @source
43
+
43
44
  Example.new(
44
45
  expected: @expected,
45
- file: @file,
46
+ location: @location,
46
47
  lvars: @lvars,
47
48
  node: @node,
48
49
  original_source: @source,
@@ -28,7 +28,7 @@ module Mutant
28
28
  private
29
29
 
30
30
  def reports
31
- reports = [example.file]
31
+ reports = [example.location]
32
32
  reports.concat(original)
33
33
  reports.concat(original_verification)
34
34
  reports.concat(make_report('Missing mutations:', missing))
@@ -94,7 +94,7 @@ module Mutant
94
94
 
95
95
  def missing
96
96
  (example.expected.map(&:node) - mutations.map(&:node)).map do |node|
97
- Mutation::Evil.new(nil, node)
97
+ Mutation::Evil.new(example, node)
98
98
  end
99
99
  end
100
100
  memoize :missing
@@ -9,37 +9,34 @@ module Mutant
9
9
  CODE_DELIMITER = "\0"
10
10
  CODE_RANGE = (0..4).freeze
11
11
 
12
- # Identification string
13
- #
14
- # @return [String]
15
- def identification
16
- "#{self.class::SYMBOL}:#{subject.identification}:#{code}"
12
+ def initialize(subject, node)
13
+ super(subject, node)
14
+
15
+ @source = Unparser.unparse(node)
16
+ @code = sha1[CODE_RANGE]
17
+ @identification = "#{self.class::SYMBOL}:#{subject.identification}:#{code}"
18
+ @monkeypatch = Unparser.unparse(subject.context.root(node))
17
19
  end
18
- memoize :identification
19
20
 
20
- # Mutation code
21
+ # Mutation identification code
21
22
  #
22
23
  # @return [String]
23
- def code
24
- sha1[CODE_RANGE]
25
- end
26
- memoize :code
24
+ attr_reader :code
27
25
 
28
26
  # Normalized mutation source
29
27
  #
30
28
  # @return [String]
31
- def source
32
- Unparser.unparse(node)
33
- end
34
- memoize :source
29
+ attr_reader :source
30
+
31
+ # Identification string
32
+ #
33
+ # @return [String]
34
+ attr_reader :identification
35
35
 
36
36
  # The monkeypatch to insert the mutation
37
37
  #
38
38
  # @return [String]
39
- def monkeypatch
40
- Unparser.unparse(subject.context.root(node))
41
- end
42
- memoize :monkeypatch
39
+ attr_reader :monkeypatch
43
40
 
44
41
  # Normalized original source
45
42
  #
@@ -77,7 +74,6 @@ module Mutant
77
74
  def sha1
78
75
  Digest::SHA1.hexdigest(subject.identification + CODE_DELIMITER + source)
79
76
  end
80
- memoize :sha1
81
77
 
82
78
  # Evil mutation that should case mutations to fail tests
83
79
  class Evil < self
@@ -233,7 +233,7 @@ module Mutant
233
233
  #
234
234
  # @return [Boolean]
235
235
  def success?
236
- test_result || timeout
236
+ process_abort || test_result || timeout
237
237
  end
238
238
  end
239
239
 
@@ -250,8 +250,9 @@ module Mutant
250
250
  # @praam [Result::CoverageCriteria]
251
251
  def criteria_result(coverage_criteria)
252
252
  CoverageCriteria.new(
253
- test_result: coverage_criteria.test_result && test_result_success?,
254
- timeout: coverage_criteria.timeout && timeout?
253
+ process_abort: coverage_criteria.process_abort && process_abort?,
254
+ test_result: coverage_criteria.test_result && test_result_success?,
255
+ timeout: coverage_criteria.timeout && timeout?
255
256
  )
256
257
  end
257
258
 
@@ -269,6 +270,15 @@ module Mutant
269
270
  !isolation_result.timeout.nil?
270
271
  end
271
272
 
273
+ # Test for unexpected process abort
274
+ #
275
+ # @return [Boolean]
276
+ def process_abort?
277
+ process_status = isolation_result.process_status or return false
278
+
279
+ !timeout? && !process_status.exited?
280
+ end
281
+
272
282
  private
273
283
 
274
284
  # Test if mutation was handled successfully
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.10.7'
5
+ VERSION = '0.10.12'
6
6
  end # Mutant
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mutant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.7
4
+ version: 0.10.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Markus Schirp
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-22 00:00:00.000000000 Z
11
+ date: 2020-12-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: abstract_type
@@ -304,8 +304,10 @@ files:
304
304
  - lib/mutant/bootstrap.rb
305
305
  - lib/mutant/cli.rb
306
306
  - lib/mutant/cli/command.rb
307
+ - lib/mutant/cli/command/environment.rb
308
+ - lib/mutant/cli/command/environment/run.rb
309
+ - lib/mutant/cli/command/environment/show.rb
307
310
  - lib/mutant/cli/command/root.rb
308
- - lib/mutant/cli/command/run.rb
309
311
  - lib/mutant/cli/command/subscription.rb
310
312
  - lib/mutant/config.rb
311
313
  - lib/mutant/context.rb