mutant 0.10.21 → 0.10.26

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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +32 -13
  3. data/lib/mutant/ast/find_metaclass_containing.rb +1 -1
  4. data/lib/mutant/ast/regexp.rb +54 -0
  5. data/lib/mutant/ast/regexp/transformer.rb +150 -0
  6. data/lib/mutant/ast/regexp/transformer/direct.rb +121 -0
  7. data/lib/mutant/ast/regexp/transformer/named_group.rb +50 -0
  8. data/lib/mutant/ast/regexp/transformer/options_group.rb +68 -0
  9. data/lib/mutant/ast/regexp/transformer/quantifier.rb +92 -0
  10. data/lib/mutant/ast/regexp/transformer/recursive.rb +56 -0
  11. data/lib/mutant/ast/regexp/transformer/root.rb +28 -0
  12. data/lib/mutant/ast/regexp/transformer/text.rb +58 -0
  13. data/lib/mutant/ast/types.rb +115 -2
  14. data/lib/mutant/bootstrap.rb +1 -1
  15. data/lib/mutant/cli/command.rb +4 -0
  16. data/lib/mutant/cli/command/environment.rb +9 -3
  17. data/lib/mutant/cli/command/environment/subject.rb +0 -4
  18. data/lib/mutant/cli/command/environment/test.rb +36 -0
  19. data/lib/mutant/cli/command/root.rb +1 -1
  20. data/lib/mutant/config.rb +9 -55
  21. data/lib/mutant/config/coverage_criteria.rb +61 -0
  22. data/lib/mutant/context.rb +1 -1
  23. data/lib/mutant/env.rb +2 -2
  24. data/lib/mutant/expression.rb +0 -12
  25. data/lib/mutant/expression/method.rb +4 -4
  26. data/lib/mutant/expression/methods.rb +5 -4
  27. data/lib/mutant/expression/namespace.rb +1 -1
  28. data/lib/mutant/integration.rb +8 -2
  29. data/lib/mutant/isolation/fork.rb +4 -11
  30. data/lib/mutant/loader.rb +1 -1
  31. data/lib/mutant/matcher.rb +3 -3
  32. data/lib/mutant/matcher/config.rb +30 -8
  33. data/lib/mutant/matcher/method.rb +10 -9
  34. data/lib/mutant/matcher/method/instance.rb +6 -2
  35. data/lib/mutant/matcher/method/metaclass.rb +1 -1
  36. data/lib/mutant/matcher/methods.rb +2 -4
  37. data/lib/mutant/meta/example.rb +1 -1
  38. data/lib/mutant/meta/example/dsl.rb +6 -1
  39. data/lib/mutant/meta/example/verification.rb +1 -1
  40. data/lib/mutant/mutation.rb +1 -1
  41. data/lib/mutant/mutator.rb +8 -1
  42. data/lib/mutant/mutator/node/argument.rb +2 -2
  43. data/lib/mutant/mutator/node/block.rb +5 -1
  44. data/lib/mutant/mutator/node/dynamic_literal.rb +1 -1
  45. data/lib/mutant/mutator/node/kwargs.rb +44 -0
  46. data/lib/mutant/mutator/node/literal/regex.rb +12 -0
  47. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +3 -3
  48. data/lib/mutant/mutator/node/regexp.rb +20 -0
  49. data/lib/mutant/mutator/node/regexp/alternation_meta.rb +20 -0
  50. data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +20 -0
  51. data/lib/mutant/mutator/node/regexp/capture_group.rb +25 -0
  52. data/lib/mutant/mutator/node/regexp/character_type.rb +31 -0
  53. data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +20 -0
  54. data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +20 -0
  55. data/lib/mutant/mutator/node/regexp/named_group.rb +39 -0
  56. data/lib/mutant/mutator/node/regexp/zero_or_more.rb +34 -0
  57. data/lib/mutant/mutator/node/regopt.rb +1 -1
  58. data/lib/mutant/mutator/node/sclass.rb +1 -1
  59. data/lib/mutant/mutator/node/send.rb +55 -6
  60. data/lib/mutant/parallel.rb +2 -2
  61. data/lib/mutant/parallel/driver.rb +1 -1
  62. data/lib/mutant/parallel/worker.rb +1 -1
  63. data/lib/mutant/parser.rb +1 -1
  64. data/lib/mutant/pipe.rb +1 -1
  65. data/lib/mutant/procto.rb +23 -0
  66. data/lib/mutant/reporter/cli/printer.rb +10 -4
  67. data/lib/mutant/reporter/cli/printer/env.rb +3 -3
  68. data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -2
  69. data/lib/mutant/selector.rb +1 -1
  70. data/lib/mutant/subject.rb +2 -4
  71. data/lib/mutant/subject/method/instance.rb +6 -45
  72. data/lib/mutant/timer.rb +2 -2
  73. data/lib/mutant/transform.rb +25 -0
  74. data/lib/mutant/variable.rb +322 -0
  75. data/lib/mutant/version.rb +1 -1
  76. data/lib/mutant/world.rb +2 -3
  77. metadata +39 -151
  78. data/lib/mutant/warnings.rb +0 -106
@@ -8,7 +8,7 @@ module Mutant
8
8
  #
9
9
  # env = config interpreted against the world
10
10
  module Bootstrap
11
- include Adamantium::Flat, Anima.new(:config, :parser, :world)
11
+ include Adamantium, Anima.new(:config, :parser, :world)
12
12
 
13
13
  SEMANTICS_MESSAGE_FORMAT =
14
14
  "%<message>s. Fix your lib to follow normal ruby semantics!\n" \
@@ -204,6 +204,10 @@ module Mutant
204
204
  def with_help(message)
205
205
  "#{full_name}: #{message}\n\n#{parser}"
206
206
  end
207
+
208
+ def print(message)
209
+ world.stdout.puts(message)
210
+ end
207
211
  end # Command
208
212
  # rubocop:enable Metrics/ClassLength
209
213
  end # CLI
@@ -29,13 +29,19 @@ module Mutant
29
29
  end
30
30
 
31
31
  def expand(file_config)
32
+ if @config.matcher.subjects.any?
33
+ file_config = file_config.with(
34
+ matcher: file_config.matcher.with(subjects: [])
35
+ )
36
+ end
37
+
32
38
  @config = Config.env.merge(file_config).merge(@config).expand_defaults
33
39
  end
34
40
 
35
41
  def parse_remaining_arguments(arguments)
36
42
  Mutant.traverse(@config.expression_parser, arguments)
37
- .fmap do |match_expressions|
38
- matcher(match_expressions: match_expressions)
43
+ .fmap do |expressions|
44
+ matcher(subjects: expressions)
39
45
  self
40
46
  end
41
47
  end
@@ -82,7 +88,7 @@ module Mutant
82
88
  parser.separator('Matcher:')
83
89
 
84
90
  parser.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
85
- add_matcher(:ignore_expressions, @config.expression_parser.call(pattern).from_right)
91
+ add_matcher(:ignore, @config.expression_parser.call(pattern).from_right)
86
92
  end
87
93
  parser.on('--start-subject EXPRESSION', 'Start mutation testing at a specific subject') do |pattern|
88
94
  add_matcher(:start_expressions, @config.expression_parser.call(pattern).from_right)
@@ -25,10 +25,6 @@ module Mutant
25
25
  print(subject.expression.syntax)
26
26
  end
27
27
  end
28
-
29
- def print(message)
30
- world.stdout.puts(message)
31
- end
32
28
  end
33
29
 
34
30
  SUBCOMMANDS = [List].freeze
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module CLI
5
+ class Command
6
+ class Environment
7
+ class Test < self
8
+ NAME = 'test'
9
+ SHORT_DESCRIPTION = 'test subcommands'
10
+
11
+ class List < self
12
+ NAME = 'list'
13
+ SHORT_DESCRIPTION = 'List tests detected in the environment'
14
+ SUBCOMMANDS = EMPTY_ARRAY
15
+
16
+ private
17
+
18
+ def action
19
+ bootstrap.fmap(&method(:list_tests))
20
+ end
21
+
22
+ def list_tests(env)
23
+ tests = env.integration.all_tests
24
+ print('Tests in environment: %d' % tests.length)
25
+ tests.each do |test|
26
+ print(test.identification)
27
+ end
28
+ end
29
+ end
30
+
31
+ SUBCOMMANDS = [List].freeze
32
+ end # Test
33
+ end # Environment
34
+ end # Command
35
+ end # CLI
36
+ end # Mutant
@@ -4,7 +4,7 @@ module Mutant
4
4
  module CLI
5
5
  class Command
6
6
  class Environment < self
7
- SUBCOMMANDS = [Environment::Subject, Environment::Show].freeze
7
+ SUBCOMMANDS = [Environment::Subject, Environment::Show, Environment::Test].freeze
8
8
  end # Environment
9
9
 
10
10
  class Root < self
@@ -6,7 +6,7 @@ module Mutant
6
6
  # Does not reference any "external" volatile state. The configuration applied
7
7
  # to current environment is being represented by the Mutant::Env object.
8
8
  class Config
9
- include Adamantium::Flat, Anima.new(
9
+ include Adamantium, Anima.new(
10
10
  :coverage_criteria,
11
11
  :expression_parser,
12
12
  :fail_fast,
@@ -37,54 +37,6 @@ module Mutant
37
37
 
38
38
  private_constant(*constants(false))
39
39
 
40
- class CoverageCriteria
41
- include Anima.new(:process_abort, :test_result, :timeout)
42
-
43
- EMPTY = new(
44
- process_abort: nil,
45
- test_result: nil,
46
- timeout: nil
47
- )
48
-
49
- DEFAULT = new(
50
- process_abort: false,
51
- test_result: true,
52
- timeout: false
53
- )
54
-
55
- TRANSFORM =
56
- Transform::Sequence.new(
57
- [
58
- Transform::Hash.new(
59
- optional: [
60
- Transform::Hash::Key.new('process_abort', Transform::BOOLEAN),
61
- Transform::Hash::Key.new('test_result', Transform::BOOLEAN),
62
- Transform::Hash::Key.new('timeout', Transform::BOOLEAN)
63
- ],
64
- required: []
65
- ),
66
- Transform::Hash::Symbolize.new,
67
- ->(value) { Either::Right.new(DEFAULT.with(**value)) }
68
- ]
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
86
- end # CoverageCriteria
87
-
88
40
  # Merge with other config
89
41
  #
90
42
  # @param [Config] other
@@ -116,13 +68,14 @@ module Mutant
116
68
  #
117
69
  # @return [Either<String,Config>]
118
70
  def self.load_config_file(world)
119
- config = DEFAULT
120
- files = CANDIDATES.map(&world.pathname.public_method(:new)).select(&:readable?)
71
+ files = CANDIDATES
72
+ .map(&world.pathname.public_method(:new))
73
+ .select(&:readable?)
121
74
 
122
75
  if files.one?
123
- load_contents(files.first).fmap(&config.public_method(:with))
76
+ load_contents(files.first).fmap(&DEFAULT.public_method(:with))
124
77
  elsif files.empty?
125
- Either::Right.new(config)
78
+ Either::Right.new(DEFAULT)
126
79
  else
127
80
  Either::Left.new(MORE_THAN_ONE_CONFIG_FILE % files.join(', '))
128
81
  end
@@ -159,13 +112,14 @@ module Mutant
159
112
  Transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
160
113
  Transform::Hash.new(
161
114
  optional: [
162
- Transform::Hash::Key.new('coverage_criteria', CoverageCriteria::TRANSFORM),
115
+ Transform::Hash::Key.new('coverage_criteria', ->(value) { CoverageCriteria::TRANSFORM.call(value) }),
163
116
  Transform::Hash::Key.new('fail_fast', Transform::BOOLEAN),
164
117
  Transform::Hash::Key.new('includes', Transform::STRING_ARRAY),
165
118
  Transform::Hash::Key.new('integration', Transform::STRING),
166
119
  Transform::Hash::Key.new('jobs', Transform::INTEGER),
167
120
  Transform::Hash::Key.new('mutation_timeout', Transform::FLOAT),
168
- Transform::Hash::Key.new('requires', Transform::STRING_ARRAY)
121
+ Transform::Hash::Key.new('requires', Transform::STRING_ARRAY),
122
+ Transform::Hash::Key.new('matcher', Matcher::Config::LOADER)
169
123
  ],
170
124
  required: []
171
125
  ),
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Config
5
+ # Configuration of coverge conditions
6
+ class CoverageCriteria
7
+ include Anima.new(:process_abort, :test_result, :timeout)
8
+
9
+ EMPTY = new(
10
+ process_abort: nil,
11
+ test_result: nil,
12
+ timeout: nil
13
+ )
14
+
15
+ DEFAULT = new(
16
+ process_abort: false,
17
+ test_result: true,
18
+ timeout: false
19
+ )
20
+
21
+ TRANSFORM =
22
+ Transform::Sequence.new(
23
+ [
24
+ Transform::Hash.new(
25
+ optional: [
26
+ Transform::Hash::Key.new('process_abort', Transform::BOOLEAN),
27
+ Transform::Hash::Key.new('test_result', Transform::BOOLEAN),
28
+ Transform::Hash::Key.new('timeout', Transform::BOOLEAN)
29
+ ],
30
+ required: []
31
+ ),
32
+ Transform::Hash::Symbolize.new,
33
+ ->(value) { Either::Right.new(DEFAULT.with(**value)) }
34
+ ]
35
+ )
36
+
37
+ # Merge coverage criteria with other instance
38
+ #
39
+ # Values from the other instance have precedence.
40
+ #
41
+ # @param [CoverageCriteria] other
42
+ #
43
+ # @return [CoverageCriteria]
44
+ def merge(other)
45
+ self.class.new(
46
+ process_abort: overwrite(other, :process_abort),
47
+ test_result: overwrite(other, :test_result),
48
+ timeout: overwrite(other, :timeout)
49
+ )
50
+ end
51
+
52
+ private
53
+
54
+ def overwrite(other, attribute_name)
55
+ other_value = other.public_send(attribute_name)
56
+
57
+ other_value.nil? ? public_send(attribute_name) : other_value
58
+ end
59
+ end # CoverageCriteria
60
+ end # Config
61
+ end # Mutant
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  # An abstract context where mutations can be applied to.
5
5
  class Context
6
- include Adamantium::Flat, Concord::Public.new(:scope, :source_path)
6
+ include Adamantium, Concord::Public.new(:scope, :source_path)
7
7
  extend AST::Sexp
8
8
 
9
9
  NAMESPACE_DELIMITER = '::'
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  # Abstract base class for mutant environments
5
5
  class Env
6
- include Adamantium::Flat, Anima.new(
6
+ include Adamantium, Anima.new(
7
7
  :config,
8
8
  :integration,
9
9
  :matchable_scopes,
@@ -31,7 +31,7 @@ module Mutant
31
31
  config: config,
32
32
  integration: Integration::Null.new(
33
33
  expression_parser: config.expression_parser,
34
- timer: world.timer
34
+ world: world
35
35
  ),
36
36
  matchable_scopes: EMPTY_ARRAY,
37
37
  mutations: EMPTY_ARRAY,
@@ -4,8 +4,6 @@ module Mutant
4
4
 
5
5
  # Abstract base class for match expression
6
6
  class Expression
7
- include AbstractType
8
-
9
7
  fragment = /[A-Za-z][A-Za-z\d_]*/.freeze
10
8
  SCOPE_NAME_PATTERN = /(?<scope_name>#{fragment}(?:#{SCOPE_OPERATOR}#{fragment})*)/.freeze
11
9
  SCOPE_SYMBOL_PATTERN = '(?<scope_symbol>[.#])'
@@ -16,16 +14,6 @@ module Mutant
16
14
  super.freeze
17
15
  end
18
16
 
19
- # Syntax of expression
20
- #
21
- # @return [Matcher]
22
- abstract_method :matcher
23
-
24
- # Syntax of expression
25
- #
26
- # @return [String]
27
- abstract_method :syntax
28
-
29
17
  # Match length with other expression
30
18
  #
31
19
  # @param [Expression] other
@@ -15,10 +15,10 @@ module Mutant
15
15
 
16
16
  private(*anima.attribute_names)
17
17
 
18
- MATCHERS = IceNine.deep_freeze(
19
- '.' => [Matcher::Methods::Singleton, Matcher::Methods::Metaclass],
20
- '#' => [Matcher::Methods::Instance]
21
- )
18
+ MATCHERS = {
19
+ '.' => [Matcher::Methods::Singleton, Matcher::Methods::Metaclass].freeze,
20
+ '#' => [Matcher::Methods::Instance].freeze
21
+ }.freeze
22
22
 
23
23
  METHOD_NAME_PATTERN = /(?<method_name>.+)/.freeze
24
24
 
@@ -12,10 +12,11 @@ module Mutant
12
12
 
13
13
  private(*anima.attribute_names)
14
14
 
15
- MATCHERS = IceNine.deep_freeze(
16
- '.' => [Matcher::Methods::Singleton, Matcher::Methods::Metaclass],
17
- '#' => [Matcher::Methods::Instance]
18
- )
15
+ MATCHERS = {
16
+ '.' => [Matcher::Methods::Singleton, Matcher::Methods::Metaclass].freeze,
17
+ '#' => [Matcher::Methods::Instance].freeze
18
+ }.freeze
19
+
19
20
  private_constant(*constants(false))
20
21
 
21
22
  REGEXP = /\A#{SCOPE_NAME_PATTERN}#{SCOPE_SYMBOL_PATTERN}\z/.freeze
@@ -17,7 +17,7 @@ module Mutant
17
17
  def initialize(*)
18
18
  super
19
19
 
20
- @syntax = "#{scope_name}*"
20
+ @syntax = "#{scope_name}*".freeze # rubocop:disable Style/RedundantFreeze
21
21
 
22
22
  @recursion_pattern = Regexp.union(
23
23
  /\A#{scope_name}\z/,
@@ -4,7 +4,7 @@ module Mutant
4
4
 
5
5
  # Abstract base class mutant test framework integrations
6
6
  class Integration
7
- include AbstractType, Adamantium::Flat, Anima.new(:expression_parser, :timer)
7
+ include AbstractType, Adamantium, Anima.new(:expression_parser, :world)
8
8
 
9
9
  LOAD_MESSAGE = <<~'MESSAGE'
10
10
  Unable to load integration mutant-%<integration_name>s:
@@ -39,7 +39,7 @@ module Mutant
39
39
  attempt_require(env).bind { attempt_const_get(env) }.fmap do |klass|
40
40
  klass.new(
41
41
  expression_parser: env.config.expression_parser,
42
- timer: env.world.timer
42
+ world: env.world
43
43
  ).setup
44
44
  end
45
45
  end
@@ -92,5 +92,11 @@ module Mutant
92
92
  #
93
93
  # @return [Enumerable<Test>]
94
94
  abstract_method :all_tests
95
+
96
+ private
97
+
98
+ def timer
99
+ world.timer
100
+ end
95
101
  end # Integration
96
102
  end # Mutant
@@ -28,7 +28,7 @@ module Mutant
28
28
  # * Child process freezing after closing the pipes needs to be
29
29
  # detected by parent process.
30
30
  class Fork < self
31
- include(Adamantium::Flat, Concord.new(:world))
31
+ include(Adamantium, Concord.new(:world))
32
32
 
33
33
  READ_SIZE = 4096
34
34
 
@@ -36,7 +36,7 @@ module Mutant
36
36
 
37
37
  # Pipe abstraction
38
38
  class Pipe
39
- include Adamantium::Flat, Anima.new(:reader, :writer)
39
+ include Adamantium, Anima.new(:reader, :writer)
40
40
 
41
41
  # Run block with pipe in binmode
42
42
  #
@@ -66,10 +66,7 @@ module Mutant
66
66
 
67
67
  # rubocop:disable Metrics/ClassLength
68
68
  class Parent
69
- include(
70
- Anima.new(*ATTRIBUTES),
71
- Procto.call
72
- )
69
+ include(Anima.new(*ATTRIBUTES), Procto)
73
70
 
74
71
  # Prevent mutation from `process.fork` to `fork` to call Kernel#fork
75
72
  undef_method :fork
@@ -212,11 +209,7 @@ module Mutant
212
209
  # rubocop:enable Metrics/ClassLength
213
210
 
214
211
  class Child
215
- include(
216
- Adamantium::Flat,
217
- Anima.new(*ATTRIBUTES),
218
- Procto.call
219
- )
212
+ include(Adamantium, Anima.new(*ATTRIBUTES), Procto)
220
213
 
221
214
  # Handle child process
222
215
  #
@@ -18,7 +18,7 @@ module Mutant
18
18
 
19
19
  # Vale returned on MRI detecting void value expressions
20
20
  class VoidValue < self
21
- end # voidValue
21
+ end # VoidValue
22
22
  end # Result
23
23
 
24
24
  # Call loader