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
@@ -2,7 +2,7 @@ module Mutant
2
2
  class Matcher
3
3
  # A chain of matchers
4
4
  class Chain < self
5
- include Concord::Public.new(:matchers)
5
+ include Concord.new(:matchers)
6
6
 
7
7
  # Enumerate subjects
8
8
  #
@@ -24,22 +24,6 @@ module Mutant
24
24
  self
25
25
  end
26
26
 
27
- # Build matcher chain
28
- #
29
- # @param [Enumerable<Matcher>] matchers
30
- #
31
- # @return [Matcher]
32
- #
33
- # @api private
34
- #
35
- def self.build(matchers)
36
- if matchers.length.equal?(1)
37
- return matchers.first
38
- end
39
-
40
- new(matchers)
41
- end
42
-
43
27
  end # Chain
44
28
  end # Matcher
45
29
  end # Mutant
@@ -0,0 +1,108 @@
1
+ module Mutant
2
+ class Matcher
3
+
4
+ # Compiler for complex matchers
5
+ class Compiler
6
+ include Concord.new(:env, :config), AST::Sexp, Procto.call(:result)
7
+
8
+ # Return generated matcher
9
+ #
10
+ # @return [Mutant::Matcher]
11
+ #
12
+ # @api private
13
+ #
14
+ def result
15
+ Filter.new(
16
+ Chain.build(config.match_expressions.map(&method(:matcher))),
17
+ predicate
18
+ )
19
+ end
20
+
21
+ # Subject expression prefix predicate
22
+ class SubjectPrefix
23
+ include Concord.new(:expression)
24
+
25
+ # Test if subject expression is matched by prefix
26
+ #
27
+ # @return [Boolean]
28
+ #
29
+ # @api private
30
+ #
31
+ def call(subject)
32
+ expression.prefix?(subject.expression)
33
+ end
34
+
35
+ end # SubjectPrefix
36
+
37
+ private
38
+
39
+ # Return predicate
40
+ #
41
+ # @return [#call]
42
+ #
43
+ # @api private
44
+ #
45
+ def predicate
46
+ if subject_selector && subject_rejector
47
+ Morpher::Evaluator::Predicate::Boolean::And.new([
48
+ subject_selector,
49
+ Morpher::Evaluator::Predicate::Negation.new(subject_rejector)
50
+ ])
51
+ elsif subject_selector
52
+ subject_selector
53
+ elsif subject_rejector
54
+ Morpher::Evaluator::Predicate::Negation.new(subject_rejector)
55
+ else
56
+ Morpher::Evaluator::Predicate::Tautology.new
57
+ end
58
+ end
59
+
60
+ # Return subject selector
61
+ #
62
+ # @return [#call]
63
+ # if selector is present
64
+ #
65
+ # @return [nil]
66
+ # otherwise
67
+ #
68
+ # @api private
69
+ #
70
+ def subject_selector
71
+ selectors = config.subject_selects.map do |attribute, value|
72
+ Morpher.compile(s(:eql, s(:attribute, attribute), s(:static, value)))
73
+ end
74
+
75
+ Morpher::Evaluator::Predicate::Boolean::Or.new(selectors) if selectors.any?
76
+ end
77
+
78
+ # Return subject rejector
79
+ #
80
+ # @return [#call]
81
+ # if there is a subject rejector
82
+ #
83
+ # @return [nil]
84
+ # otherwise
85
+ #
86
+ # @api private
87
+ #
88
+ def subject_rejector
89
+ rejectors = config.subject_ignores.map(&SubjectPrefix.method(:new))
90
+
91
+ Morpher::Evaluator::Predicate::Boolean::Or.new(rejectors) if rejectors.any?
92
+ end
93
+
94
+ # Return a matcher from expression
95
+ #
96
+ # @param [Mutant::Expression] expression
97
+ #
98
+ # @return [Matcher]
99
+ #
100
+ # @api private
101
+ #
102
+ def matcher(expression)
103
+ expression.matcher(env)
104
+ end
105
+
106
+ end # Compiler
107
+ end # Matcher
108
+ end # Mutant
@@ -0,0 +1,28 @@
1
+ module Mutant
2
+ class Matcher
3
+ # Match configuration
4
+ class Config
5
+ include Adamantium, Anima::Update, Anima.new(
6
+ :match_expressions,
7
+ :subject_ignores,
8
+ :subject_selects
9
+ )
10
+
11
+ DEFAULT = new(Hash[anima.attribute_names.map { |name| [name, []] }])
12
+
13
+ # Return configuration with added value
14
+ #
15
+ # @param [Symbol] attribute
16
+ # @param [Object] value
17
+ #
18
+ # @return [Config]
19
+ #
20
+ # @api private
21
+ #
22
+ def add(attribute, value)
23
+ update(attribute => public_send(attribute).dup << value)
24
+ end
25
+
26
+ end # Config
27
+ end # Matcher
28
+ end # Mutant
@@ -120,7 +120,7 @@ module Mutant
120
120
  def subject
121
121
  node = matched_node
122
122
  return unless node
123
- self.class::SUBJECT_CLASS.new(context, node)
123
+ self.class::SUBJECT_CLASS.new(env.config, context, node)
124
124
  end
125
125
  memoize :subject
126
126
 
@@ -19,8 +19,9 @@ module Mutant
19
19
  #
20
20
  def each(&block)
21
21
  return to_enum unless block_given?
22
- scopes.each do |scope|
23
- scope.each(&block)
22
+
23
+ env.matchable_scopes.select do |scope|
24
+ scope.each(&block) if match?(scope)
24
25
  end
25
26
 
26
27
  self
@@ -28,64 +29,16 @@ module Mutant
28
29
 
29
30
  private
30
31
 
31
- # Return scope enumerator
32
- #
33
- # @return [Array<Class, Module>]
34
- #
35
- # @api private
36
- #
37
- def scopes
38
- ::ObjectSpace.each_object(Module).each_with_object([]) do |scope, aggregate|
39
- aggregate << Scope.new(env, scope) if match?(scope)
40
- end.sort_by(&:identification)
41
- end
42
- memoize :scopes
43
-
44
- # Return scope name
45
- #
46
- # @param [Class,Module] scope
47
- #
48
- # @return [String]
49
- # if scope has a name and does not raise exceptions optaining it
50
- #
51
- # @return [nil]
52
- # otherwise
53
- #
54
- # @api private
55
- #
56
- # rubocop:disable LineLength
57
- #
58
- def scope_name(scope)
59
- scope.name
60
- rescue => exception
61
- env.warn("While optaining #{scope.class}#name from: #{scope.inspect} It raised an error: #{exception.inspect} fix your lib!")
62
- nil
63
- end
64
-
65
32
  # Test scope if name matches expresion
66
33
  #
67
- # @param [Module,Class] scope
34
+ # @param [Module, Class] scope
68
35
  #
69
36
  # @return [Boolean]
70
37
  #
71
38
  # @api private
72
39
  #
73
40
  def match?(scope)
74
- name = scope_name(scope) or return false
75
-
76
- unless name.kind_of?(String)
77
- env.warn("#{scope.class}#name from: #{scope.inspect} did not return a String or nil. Fix your lib to support normal ruby semantics!")
78
- return false
79
- end
80
-
81
- scope_expression = Expression.try_parse(name)
82
-
83
- unless scope_expression
84
- $stderr.puts("WARNING: #{name.inspect} is not an identifiable ruby class name.")
85
- return false
86
- end
87
-
88
- expression.prefix?(scope_expression)
41
+ expression.prefix?(scope.expression)
89
42
  end
90
43
 
91
44
  end # Namespace
@@ -2,7 +2,7 @@ module Mutant
2
2
  class Matcher
3
3
  # A null matcher, that does not match any subjects
4
4
  class Null < self
5
- include Equalizer.new
5
+ include Concord.new
6
6
 
7
7
  # Enumerate subjects
8
8
  #
@@ -2,7 +2,7 @@ module Mutant
2
2
  class Matcher
3
3
  # Matcher for specific namespace
4
4
  class Scope < self
5
- include Concord::Public.new(:env, :scope)
5
+ include Concord::Public.new(:env, :scope, :expression)
6
6
 
7
7
  MATCHERS = [
8
8
  Matcher::Methods::Singleton,
@@ -18,16 +18,6 @@ module Mutant
18
18
  end
19
19
  memoize :root
20
20
 
21
- # Test if killer is successful
22
- #
23
- # @param [Killer] killer
24
- #
25
- # @return [Boolean]
26
- #
27
- # @api private
28
- #
29
- abstract_method :success?
30
-
31
21
  # Insert mutated node
32
22
  #
33
23
  # FIXME: Cache subject visibility in a better way! Ideally dont mutate it
@@ -87,14 +77,16 @@ module Mutant
87
77
  subject.source
88
78
  end
89
79
 
90
- # Test if test should fail under mutation
80
+ # Test if mutation is killed by test report
81
+ #
82
+ # @param [Report::Test] test_report
91
83
  #
92
84
  # @return [Boolean]
93
85
  #
94
86
  # @api private
95
87
  #
96
- def should_fail?
97
- self.class::SHOULD_FAIL
88
+ def killed_by?(test_report)
89
+ self.class::SHOULD_PASS.equal?(test_report.passed)
98
90
  end
99
91
 
100
92
  private
@@ -110,5 +102,29 @@ module Mutant
110
102
  end
111
103
  memoize :sha1
112
104
 
105
+ # Evil mutation that should case mutations to fail tests
106
+ class Evil < self
107
+
108
+ SHOULD_PASS = false
109
+ SYMBOL = 'evil'.freeze
110
+
111
+ end # Evil
112
+
113
+ # Neutral mutation that should not cause mutations to fail tests
114
+ class Neutral < self
115
+
116
+ SYMBOL = 'neutral'.freeze
117
+ SHOULD_PASS = true
118
+
119
+ end # Neutral
120
+
121
+ # Noop mutation, special case of neutral
122
+ class Noop < self
123
+
124
+ SYMBOL = 'noop'.freeze
125
+ SHOULD_PASS = true
126
+
127
+ end # Noop
128
+
113
129
  end # Mutation
114
130
  end # Mutant
@@ -49,22 +49,12 @@ module Mutant
49
49
  #
50
50
  alias_method :dup_node, :dup_input
51
51
 
52
- # Emit children mutations
53
- #
54
- # @return [undefined]
55
- #
56
- # @api private
57
- #
58
- def emit_children_mutations
59
- Mutator::Util::Array.each(children, self) do |children|
60
- emit_type(*children)
61
- end
62
- end
63
-
64
52
  # Return ast meta description
65
53
  #
66
54
  # @return [AST::Meta]
67
55
  #
56
+ # @api private
57
+ #
68
58
  def meta
69
59
  AST::Meta.for(node)
70
60
  end
@@ -12,7 +12,7 @@ module Mutant
12
12
  gvasgn: '$',
13
13
  cvasgn: '@@',
14
14
  ivasgn: '@',
15
- lvasgn: ''
15
+ lvasgn: EMPTY_STRING
16
16
  }
17
17
 
18
18
  MAP = IceNine.deep_freeze(
@@ -4,8 +4,6 @@ module Mutant
4
4
  class CLI < self
5
5
  include Concord.new(:output)
6
6
 
7
- NL = "\n".freeze
8
-
9
7
  # Report progress object
10
8
  #
11
9
  # @param [Object] object
@@ -6,6 +6,8 @@ module Mutant
6
6
  class Printer
7
7
  include AbstractType, Delegator, Adamantium::Flat, Concord.new(:output, :object)
8
8
 
9
+ NL = "\n".freeze
10
+
9
11
  # Run printer on object to output
10
12
  #
11
13
  # @param [IO] output
@@ -41,6 +43,18 @@ module Mutant
41
43
  success? ? Color::GREEN : Color::RED
42
44
  end
43
45
 
46
+ # Visit a collection of objects
47
+ #
48
+ # @return [Enumerable<Object>] collection
49
+ #
50
+ # @return [undefined]
51
+ #
52
+ # @api private
53
+ #
54
+ def visit_collection(collection)
55
+ collection.each(&method(:visit))
56
+ end
57
+
44
58
  # Visit object
45
59
  #
46
60
  # @param [Object] object
@@ -1,11 +1,9 @@
1
1
  module Mutant
2
2
  class Reporter
3
3
  class CLI
4
- # Abstract base class for process printers
4
+ # Abstract base and namespace class for process printers
5
5
  class Progress < Printer
6
6
  include AbstractType, Registry.new
7
-
8
- delegate :running?
9
7
  end # Progress
10
8
  end # CLI
11
9
  end # Reporter
@@ -5,9 +5,7 @@ module Mutant
5
5
  # Progress printer for configuration
6
6
  class Config < self
7
7
 
8
- handle(Mutant::Runner::Config)
9
-
10
- delegate :config
8
+ handle(Mutant::Config)
11
9
 
12
10
  # Report configuration
13
11
  #
@@ -18,12 +16,10 @@ module Mutant
18
16
  # @api private
19
17
  #
20
18
  def run
21
- if running?
22
- info 'Mutant configuration:'
23
- info 'Matcher: %s', config.matcher.inspect
24
- info 'Integration: %s', config.integration.name
25
- info 'Expect Coverage: %0.2f%%', config.expected_coverage.inspect
26
- end
19
+ info 'Mutant configuration:'
20
+ info 'Matcher: %s', object.matcher_config.inspect
21
+ info 'Integration: %s', object.integration.name
22
+ info 'Expect Coverage: %0.2f%%', object.expected_coverage.inspect
27
23
  self
28
24
  end
29
25