mutant 0.9.9 → 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -165,8 +165,6 @@ require 'mutant/selector/expression'
165
165
  require 'mutant/selector/null'
166
166
  require 'mutant/config'
167
167
  require 'mutant/cli'
168
- require 'mutant/color'
169
- require 'mutant/diff'
170
168
  require 'mutant/runner'
171
169
  require 'mutant/runner/sink'
172
170
  require 'mutant/result'
@@ -23,7 +23,7 @@ module Mutant
23
23
  emit_singletons
24
24
  emit_inverse
25
25
  emit_lower_bound_mutations
26
- emit_upper_bound_mutations
26
+ emit_upper_bound_mutations if upper_bound
27
27
  end
28
28
 
29
29
  def emit_inverse
@@ -45,7 +45,7 @@ module Mutant
45
45
  private
46
46
 
47
47
  def status_color
48
- success? ? Color::GREEN : Color::RED
48
+ success? ? Unparser::Color::GREEN : Unparser::Color::RED
49
49
  end
50
50
 
51
51
  def visit_collection(printer, collection)
@@ -71,7 +71,7 @@ module Mutant
71
71
  end
72
72
 
73
73
  def colorize(color, message)
74
- color = Color::NONE unless tty?
74
+ color = Unparser::Color::NONE unless tty?
75
75
  color.format(message)
76
76
  end
77
77
 
@@ -67,7 +67,7 @@ module Mutant
67
67
  end
68
68
 
69
69
  def evil_details
70
- diff = Diff.build(mutation.original_source, mutation.source)
70
+ diff = Unparser::Diff.build(mutation.original_source, mutation.source)
71
71
  diff = color? ? diff.colorized_diff : diff.diff
72
72
  if diff
73
73
  output.write(diff)
@@ -23,18 +23,57 @@ module Mutant
23
23
  class Memoized < self
24
24
  include AST::Sexp
25
25
 
26
+ FREEZER_OPTION_VALUES = {
27
+ Adamantium::Freezer::Deep => :deep,
28
+ Adamantium::Freezer::Flat => :flat,
29
+ Adamantium::Freezer::Noop => :noop
30
+ }.freeze
31
+
32
+ private_constant(*constants(false))
33
+
26
34
  # Prepare subject for mutation insertion
27
35
  #
28
36
  # @return [self]
29
37
  def prepare
30
- scope.__send__(:memoized_methods).instance_variable_get(:@memory).delete(name)
38
+ memory.delete(name)
31
39
  super()
32
40
  end
33
41
 
34
42
  private
35
43
 
36
44
  def wrap_node(mutant)
37
- s(:begin, mutant, s(:send, nil, :memoize, s(:args, s(:sym, name))))
45
+ s(:begin, mutant, s(:send, nil, :memoize, s(:args, s(:sym, name), *options)))
46
+ end
47
+
48
+ # The optional AST node for adamantium memoization options
49
+ #
50
+ # @return [Array(Parser::AST::Node), nil]
51
+ def options
52
+ # rubocop:disable Style/GuardClause
53
+ if FREEZER_OPTION_VALUES.key?(freezer)
54
+ [
55
+ s(:hash,
56
+ s(:pair,
57
+ s(:sym, :freezer),
58
+ s(:sym, FREEZER_OPTION_VALUES.fetch(freezer))))
59
+ ]
60
+ end
61
+ # rubocop:enable Style/GuardClause
62
+ end
63
+
64
+ # The freezer used for memoization
65
+ #
66
+ # @return [Object]
67
+ def freezer
68
+ memory.fetch(name).instance_variable_get(:@freezer)
69
+ end
70
+ memoize :freezer, freezer: :noop
71
+
72
+ # The memory used for memoization
73
+ #
74
+ # @return [ThreadSafe::Cache]
75
+ def memory
76
+ scope.__send__(:memoized_methods).instance_variable_get(:@memory)
38
77
  end
39
78
 
40
79
  end # Memoized
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.9.9'
5
+ VERSION = '0.9.10'
6
6
  end # Mutant
@@ -37,3 +37,29 @@ Mutant::Meta::Example.add :erange do
37
37
  mutation '1...101'
38
38
  mutation '1...-100'
39
39
  end
40
+
41
+ unless RUBY_VERSION.start_with?('2.5')
42
+ Mutant::Meta::Example.add :erange do
43
+ source '1...'
44
+
45
+ singleton_mutations
46
+ mutation '-1...'
47
+ mutation '0...'
48
+ mutation '1..'
49
+ mutation '2...'
50
+ mutation 'nil...'
51
+ mutation 'self...'
52
+ end
53
+
54
+ Mutant::Meta::Example.add :irange do
55
+ source '1..'
56
+
57
+ singleton_mutations
58
+ mutation '-1..'
59
+ mutation '0..'
60
+ mutation '1...'
61
+ mutation '2..'
62
+ mutation 'nil..'
63
+ mutation 'self..'
64
+ end
65
+ end
@@ -26,16 +26,19 @@ Gem::Specification.new do |gem|
26
26
  gem.add_runtime_dependency('anima', '~> 0.3.1')
27
27
  gem.add_runtime_dependency('ast', '~> 2.2')
28
28
  gem.add_runtime_dependency('concord', '~> 0.1.5')
29
- gem.add_runtime_dependency('diff-lcs', '= 1.3')
29
+ gem.add_runtime_dependency('diff-lcs', '~> 1.3')
30
30
  gem.add_runtime_dependency('equalizer', '~> 0.0.9')
31
31
  gem.add_runtime_dependency('ice_nine', '~> 0.11.1')
32
32
  gem.add_runtime_dependency('memoizable', '~> 0.4.2')
33
33
  gem.add_runtime_dependency('mprelude', '~> 0.1.0')
34
34
  gem.add_runtime_dependency('parser', '~> 2.7.1')
35
35
  gem.add_runtime_dependency('procto', '~> 0.0.2')
36
- gem.add_runtime_dependency('unparser', '~> 0.4.6')
36
+ gem.add_runtime_dependency('unparser', '~> 0.4.8')
37
37
  gem.add_runtime_dependency('variable', '~> 0.0.1')
38
38
 
39
- gem.add_development_dependency('devtools', '~> 0.1.25')
40
- gem.add_development_dependency('parallel', '~> 1.3')
39
+ gem.add_development_dependency('parallel', '~> 1.3')
40
+ gem.add_development_dependency('rspec', '~> 3.9')
41
+ gem.add_development_dependency('rspec-core', '~> 3.9')
42
+ gem.add_development_dependency('rspec-its', '~> 1.2.0')
43
+ gem.add_development_dependency('rubocop', '~> 0.79.0')
41
44
  end
@@ -1,31 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- if ENV['COVERAGE'] == 'true'
4
- require 'simplecov'
5
-
6
- SimpleCov.start do
7
- command_name 'spec:unit'
8
-
9
- add_filter 'config'
10
- add_filter 'spec'
11
- add_filter 'vendor'
12
- add_filter 'test_app'
13
- add_filter 'lib/mutant.rb' # simplecov bug not seeing default block is executed
14
-
15
- minimum_coverage 100
16
- end
17
- end
18
-
19
- require 'tempfile'
20
- require 'concord'
21
- require 'anima'
22
3
  require 'adamantium'
23
- require 'devtools/spec_helper'
24
- require 'unparser/cli'
4
+ require 'anima'
5
+ require 'concord'
25
6
  require 'mutant'
26
7
  require 'mutant/meta'
8
+ require 'rspec/its'
9
+ require 'timeout'
10
+ require 'tempfile'
11
+ require 'tmpdir'
12
+
13
+ require './spec/shared/framework_integration_behavior'
14
+ require './spec/shared/method_matcher_behavior'
15
+ require './spec/support/corpus'
16
+ require './spec/support/file_system'
17
+ require './spec/support/ruby_vm'
18
+ require './spec/support/shared_context'
19
+ require './spec/support/xspec'
27
20
 
28
- $LOAD_PATH << File.join(TestApp.root, 'lib')
21
+ $LOAD_PATH << File.expand_path('../test_app/lib', __dir__)
29
22
 
30
23
  require 'test_app'
31
24
 
@@ -67,6 +60,28 @@ module XSpecHelper
67
60
  end
68
61
  end # XSpecHelper
69
62
 
63
+ RSpec.configuration.around(file_path: %r{spec/unit}) do |example|
64
+ Timeout.timeout(1, &example)
65
+ end
66
+
67
+ RSpec.shared_examples_for 'a command method' do
68
+ it 'returns self' do
69
+ should equal(object)
70
+ end
71
+ end
72
+
73
+ RSpec.shared_examples_for 'an idempotent method' do
74
+ it 'is idempotent' do
75
+ first = subject
76
+ fail 'RSpec not configured for threadsafety' unless RSpec.configuration.threadsafe?
77
+ mutex = __memoized.instance_variable_get(:@mutex)
78
+ memoized = __memoized.instance_variable_get(:@memoized)
79
+
80
+ mutex.synchronize { memoized.delete(:subject) }
81
+ should equal(first)
82
+ end
83
+ end
84
+
70
85
  RSpec.configure do |config|
71
86
  config.extend(SharedContext)
72
87
  config.include(ParserHelper)
@@ -12,7 +12,7 @@ RSpec.describe Mutant::Reporter::CLI::Printer::EnvResult do
12
12
  subject-a
13
13
  - test-a
14
14
  evil:subject-a:d27d2
15
- @@ -1,2 +1,2 @@
15
+ @@ -1 +1 @@
16
16
  -true
17
17
  +false
18
18
  -----------------------
@@ -13,13 +13,13 @@ RSpec.describe Mutant::Reporter::CLI::Printer::MutationProgressResult do
13
13
  context 'on killed mutant' do
14
14
  with(:mutation_a_test_result) { { passed: true } }
15
15
 
16
- it_reports Mutant::Color::RED.format('F')
16
+ it_reports Unparser::Color::RED.format('F')
17
17
  end
18
18
 
19
19
  context 'on alive mutant' do
20
20
  with(:mutation_a_test_result) { { passed: false } }
21
21
 
22
- it_reports Mutant::Color::GREEN.format('.')
22
+ it_reports Unparser::Color::GREEN.format('.')
23
23
  end
24
24
  end
25
25
  end
@@ -17,7 +17,7 @@ RSpec.describe Mutant::Reporter::CLI::Printer::MutationResult do
17
17
 
18
18
  it_reports(<<~'REPORT')
19
19
  evil:subject-a:d27d2
20
- @@ -1,2 +1,2 @@
20
+ @@ -1 +1 @@
21
21
  -true
22
22
  +false
23
23
  -----------------------
@@ -42,16 +42,16 @@ RSpec.describe Mutant::Reporter::CLI::Printer::MutationResult do
42
42
 
43
43
  it_reports(
44
44
  [
45
- [Mutant::Color::NONE, "evil:subject-a:d27d2\n"],
46
- [Mutant::Color::NONE, "@@ -1,2 +1,2 @@\n"],
47
- [Mutant::Color::RED, "-true\n"],
48
- [Mutant::Color::GREEN, "+false\n"],
49
- [Mutant::Color::NONE, "-----------------------\n"],
50
- [Mutant::Color::NONE, "- 1 @ runtime: 1.0\n"],
51
- [Mutant::Color::NONE, " - test-a\n"],
52
- [Mutant::Color::NONE, "Test Output:\n"],
53
- [Mutant::Color::NONE, "mutation a test result output\n"],
54
- [Mutant::Color::NONE, "-----------------------\n"]
45
+ [Unparser::Color::NONE, "evil:subject-a:d27d2\n"],
46
+ [Unparser::Color::NONE, "@@ -1 +1 @@\n"],
47
+ [Unparser::Color::RED, "-true\n"],
48
+ [Unparser::Color::GREEN, "+false\n"],
49
+ [Unparser::Color::NONE, "-----------------------\n"],
50
+ [Unparser::Color::NONE, "- 1 @ runtime: 1.0\n"],
51
+ [Unparser::Color::NONE, " - test-a\n"],
52
+ [Unparser::Color::NONE, "Test Output:\n"],
53
+ [Unparser::Color::NONE, "mutation a test result output\n"],
54
+ [Unparser::Color::NONE, "-----------------------\n"]
55
55
  ].map { |color, text| color.format(text) }.join
56
56
  )
57
57
  end
@@ -59,7 +59,7 @@ RSpec.describe Mutant::Reporter::CLI::Printer::MutationResult do
59
59
  context 'on non tty' do
60
60
  it_reports(<<~'STR')
61
61
  evil:subject-a:d27d2
62
- @@ -1,2 +1,2 @@
62
+ @@ -1 +1 @@
63
63
  -true
64
64
  +false
65
65
  -----------------------
@@ -20,7 +20,7 @@ RSpec.describe Mutant::Reporter::CLI::Printer::SubjectResult do
20
20
  subject-a
21
21
  - test-a
22
22
  evil:subject-a:d27d2
23
- @@ -1,2 +1,2 @@
23
+ @@ -1 +1 @@
24
24
  -true
25
25
  +false
26
26
  -----------------------
@@ -64,12 +64,12 @@ RSpec.describe Mutant::Reporter::CLI::Printer do
64
64
 
65
65
  context 'on tty' do
66
66
  context 'on success' do
67
- it_reports Mutant::Color::GREEN.format('foo bar') + "\n"
67
+ it_reports Unparser::Color::GREEN.format('foo bar') + "\n"
68
68
  end
69
69
 
70
70
  context 'on failure' do
71
71
  let(:success?) { false }
72
- it_reports Mutant::Color::RED.format('foo bar') + "\n"
72
+ it_reports Unparser::Color::RED.format('foo bar') + "\n"
73
73
  end
74
74
  end
75
75
 
@@ -146,13 +146,13 @@ RSpec.describe Mutant::Reporter::CLI::Printer do
146
146
  let(:class_under_test) do
147
147
  Class.new(described_class) do
148
148
  def run
149
- puts(colorize(Mutant::Color::RED, 'foo'))
149
+ puts(colorize(Unparser::Color::RED, 'foo'))
150
150
  end
151
151
  end
152
152
  end
153
153
 
154
154
  context 'when output is a tty?' do
155
- it_reports Mutant::Color::RED.format('foo') + "\n"
155
+ it_reports Unparser::Color::RED.format('foo') + "\n"
156
156
  end
157
157
 
158
158
  context 'when output is NOT a tty?' do
@@ -116,7 +116,7 @@ RSpec.describe Mutant::Reporter::CLI do
116
116
  let(:tty?) { true }
117
117
 
118
118
  # rubocop:disable Metrics/LineLength
119
- it_reports Mutant::Color::GREEN.format('progress: 00/02 alive: 0 runtime: 4.00s killtime: 0.00s mutations/s: 0.00') + "\n"
119
+ it_reports Unparser::Color::GREEN.format('progress: 00/02 alive: 0 runtime: 4.00s killtime: 0.00s mutations/s: 0.00') + "\n"
120
120
  # rubocop:enable Metrics/LineLength
121
121
  end
122
122
 
@@ -62,10 +62,6 @@ RSpec.describe Mutant::Subject::Method::Instance do
62
62
  end
63
63
 
64
64
  describe '#prepare' do
65
- let(:context) do
66
- Mutant::Context.new(scope, instance_double(Pathname))
67
- end
68
-
69
65
  subject { object.prepare }
70
66
 
71
67
  it 'undefines method on scope' do
@@ -104,7 +100,10 @@ RSpec.describe Mutant::Subject::Method::Instance::Memoized do
104
100
  )
105
101
  end
106
102
 
107
- let(:context) { double('Context') }
103
+ let(:context) do
104
+ Mutant::Context.new(scope, double('Source Path'))
105
+ end
106
+
108
107
  let(:warnings) { instance_double(Mutant::Warnings) }
109
108
 
110
109
  let(:node) do
@@ -115,12 +114,7 @@ RSpec.describe Mutant::Subject::Method::Instance::Memoized do
115
114
  allow(warnings).to receive(:call).and_yield
116
115
  end
117
116
 
118
- describe '#prepare' do
119
-
120
- let(:context) do
121
- Mutant::Context.new(scope, double('Source Path'))
122
- end
123
-
117
+ shared_context 'memoizable scope setup' do
124
118
  let(:scope) do
125
119
  Class.new do
126
120
  include Memoizable
@@ -128,6 +122,23 @@ RSpec.describe Mutant::Subject::Method::Instance::Memoized do
128
122
  memoize :foo
129
123
  end
130
124
  end
125
+ end
126
+
127
+ shared_context 'adamantium scope setup' do
128
+ let(:scope) do
129
+ memoize_options = self.memoize_options
130
+ memoize_provider = self.memoize_provider
131
+
132
+ Class.new do
133
+ include memoize_provider
134
+ def foo; end
135
+ memoize :foo, **memoize_options
136
+ end
137
+ end
138
+ end
139
+
140
+ describe '#prepare' do
141
+ include_context 'memoizable scope setup'
131
142
 
132
143
  subject { object.prepare }
133
144
 
@@ -142,40 +153,124 @@ RSpec.describe Mutant::Subject::Method::Instance::Memoized do
142
153
  it_should_behave_like 'a command method'
143
154
  end
144
155
 
145
- describe '#mutations', mutant_expression: 'Mutant::Subject#mutations' do
156
+ describe '#mutations' do
146
157
  subject { object.mutations }
147
158
 
148
159
  let(:expected) do
149
160
  [
150
161
  Mutant::Mutation::Neutral.new(
151
162
  object,
152
- s(:begin,
153
- s(:def, :foo, s(:args)), s(:send, nil, :memoize, s(:args, s(:sym, :foo))))
163
+ s(:begin, s(:def, :foo, s(:args)), memoize_node)
154
164
  ),
155
165
  Mutant::Mutation::Evil.new(
156
166
  object,
157
- s(:begin,
158
- s(:def, :foo, s(:args), s(:send, nil, :raise)), s(:send, nil, :memoize, s(:args, s(:sym, :foo))))
167
+ s(:begin, s(:def, :foo, s(:args), s(:send, nil, :raise)), memoize_node)
159
168
  ),
160
169
  Mutant::Mutation::Evil.new(
161
170
  object,
162
- s(:begin,
163
- s(:def, :foo, s(:args), s(:zsuper)), s(:send, nil, :memoize, s(:args, s(:sym, :foo))))
171
+ s(:begin, s(:def, :foo, s(:args), s(:zsuper)), memoize_node)
164
172
  ),
165
173
  Mutant::Mutation::Evil.new(
166
174
  object,
167
- s(:begin,
168
- s(:def, :foo, s(:args), nil), s(:send, nil, :memoize, s(:args, s(:sym, :foo))))
175
+ s(:begin, s(:def, :foo, s(:args), nil), memoize_node)
169
176
  )
170
177
  ]
171
178
  end
172
179
 
173
- it { should eql(expected) }
180
+ let(:memoize_node) do
181
+ s(:send, nil, :memoize, s(:args, s(:sym, :foo), *options_node))
182
+ end
183
+
184
+ let(:options_node) { nil }
185
+
186
+ context 'when Memoizable is included in scope' do
187
+ include_context 'memoizable scope setup'
188
+
189
+ it { should eql(expected) }
190
+ end
191
+
192
+ context 'when Adamantium is included in scope' do
193
+ include_context 'adamantium scope setup'
194
+
195
+ {
196
+ Adamantium => :deep,
197
+ Adamantium::Flat => :flat
198
+ }.each do |memoize_provider, default_freezer_option|
199
+ context "as include #{memoize_provider}" do
200
+ let(:memoize_provider) { memoize_provider }
201
+ let(:default_freezer_option) { default_freezer_option }
202
+
203
+ let(:options_node) do
204
+ [s(:hash, s(:pair, s(:sym, :freezer), s(:sym, freezer_option)))]
205
+ end
206
+
207
+ context 'when no memoize options are given' do
208
+ let(:memoize_options) { Mutant::EMPTY_HASH }
209
+ let(:freezer_option) { default_freezer_option }
210
+
211
+ it { should eql(expected) }
212
+ end
213
+
214
+ context 'when memoize options are given' do
215
+ let(:memoize_options) { { freezer: freezer_option } }
216
+
217
+ %i[deep flat noop].each do |option|
218
+ context "as #{option.inspect}" do
219
+ let(:freezer_option) { option }
220
+
221
+ it { should eql(expected) }
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
174
228
  end
175
229
 
176
230
  describe '#source' do
177
231
  subject { object.source }
178
232
 
179
- it { should eql("def foo\nend\nmemoize(:foo)") }
233
+ context 'when Memoizable is included in scope' do
234
+ include_context 'memoizable scope setup'
235
+
236
+ let(:source) { "def foo\nend\nmemoize(:foo)" }
237
+
238
+ it { should eql(source) }
239
+ end
240
+
241
+ context 'when Adamantium is included in scope' do
242
+ include_context 'adamantium scope setup'
243
+
244
+ let(:source) do
245
+ "def foo\nend\nmemoize(:foo, { freezer: #{freezer_option.inspect} })"
246
+ end
247
+
248
+ {
249
+ Adamantium => :deep,
250
+ Adamantium::Flat => :flat
251
+ }.each do |memoize_provider, default_freezer_option|
252
+ context "as include #{memoize_provider}" do
253
+ let(:memoize_provider) { memoize_provider }
254
+
255
+ context 'when no memoize options are given' do
256
+ let(:memoize_options) { Mutant::EMPTY_HASH }
257
+ let(:freezer_option) { default_freezer_option }
258
+
259
+ it { should eql(source) }
260
+ end
261
+
262
+ context 'when memoize options are given' do
263
+ %i[deep flat noop].each do |freezer_option|
264
+ context "as #{freezer_option.inspect}" do
265
+ let(:memoize_options) { { freezer: freezer_option } }
266
+ let(:freezer_option) { freezer_option }
267
+
268
+ it { should eql(source) }
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
180
275
  end
181
276
  end