mutant 0.10.12 → 0.10.17

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c573735f4e77ea0dd9313412e82bbca47771e8fd379c5d53393e855248b7792
4
- data.tar.gz: 7c59c273789c9fb3d85dd858a7ed7d6bec85b8440ad05736f70541325d214947
3
+ metadata.gz: 977c03a9678ae669eaadb837b2d20402e92344cd1aa1d882c358c72819aea2f8
4
+ data.tar.gz: b27367b4065c83e34423221ba99d868e0a993d35b133eee8a99b18392aaf6a85
5
5
  SHA512:
6
- metadata.gz: ff3a349908f6f3f83ace0ce4de9555b981f6a82515954e635a7539b2d8852d9bfa740a2c33afd2b3b49a34e0358920be9d0c22b8c05c2903356d75b9c4a4ccc6
7
- data.tar.gz: 2b3cc3f947b50bc407c8c5a3239ad5bbddf2ff2adc773e661f8279d650297accaad05cdcdbce5e64ae04e419dd63740e0de493542f44464bb5d7d7ee60ab37c1
6
+ metadata.gz: a238aea95f75d4bbdccf7418824b62c24c82b56c6f6d04a1e2298783a2208135bf91a2b48e224ad628c033d3f0bf893f9dd6498f3b355133c59339bf5a26964c
7
+ data.tar.gz: bf517485919b231bd1994fbedab20741de57029add17a22ab15c2d2058c5c1d8453942583fc0a44d99220b09afe60ce891341d7ac42b4c5921cbdee666558503
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}"
@@ -29,27 +29,17 @@ module Mutant
29
29
  end
30
30
 
31
31
  def expand(file_config)
32
- @config = Config.env.merge(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)
@@ -64,6 +64,7 @@ module Mutant
64
64
  end
65
65
  end # Pipe
66
66
 
67
+ # rubocop:disable Metrics/ClassLength
67
68
  class Parent
68
69
  include(
69
70
  Anima.new(*ATTRIBUTES),
@@ -149,17 +150,26 @@ module Mutant
149
150
 
150
151
  break unless ready
151
152
 
152
- ready.each do |fd|
153
- if fd.eof?
154
- targets.delete(fd)
153
+ ready.each do |target|
154
+ if target.eof?
155
+ targets.delete(target)
155
156
  else
156
- targets.fetch(fd) << fd.read_nonblock(READ_SIZE)
157
+ read_fragment(target, targets.fetch(target))
157
158
  end
158
159
  end
159
160
  end
160
161
  end
161
162
  # rubocop:enable Metrics/MethodLength
162
163
 
164
+ def read_fragment(target, fragments)
165
+ loop do
166
+ result = target.read_nonblock(READ_SIZE, exception: false)
167
+ break unless result.instance_of?(String)
168
+ fragments << result
169
+ break if result.bytesize < READ_SIZE
170
+ end
171
+ end
172
+
163
173
  # rubocop:disable Metrics/MethodLength
164
174
  def terminate_graceful
165
175
  status = nil
@@ -199,6 +209,7 @@ module Mutant
199
209
  @result = defined?(@result) ? @result.add_error(result) : result
200
210
  end
201
211
  end # Parent
212
+ # rubocop:enable Metrics/ClassLength
202
213
 
203
214
  class Child
204
215
  include(
@@ -9,34 +9,37 @@ module Mutant
9
9
  CODE_DELIMITER = "\0"
10
10
  CODE_RANGE = (0..4).freeze
11
11
 
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))
19
- end
20
-
21
12
  # Mutation identification code
22
13
  #
23
14
  # @return [String]
24
- attr_reader :code
15
+ def code
16
+ sha1[CODE_RANGE]
17
+ end
18
+ memoize :code
25
19
 
26
20
  # Normalized mutation source
27
21
  #
28
22
  # @return [String]
29
- attr_reader :source
23
+ def source
24
+ Unparser.unparse(node)
25
+ end
26
+ memoize :source
30
27
 
31
28
  # Identification string
32
29
  #
33
30
  # @return [String]
34
- attr_reader :identification
31
+ def identification
32
+ "#{self.class::SYMBOL}:#{subject.identification}:#{code}"
33
+ end
34
+ memoize :identification
35
35
 
36
36
  # The monkeypatch to insert the mutation
37
37
  #
38
38
  # @return [String]
39
- attr_reader :monkeypatch
39
+ def monkeypatch
40
+ Unparser.unparse(subject.context.root(node))
41
+ end
42
+ memoize :monkeypatch
40
43
 
41
44
  # Normalized original source
42
45
  #
@@ -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.12'
5
+ VERSION = '0.10.17'
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.12
4
+ version: 0.10.17
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-03 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