mutant 0.9.9 → 0.9.10

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.
@@ -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