mutant 0.10.11 → 0.10.16

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: f812d15b5c09f8681cc6c75eb01bd00fffe412bc53397b9e13c29c7cad69db9a
4
- data.tar.gz: 45c24afcbfbef29a50b90b81946e75391c58ecea7ac32b9545a5e7fdc22fd063
3
+ metadata.gz: 376273ed7dd5673bc6acc7d2393b3b9d05c5779a33b36fffce4a2fd93742e313
4
+ data.tar.gz: 186a12fae490b557d0f48f879017a2faf61b2edfef09cd44d8e11549c34a8526
5
5
  SHA512:
6
- metadata.gz: fcb16a15ec71fe5dc51d07e08b426e8dc68e2f4bad2bd47f95a19b136cd06dbefdc9be44f4a7e0723b8a9450b4b924ab2744f8be05b09fbf1d6e9774431f2e97
7
- data.tar.gz: b9213ce07ce70908f072b23d92bd86c86492ed8f348ae2c7419ed625989a36b7b2d1e110048e14364e044790e7e086894c124f081043d989844de02adcc81684
6
+ metadata.gz: 8f2a51ac2f6e515d9e4b96f3ede03773ae20c9038e9b4f31f639e733598cff0d86ed6d7020b727b167b5e518425ae90b97c1b53a5c3c26abbc6c417bf18eacd0
7
+ data.tar.gz: bd2084f932c8267845e0371ff267d0acd2c8085684fdb7acb718253777adcd1b8098c5288c6978c4542ca9327bba0f62d8206ec90c8baad73be3fad46e471808
data/bin/mutant CHANGED
@@ -28,12 +28,14 @@ status =
28
28
  .call(Kernel),
29
29
  root_require: 'mutant',
30
30
  includes: %w[
31
- mutant
32
- unparser
33
31
  adamantium
34
- equalizer
35
32
  anima
36
33
  concord
34
+ equalizer
35
+ mprelude
36
+ mutant
37
+ unparser
38
+ variable
37
39
  ]
38
40
  )
39
41
 
@@ -226,7 +226,7 @@ module Mutant
226
226
  # Reopen class to initialize constant to avoid dep circle
227
227
  class Config
228
228
  DEFAULT = new(
229
- coverage_criteria: Config::CoverageCriteria::DEFAULT,
229
+ coverage_criteria: Config::CoverageCriteria::EMPTY,
230
230
  expression_parser: Expression::Parser.new([
231
231
  Expression::Method,
232
232
  Expression::Methods,
@@ -245,4 +245,17 @@ module Mutant
245
245
  zombie: false
246
246
  )
247
247
  end # Config
248
+
249
+ # Traverse values against action
250
+ #
251
+ # Specialized to Either. Its *always* traverse.
252
+ def self.traverse(action, values)
253
+ Either::Right.new(
254
+ values.map do |value|
255
+ action.call(value).from_right do |error|
256
+ return Either::Left.new(error)
257
+ end
258
+ end
259
+ )
260
+ end
248
261
  end # Mutant
@@ -75,6 +75,18 @@ module Mutant
75
75
  self.class::SUBCOMMANDS
76
76
  end
77
77
 
78
+ def execute
79
+ action.either(
80
+ method(:fail_message),
81
+ ->(_) { true }
82
+ )
83
+ end
84
+
85
+ def fail_message(message)
86
+ world.stderr.puts(message)
87
+ false
88
+ end
89
+
78
90
  def parser
79
91
  OptionParser.new do |parser|
80
92
  parser.banner = "usage: #{banner}"
@@ -19,7 +19,7 @@ module Mutant
19
19
 
20
20
  def initialize(attributes)
21
21
  super(attributes)
22
- @config = Config.env
22
+ @config = Config::DEFAULT
23
23
  end
24
24
 
25
25
  def bootstrap
@@ -29,27 +29,17 @@ module Mutant
29
29
  end
30
30
 
31
31
  def expand(file_config)
32
- @config = file_config.merge(@config)
32
+ @config = Config.env.merge(file_config).merge(@config).expand_defaults
33
33
  end
34
34
 
35
35
  def parse_remaining_arguments(arguments)
36
- traverse(@config.expression_parser.public_method(:apply), arguments)
36
+ Mutant.traverse(@config.expression_parser.public_method(:apply), arguments)
37
37
  .fmap do |match_expressions|
38
38
  matcher(match_expressions: match_expressions)
39
39
  self
40
40
  end
41
41
  end
42
42
 
43
- def traverse(action, values)
44
- Either::Right.new(
45
- values.map do |value|
46
- action.call(value).from_right do |error|
47
- return Either::Left.new(error)
48
- end
49
- end
50
- )
51
- end
52
-
53
43
  def set(**attributes)
54
44
  @config = @config.with(attributes)
55
45
  end
@@ -25,12 +25,19 @@ module Mutant
25
25
 
26
26
  private
27
27
 
28
- def execute
28
+ def action
29
29
  soft_fail(License.apply(world))
30
30
  .bind { bootstrap }
31
31
  .bind(&Runner.public_method(:apply))
32
- .from_right { |error| world.stderr.puts(error); return false }
33
- .success?
32
+ .bind(&method(:from_result))
33
+ end
34
+
35
+ def from_result(result)
36
+ if result.success?
37
+ Either::Right.new(nil)
38
+ else
39
+ Either::Left.new('Uncovered mutations detected, exiting nonzero!')
40
+ end
34
41
  end
35
42
 
36
43
  def soft_fail(result)
@@ -11,12 +11,9 @@ module Mutant
11
11
 
12
12
  private
13
13
 
14
- def execute
15
- Config.load_config_file(world)
16
- .fmap(&method(:expand))
17
- .bind { Bootstrap.apply(world, @config) }
14
+ def action
15
+ bootstrap
18
16
  .fmap(&method(:report_env))
19
- .right?
20
17
  end
21
18
 
22
19
  def report_env(env)
@@ -40,6 +40,12 @@ module Mutant
40
40
  class CoverageCriteria
41
41
  include Anima.new(:process_abort, :test_result, :timeout)
42
42
 
43
+ EMPTY = new(
44
+ process_abort: nil,
45
+ test_result: nil,
46
+ timeout: nil
47
+ )
48
+
43
49
  DEFAULT = new(
44
50
  process_abort: false,
45
51
  test_result: true,
@@ -61,6 +67,22 @@ module Mutant
61
67
  ->(value) { Either::Right.new(DEFAULT.with(**value)) }
62
68
  ]
63
69
  )
70
+
71
+ def merge(other)
72
+ self.class.new(
73
+ process_abort: overwrite(other, :process_abort),
74
+ test_result: overwrite(other, :test_result),
75
+ timeout: overwrite(other, :timeout)
76
+ )
77
+ end
78
+
79
+ private
80
+
81
+ def overwrite(other, attribute_name)
82
+ other_value = other.public_send(attribute_name)
83
+
84
+ other_value.nil? ? public_send(attribute_name) : other_value
85
+ end
64
86
  end # CoverageCriteria
65
87
 
66
88
  # Merge with other config
@@ -68,18 +90,24 @@ module Mutant
68
90
  # @param [Config] other
69
91
  #
70
92
  # @return [Config]
93
+ #
94
+ # rubocop:disable Metrics/AbcSize
95
+ # rubocop:disable Metrics/MethodLength
71
96
  def merge(other)
72
97
  other.with(
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
98
+ coverage_criteria: coverage_criteria.merge(other.coverage_criteria),
99
+ fail_fast: fail_fast || other.fail_fast,
100
+ includes: includes + other.includes,
101
+ jobs: other.jobs || jobs,
102
+ integration: other.integration || integration,
103
+ mutation_timeout: other.mutation_timeout || mutation_timeout,
104
+ matcher: matcher.merge(other.matcher),
105
+ requires: requires + other.requires,
106
+ zombie: zombie || other.zombie
81
107
  )
82
108
  end
109
+ # rubocop:enable Metrics/AbcSize
110
+ # rubocop:enable Metrics/MethodLength
83
111
 
84
112
  # Load config file
85
113
  #
@@ -100,6 +128,16 @@ module Mutant
100
128
  end
101
129
  end
102
130
 
131
+ # Expand config with defaults
132
+ #
133
+ # @return [Config]
134
+ def expand_defaults
135
+ with(
136
+ coverage_criteria: CoverageCriteria::DEFAULT.merge(coverage_criteria),
137
+ jobs: jobs || 1
138
+ )
139
+ end
140
+
103
141
  def self.load_contents(path)
104
142
  Transform::Named
105
143
  .new(path.to_s, TRANSFORM)
@@ -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,15 +9,7 @@ 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}"
17
- end
18
- memoize :identification
19
-
20
- # Mutation code
12
+ # Mutation identification code
21
13
  #
22
14
  # @return [String]
23
15
  def code
@@ -33,6 +25,14 @@ module Mutant
33
25
  end
34
26
  memoize :source
35
27
 
28
+ # Identification string
29
+ #
30
+ # @return [String]
31
+ def identification
32
+ "#{self.class::SYMBOL}:#{subject.identification}:#{code}"
33
+ end
34
+ memoize :identification
35
+
36
36
  # The monkeypatch to insert the mutation
37
37
  #
38
38
  # @return [String]
@@ -77,7 +77,6 @@ module Mutant
77
77
  def sha1
78
78
  Digest::SHA1.hexdigest(subject.identification + CODE_DELIMITER + source)
79
79
  end
80
- memoize :sha1
81
80
 
82
81
  # Evil mutation that should case mutations to fail tests
83
82
  class Evil < self
@@ -23,39 +23,55 @@ module Mutant
23
23
  # when git command failed
24
24
  def touches?(path, line_range)
25
25
  touched_paths
26
+ .from_right { |message| fail Error, message }
26
27
  .fetch(path) { return false }
27
28
  .touches?(line_range)
28
29
  end
29
30
 
30
31
  private
31
32
 
32
- # rubocop:disable Metrics/MethodLength
33
+ def repository_root
34
+ world
35
+ .capture_stdout(%w[git rev-parse --show-toplevel])
36
+ .fmap(&:chomp)
37
+ .fmap(&world.pathname.public_method(:new))
38
+ end
39
+
33
40
  def touched_paths
34
- pathname = world.pathname
35
- work_dir = pathname.pwd
41
+ repository_root.bind(&method(:diff_index))
42
+ end
43
+ memoize :touched_paths
36
44
 
45
+ def diff_index(root)
37
46
  world
38
47
  .capture_stdout(%W[git diff-index #{to}])
39
- .from_right
40
- .lines
41
- .map do |line|
42
- path = parse_line(work_dir, line)
43
- [path.path, path]
48
+ .fmap(&:lines)
49
+ .bind do |lines|
50
+ Mutant
51
+ .traverse(->(line) { parse_line(root, line) }, lines)
52
+ .fmap do |paths|
53
+ paths.map { |path| [path.path, path] }.to_h
54
+ end
44
55
  end
45
- .to_h
46
56
  end
47
- memoize :touched_paths
48
- # rubocop:enable Metrics/MethodLength
49
-
50
- def parse_line(work_dir, line)
51
- match = FORMAT.match(line) or fail Error, "Invalid git diff-index line: #{line}"
52
57
 
53
- Path.new(
54
- path: work_dir.join(match.captures.first),
55
- to: to,
56
- world: world
57
- )
58
+ # rubocop:disable Metrics/MethodLength
59
+ def parse_line(root, line)
60
+ match = FORMAT.match(line)
61
+
62
+ if match
63
+ Either::Right.new(
64
+ Path.new(
65
+ path: root.join(match.captures.first),
66
+ to: to,
67
+ world: world
68
+ )
69
+ )
70
+ else
71
+ Either::Left.new("Invalid git diff-index line: #{line}")
72
+ end
58
73
  end
74
+ # rubocop:enable Metrics/MethodLength
59
75
 
60
76
  # Path touched by a diff
61
77
  class Path
@@ -276,7 +276,7 @@ module Mutant
276
276
  def process_abort?
277
277
  process_status = isolation_result.process_status or return false
278
278
 
279
- !timeout? && !process_status.exited?
279
+ !timeout? && !process_status.success?
280
280
  end
281
281
 
282
282
  private
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.10.11'
5
+ VERSION = '0.10.16'
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.11
4
+ version: 0.10.16
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-12-02 00:00:00.000000000 Z
11
+ date: 2020-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: abstract_type