mutant 0.5.24 → 0.5.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +8 -0
  3. data/config/flay.yml +1 -1
  4. data/config/flog.yml +1 -1
  5. data/config/reek.yml +15 -13
  6. data/lib/mutant.rb +28 -12
  7. data/lib/mutant/ast/meta.rb +0 -10
  8. data/lib/mutant/ast/named_children.rb +1 -0
  9. data/lib/mutant/ast/types.rb +5 -5
  10. data/lib/mutant/cli.rb +84 -64
  11. data/lib/mutant/config.rb +7 -39
  12. data/lib/mutant/delegator.rb +2 -0
  13. data/lib/mutant/env.rb +119 -16
  14. data/lib/mutant/expression.rb +8 -2
  15. data/lib/mutant/expression/method.rb +6 -16
  16. data/lib/mutant/expression/methods.rb +5 -5
  17. data/lib/mutant/expression/namespace.rb +7 -7
  18. data/lib/mutant/integration.rb +0 -10
  19. data/lib/mutant/isolation.rb +41 -15
  20. data/lib/mutant/matcher/chain.rb +1 -17
  21. data/lib/mutant/matcher/compiler.rb +108 -0
  22. data/lib/mutant/matcher/config.rb +28 -0
  23. data/lib/mutant/matcher/method.rb +1 -1
  24. data/lib/mutant/matcher/namespace.rb +5 -52
  25. data/lib/mutant/matcher/null.rb +1 -1
  26. data/lib/mutant/matcher/scope.rb +1 -1
  27. data/lib/mutant/mutation.rb +29 -13
  28. data/lib/mutant/mutator/node.rb +2 -12
  29. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +1 -1
  30. data/lib/mutant/reporter/cli.rb +0 -2
  31. data/lib/mutant/reporter/cli/printer.rb +14 -0
  32. data/lib/mutant/reporter/cli/progress.rb +1 -3
  33. data/lib/mutant/reporter/cli/progress/config.rb +5 -9
  34. data/lib/mutant/reporter/cli/progress/env.rb +30 -0
  35. data/lib/mutant/reporter/cli/progress/noop.rb +4 -1
  36. data/lib/mutant/reporter/cli/progress/result.rb +12 -0
  37. data/lib/mutant/reporter/cli/progress/result/mutation.rb +45 -0
  38. data/lib/mutant/reporter/cli/progress/result/subject.rb +54 -0
  39. data/lib/mutant/reporter/cli/progress/subject.rb +7 -90
  40. data/lib/mutant/reporter/cli/registry.rb +2 -0
  41. data/lib/mutant/reporter/cli/report/env.rb +92 -0
  42. data/lib/mutant/reporter/cli/report/mutation.rb +58 -77
  43. data/lib/mutant/reporter/cli/report/subject.rb +4 -3
  44. data/lib/mutant/reporter/cli/report/test.rb +28 -0
  45. data/lib/mutant/reporter/null.rb +1 -1
  46. data/lib/mutant/reporter/trace.rb +16 -3
  47. data/lib/mutant/result.rb +302 -0
  48. data/lib/mutant/runner.rb +77 -123
  49. data/lib/mutant/subject.rb +32 -16
  50. data/lib/mutant/subject/method.rb +0 -15
  51. data/lib/mutant/subject/method/instance.rb +3 -3
  52. data/lib/mutant/version.rb +1 -1
  53. data/lib/mutant/warning_expectation.rb +12 -5
  54. data/spec/integration/mutant/corpus_spec.rb +1 -1
  55. data/spec/spec_helper.rb +5 -1
  56. data/spec/unit/mutant/cli_spec.rb +248 -0
  57. data/spec/unit/mutant/expression/namespace/flat_spec.rb +1 -1
  58. data/spec/unit/mutant/expression_spec.rb +55 -0
  59. data/spec/unit/mutant/integration_spec.rb +0 -5
  60. data/spec/unit/mutant/isolation_spec.rb +36 -5
  61. data/spec/unit/mutant/matcher/chain_spec.rb +1 -13
  62. data/spec/unit/mutant/matcher/compiler_spec.rb +95 -0
  63. data/spec/unit/mutant/matcher/filter_spec.rb +31 -0
  64. data/spec/unit/mutant/matcher/method/instance_spec.rb +33 -2
  65. data/spec/unit/mutant/matcher/method/singleton_spec.rb +1 -1
  66. data/spec/unit/mutant/matcher/methods/instance_spec.rb +1 -1
  67. data/spec/unit/mutant/matcher/methods/singleton_spec.rb +1 -1
  68. data/spec/unit/mutant/matcher/namespace_spec.rb +10 -6
  69. data/spec/unit/mutant/matcher/null_spec.rb +26 -0
  70. data/spec/unit/mutant/reporter/cli_spec.rb +337 -0
  71. data/spec/unit/mutant/reporter/null_spec.rb +12 -0
  72. data/spec/unit/mutant/runner_spec.rb +130 -0
  73. data/spec/unit/mutant/subject/context_spec.rb +4 -3
  74. data/spec/unit/mutant/subject/method/instance_spec.rb +5 -3
  75. data/spec/unit/mutant/subject/method/singleton_spec.rb +3 -2
  76. data/spec/unit/mutant/subject_spec.rb +36 -1
  77. data/spec/unit/mutant/test_spec.rb +25 -0
  78. data/spec/unit/mutant/warning_expectation.rb +11 -8
  79. data/spec/unit/mutant_spec.rb +11 -2
  80. metadata +27 -28
  81. data/lib/mutant/killer.rb +0 -44
  82. data/lib/mutant/matcher/builder.rb +0 -142
  83. data/lib/mutant/mutation/evil.rb +0 -23
  84. data/lib/mutant/mutation/neutral.rb +0 -18
  85. data/lib/mutant/reporter/cli/progress/mutation.rb +0 -46
  86. data/lib/mutant/reporter/cli/report/config.rb +0 -116
  87. data/lib/mutant/rspec.rb +0 -0
  88. data/lib/mutant/runner/config.rb +0 -138
  89. data/lib/mutant/runner/killer.rb +0 -75
  90. data/lib/mutant/runner/mutation.rb +0 -78
  91. data/lib/mutant/runner/subject.rb +0 -85
  92. data/lib/mutant/test/report.rb +0 -59
  93. data/spec/unit/mutant/cli_new_spec.rb +0 -147
  94. data/spec/unit/mutant/cli_run_spec.rb +0 -46
  95. data/spec/unit/mutant/runner/config_spec.rb +0 -157
  96. data/spec/unit/mutant/runner/mutation_spec.rb +0 -101
  97. data/spec/unit/mutant/runner/subject_spec.rb +0 -59
  98. data/spec/unit/mutant/subject/mutations_spec.rb +0 -23
  99. data/spec/unit/mutant/subject/node_spec.rb +0 -17
@@ -1,53 +1,21 @@
1
1
  module Mutant
2
2
  # The configuration of a mutator run
3
3
  class Config
4
- include Adamantium::Flat, Anima.new(
4
+ include Adamantium::Flat, Anima::Update, Anima.new(
5
5
  :debug,
6
6
  :integration,
7
- :matcher,
7
+ :matcher_config,
8
+ :includes,
9
+ :requires,
8
10
  :reporter,
11
+ :isolation,
9
12
  :fail_fast,
10
13
  :zombie,
11
14
  :expected_coverage
12
15
  )
13
16
 
14
- # Enumerate subjects
15
- #
16
- # @api private
17
- #
18
- # @return [self]
19
- # if block given
20
- #
21
- # @return [Enumerator<Subject>]
22
- # otherwise
23
- #
24
- # @api private
25
- #
26
- def subjects(&block)
27
- return to_enum(__method__) unless block_given?
28
- matcher.each(&block)
29
- self
30
- end
31
-
32
- # Return tests for mutation
33
- #
34
- # TODO: This logic is now centralized but still fucked.
35
- #
36
- # @param [Mutation] mutation
37
- #
38
- # @return [Enumerable<Test>]
39
- #
40
- # @api private
41
- #
42
- def tests(subject)
43
- subject.match_expressions.each do |match_expression|
44
- tests = integration.all_tests.select do |test|
45
- match_expression.prefix?(test.expression)
46
- end
47
- return tests if tests.any?
48
- end
49
-
50
- EMPTY_ARRAY
17
+ [:fail_fast, :zombie, :debug].each do |name|
18
+ define_method(:"#{name}?") { public_send(name) }
51
19
  end
52
20
 
53
21
  end # Config
@@ -25,6 +25,7 @@ module Mutant
25
25
  # @api private
26
26
  #
27
27
  def define_delegator(name)
28
+ fail "method #{name} already defined" if instance_methods.include?(name)
28
29
  define_method(name) do
29
30
  object.public_send(name)
30
31
  end
@@ -43,6 +44,7 @@ module Mutant
43
44
  #
44
45
  def self.included(host)
45
46
  super
47
+
46
48
  host.extend(ClassMethods)
47
49
  end
48
50
 
@@ -1,31 +1,43 @@
1
1
  module Mutant
2
2
  # Abstract base class for mutant environments
3
3
  class Env
4
- include AbstractType, Adamantium
4
+ include Adamantium, Concord::Public.new(:config, :cache), Procto.call(:run)
5
5
 
6
- # Return config
6
+ # Return new env
7
7
  #
8
- # @return [Config]
8
+ # @param [Config] config
9
+ #
10
+ # @return [Env]
9
11
  #
10
12
  # @api private
11
13
  #
12
- abstract_method :config
14
+ def self.new(config, cache = Cache.new)
15
+ super(config, cache)
16
+ end
13
17
 
14
- # Return cache
18
+ # Initialize env
15
19
  #
16
- # @return [Cache]
20
+ # @return [undefined]
17
21
  #
18
22
  # @api private
19
23
  #
20
- abstract_method :cache
24
+ def initialize(*)
25
+ super
26
+
27
+ infect
28
+ initialize_matchable_scopes
29
+ initialize_subjects
30
+ end
21
31
 
22
- # Return reporter
32
+ # Run mutant producing a report on configured env
23
33
  #
24
- # @return [Reporter]
34
+ # @return [Report]
25
35
  #
26
36
  # @api private
27
37
  #
28
- abstract_method :reporter
38
+ def run
39
+ Runner.call(self)
40
+ end
29
41
 
30
42
  # Print warning message
31
43
  #
@@ -36,14 +48,105 @@ module Mutant
36
48
  # @api private
37
49
  #
38
50
  def warn(message)
39
- reporter.warn(message)
51
+ config.reporter.warn(message)
40
52
  self
41
53
  end
42
54
 
43
- # Boot environment used for matching
44
- class Boot < self
45
- include Concord::Public.new(:reporter, :cache)
46
- end # Boot
55
+ # Return subjects
56
+ #
57
+ # @return [Array<Subject>]
58
+ #
59
+ # @api private
60
+ #
61
+ attr_reader :subjects
62
+
63
+ # Return all usable match scopes
64
+ #
65
+ # @return [Array<Matcher::Scope>]
66
+ #
67
+ # @api private
68
+ #
69
+ attr_reader :matchable_scopes
70
+
71
+ private
72
+
73
+ # Return scope name
74
+ #
75
+ # @param [Class, Module] scope
76
+ #
77
+ # @return [String]
78
+ # if scope has a name and does not raise exceptions optaining it
79
+ #
80
+ # @return [nil]
81
+ # otherwise
82
+ #
83
+ # @api private
84
+ #
85
+ # rubocop:disable LineLength
86
+ #
87
+ def scope_name(scope)
88
+ scope.name
89
+ rescue => exception
90
+ warn("While optaining #{scope.class}#name from: #{scope.inspect} It raised an error: #{exception.inspect} fix your lib!")
91
+ nil
92
+ end
93
+
94
+ # Try to turn scope into expression
95
+ #
96
+ # @param [Class, Module] scope
97
+ #
98
+ # @return [Expression]
99
+ # if scope can be represented in an expression
100
+ #
101
+ # @return [nil]
102
+ # otherwise
103
+ #
104
+ # @api private
105
+ #
106
+ def expression(scope)
107
+ name = scope_name(scope) or return
108
+
109
+ unless name.kind_of?(String)
110
+ warn("#{scope.class}#name from: #{scope.inspect} did not return a String or nil. Fix your lib to support normal ruby semantics!")
111
+ return
112
+ end
113
+
114
+ Expression.try_parse(name)
115
+ end
116
+
117
+ # Initialize subjects
118
+ #
119
+ # @return [undefined]
120
+ #
121
+ # @api private
122
+ #
123
+ def initialize_subjects
124
+ @subjects = Matcher::Compiler.call(self, config.matcher_config).to_a
125
+ end
126
+
127
+ # Infect environment
128
+ #
129
+ # @return [undefined]
130
+ #
131
+ # @api private
132
+ #
133
+ def infect
134
+ config.includes.each(&$LOAD_PATH.method(:<<))
135
+ config.requires.each(&method(:require))
136
+ end
137
+
138
+ # Initialize matchable scopes
139
+ #
140
+ # @return [undefined]
141
+ #
142
+ # @api private
143
+ #
144
+ def initialize_matchable_scopes
145
+ @matchable_scopes = ObjectSpace.each_object(Module).each_with_object([]) do |scope, aggregate|
146
+ expression = expression(scope)
147
+ aggregate << Matcher::Scope.new(self, scope, expression) if expression
148
+ end.sort_by(&:identification)
149
+ end
47
150
 
48
- end # ENV
151
+ end # Env
49
152
  end # Mutant
@@ -17,6 +17,12 @@ module Mutant
17
17
 
18
18
  REGISTRY = {}
19
19
 
20
+ # Error raised on invalid expressions
21
+ class InvalidExpressionError < RuntimeError; end
22
+
23
+ # Error raised on ambigous expressions
24
+ class AmbigousExpressionError < RuntimeError; end
25
+
20
26
  # Initialize expression
21
27
  #
22
28
  # @param [MatchData] match
@@ -88,7 +94,7 @@ module Mutant
88
94
  # @api private
89
95
  #
90
96
  def self.parse(input)
91
- try_parse(input) or raise "Expression: #{input.inspect} is not valid"
97
+ try_parse(input) or fail InvalidExpressionError, "Expression: #{input.inspect} is not valid"
92
98
  end
93
99
 
94
100
  # Parse input into expression
@@ -110,7 +116,7 @@ module Mutant
110
116
  when 1
111
117
  expressions.first
112
118
  else
113
- fail "Ambigous expression: #{input.inspect}"
119
+ fail AmbigousExpressionError, "Ambigous expression: #{input.inspect}"
114
120
  end
115
121
  end
116
122
 
@@ -4,10 +4,10 @@ module Mutant
4
4
  # Explicit method expression
5
5
  class Method < self
6
6
 
7
- MATCHERS = {
7
+ MATCHERS = IceNine.deep_freeze(
8
8
  '.' => Matcher::Methods::Singleton,
9
9
  '#' => Matcher::Methods::Instance
10
- }.freeze
10
+ )
11
11
 
12
12
  register(
13
13
  /\A(?<scope_name>#{SCOPE_PATTERN})(?<scope_symbol>[.#])(?<method_name>#{METHOD_NAME_PATTERN})\z/
@@ -15,18 +15,18 @@ module Mutant
15
15
 
16
16
  # Return method matcher
17
17
  #
18
- # @param [Cache] cache
18
+ # @param [Env] env
19
19
  #
20
20
  # @return [Matcher::Method]
21
21
  #
22
22
  # @api private
23
23
  #
24
- def matcher(cache)
25
- methods_matcher = MATCHERS.fetch(scope_symbol).new(cache, scope)
24
+ def matcher(env)
25
+ methods_matcher = MATCHERS.fetch(scope_symbol).new(env, scope)
26
26
  method = methods_matcher.methods.detect do |meth|
27
27
  meth.name.equal?(method_name)
28
28
  end or raise NameError, "Cannot find method #{identifier}"
29
- methods_matcher.matcher.build(cache, scope, method)
29
+ methods_matcher.matcher.build(env, scope, method)
30
30
  end
31
31
 
32
32
  private
@@ -71,16 +71,6 @@ module Mutant
71
71
  match[__method__]
72
72
  end
73
73
 
74
- # Return matcher class
75
- #
76
- # @return [Class:Mutant::Matcher]
77
- #
78
- # @api private
79
- #
80
- def methods_matcher(cache)
81
- MATCHERS.fetch(scope_symbol).new(cache, scope)
82
- end
83
-
84
74
  end # Method
85
75
  end # Expression
86
76
  end # Mutant
@@ -4,10 +4,10 @@ module Mutant
4
4
  # Abstrat base class for methods expression
5
5
  class Methods < self
6
6
 
7
- MATCHERS = {
7
+ MATCHERS = IceNine.deep_freeze(
8
8
  '.' => Matcher::Methods::Singleton,
9
9
  '#' => Matcher::Methods::Instance
10
- }.freeze
10
+ )
11
11
 
12
12
  register(
13
13
  /\A(?<scope_name>#{SCOPE_PATTERN})(?<scope_symbol>[.#])\z/
@@ -15,14 +15,14 @@ module Mutant
15
15
 
16
16
  # Return method matcher
17
17
  #
18
- # @param [Cache] cache
18
+ # @param [Env] env
19
19
  #
20
20
  # @return [Matcher::Method]
21
21
  #
22
22
  # @api private
23
23
  #
24
- def matcher(cache)
25
- MATCHERS.fetch(scope_symbol).new(cache, scope)
24
+ def matcher(env)
25
+ MATCHERS.fetch(scope_symbol).new(env, scope)
26
26
  end
27
27
 
28
28
  private
@@ -26,14 +26,14 @@ module Mutant
26
26
 
27
27
  # Return matcher
28
28
  #
29
- # @param [Cache] cache
29
+ # @param [Env] env
30
30
  #
31
31
  # @return [Matcher]
32
32
  #
33
33
  # @api private
34
34
  #
35
- def matcher(cache)
36
- Matcher::Namespace.new(cache, self)
35
+ def matcher(env)
36
+ Matcher::Namespace.new(env, self)
37
37
  end
38
38
 
39
39
  # Return length of match
@@ -61,7 +61,7 @@ module Mutant
61
61
  # @api private
62
62
  #
63
63
  def namespace
64
- match[__method__] || ''
64
+ match[__method__] || EMPTY_STRING
65
65
  end
66
66
 
67
67
  end # Recursive
@@ -75,14 +75,14 @@ module Mutant
75
75
 
76
76
  # Return matcher
77
77
  #
78
- # @param [Cache] cache
78
+ # @param [Cache] env
79
79
  #
80
80
  # @return [Matcher]
81
81
  #
82
82
  # @api private
83
83
  #
84
- def matcher(cache)
85
- Matcher::Scope.new(cache, Mutant.constant_lookup(namespace))
84
+ def matcher(env)
85
+ Matcher::Scope.new(env, Mutant.constant_lookup(namespace), self)
86
86
  end
87
87
 
88
88
  private
@@ -44,16 +44,6 @@ module Mutant
44
44
  self
45
45
  end
46
46
 
47
- # Perform integration teardown
48
- #
49
- # @return [self]
50
- #
51
- # @api private
52
- #
53
- def teardown
54
- self
55
- end
56
-
57
47
  # Return all available tests by integration
58
48
  #
59
49
  # @return [Enumerable<Test>]
@@ -3,23 +3,49 @@ module Mutant
3
3
  module Isolation
4
4
  Error = Class.new(RuntimeError)
5
5
 
6
- # Call block in isolation
7
- #
8
- # This isolation implements the fork strategy.
9
- # Future strategies will probably use a process pool that can
10
- # handle multiple mutation kills, in-isolation at once.
11
- #
12
- # @return [Object]
13
- #
14
- # @raise [Error]
15
- #
16
- # @api private
17
- #
18
- def self.call(&block)
19
- Parallel.map([block], in_processes: 1) do
6
+ module None
7
+
8
+ # Call block in isolation
9
+ #
10
+ # @return [Object]
11
+ #
12
+ # @raise [Error]
13
+ # if block terminates abnormal
14
+ #
15
+ # @api private
16
+ #
17
+ def self.call(&block)
20
18
  block.call
21
- end.first
19
+ rescue => exception
20
+ fail Error, exception
21
+ end
22
22
  end
23
23
 
24
+ module Fork
25
+
26
+ # Call block in isolation
27
+ #
28
+ # This isolation implements the fork strategy.
29
+ # Future strategies will probably use a process pool that can
30
+ # handle multiple mutation kills, in-isolation at once.
31
+ #
32
+ # @return [Object]
33
+ # returns block execution result
34
+ #
35
+ # @raise [Error]
36
+ # if block terminates abnormal
37
+ #
38
+ # @api private
39
+ #
40
+ def self.call(&block)
41
+ Parallel.map([block], in_processes: 1) do
42
+ block.call
43
+ end.first
44
+ rescue Parallel::DeadWorker => exception
45
+ fail Error, exception
46
+ end
47
+
48
+ end # Fork
49
+
24
50
  end # Isolator
25
51
  end # Mutant