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 +4 -4
- data/lib/mutant.rb +3 -1
- data/lib/mutant/cli/command.rb +1 -1
- data/lib/mutant/cli/command/{run.rb → environment.rb} +7 -45
- data/lib/mutant/cli/command/environment/run.rb +56 -0
- data/lib/mutant/cli/command/environment/show.rb +29 -0
- data/lib/mutant/cli/command/root.rb +7 -3
- data/lib/mutant/config.rb +9 -7
- data/lib/mutant/integration.rb +9 -0
- data/lib/mutant/license.rb +2 -2
- data/lib/mutant/meta.rb +1 -2
- data/lib/mutant/meta/example.rb +20 -2
- data/lib/mutant/meta/example/dsl.rb +11 -10
- data/lib/mutant/meta/example/verification.rb +2 -2
- data/lib/mutant/mutation.rb +16 -20
- data/lib/mutant/result.rb +13 -3
- data/lib/mutant/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c573735f4e77ea0dd9313412e82bbca47771e8fd379c5d53393e855248b7792
|
4
|
+
data.tar.gz: 7c59c273789c9fb3d85dd858a7ed7d6bec85b8440ad05736f70541325d214947
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff3a349908f6f3f83ace0ce4de9555b981f6a82515954e635a7539b2d8852d9bfa740a2c33afd2b3b49a34e0358920be9d0c22b8c05c2903356d75b9c4a4ccc6
|
7
|
+
data.tar.gz: 2b3cc3f947b50bc407c8c5a3239ad5bbddf2ff2adc773e661f8279d650297accaad05cdcdbce5e64ae04e419dd63740e0de493542f44464bb5d7d7ee60ab37c1
|
data/lib/mutant.rb
CHANGED
@@ -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'
|
data/lib/mutant/cli/command.rb
CHANGED
@@ -3,10 +3,9 @@
|
|
3
3
|
module Mutant
|
4
4
|
module CLI
|
5
5
|
class Command
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
22
|
+
@config = Config::DEFAULT
|
39
23
|
end
|
40
24
|
|
41
|
-
def
|
42
|
-
|
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 =
|
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
|
-
|
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
|
data/lib/mutant/config.rb
CHANGED
@@ -38,11 +38,12 @@ module Mutant
|
|
38
38
|
private_constant(*constants(false))
|
39
39
|
|
40
40
|
class CoverageCriteria
|
41
|
-
include Anima.new(:
|
41
|
+
include Anima.new(:process_abort, :test_result, :timeout)
|
42
42
|
|
43
43
|
DEFAULT = new(
|
44
|
-
|
45
|
-
test_result:
|
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('
|
54
|
-
Transform::Hash::Key.new('test_result',
|
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:
|
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:
|
79
|
+
requires: requires + other.requires,
|
78
80
|
zombie: zombie || other.zombie
|
79
81
|
)
|
80
82
|
end
|
data/lib/mutant/integration.rb
CHANGED
@@ -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,
|
data/lib/mutant/license.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Mutant
|
4
4
|
module License
|
5
5
|
NAME = 'mutant-license'
|
6
|
-
VERSION = '
|
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
|
data/lib/mutant/meta.rb
CHANGED
@@ -17,8 +17,7 @@ module Mutant
|
|
17
17
|
#
|
18
18
|
# @return [undefined]
|
19
19
|
def self.add(*types, &block)
|
20
|
-
|
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'))
|
data/lib/mutant/meta/example.rb
CHANGED
@@ -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
|
-
:
|
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 [
|
12
|
+
# @param [Thread::Backtrace::Location] location
|
13
13
|
# @param [Set<Symbol>] types
|
14
14
|
#
|
15
15
|
# @return [Example]
|
16
|
-
def self.call(
|
17
|
-
instance = new(
|
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(
|
28
|
-
@expected
|
29
|
-
@
|
30
|
-
@lvars
|
31
|
-
@source
|
32
|
-
@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
|
-
|
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.
|
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(
|
97
|
+
Mutation::Evil.new(example, node)
|
98
98
|
end
|
99
99
|
end
|
100
100
|
memoize :missing
|
data/lib/mutant/mutation.rb
CHANGED
@@ -9,37 +9,34 @@ module Mutant
|
|
9
9
|
CODE_DELIMITER = "\0"
|
10
10
|
CODE_RANGE = (0..4).freeze
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
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
|
data/lib/mutant/result.rb
CHANGED
@@ -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
|
-
|
254
|
-
|
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
|
data/lib/mutant/version.rb
CHANGED
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.
|
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
|
+
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
|