mutant 0.10.11 → 0.10.16

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: 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