unparser 0.4.7 → 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -81,26 +81,40 @@ describe 'Unparser on ruby corpus', mutant: false do
81
81
  end
82
82
  end
83
83
 
84
- LOADER = Morpher.build do
85
- s(:block,
86
- s(:guard, s(:primitive, Array)),
87
- s(:map,
88
- s(:block,
89
- s(:guard, s(:primitive, Hash)),
90
- s(:hash_transform,
91
- s(:key_symbolize, :repo_uri, s(:guard, s(:primitive, String))),
92
- s(:key_symbolize, :repo_ref, s(:guard, s(:primitive, String))),
93
- s(:key_symbolize, :name, s(:guard, s(:primitive, String))),
94
- s(:key_symbolize, :exclude, s(:map, s(:guard, s(:primitive, String))))),
95
- s(:load_attribute_hash,
96
- # NOTE: The domain param has no DSL currently!
97
- Morpher::Evaluator::Transformer::Domain::Param.new(
98
- Project,
99
- [:repo_uri, :repo_ref, :name, :exclude]
100
- )))))
101
- end
84
+ transform = Mutant::Transform
85
+ string = transform::Primitive.new(String)
86
+ string_array = transform::Array.new(string)
87
+ path = ROOT.join('spec', 'integrations.yml')
88
+
89
+ loader =
90
+ transform::Named.new(
91
+ path.to_s,
92
+ transform::Sequence.new(
93
+ [
94
+ transform::Exception.new(SystemCallError, :read.to_proc),
95
+ transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
96
+ transform::Array.new(
97
+ transform::Sequence.new(
98
+ [
99
+ transform::Hash.new(
100
+ optional: [],
101
+ required: [
102
+ transform::Hash::Key.new('exclude', string_array),
103
+ transform::Hash::Key.new('name', string),
104
+ transform::Hash::Key.new('repo_ref', string),
105
+ transform::Hash::Key.new('repo_uri', string)
106
+ ]
107
+ ),
108
+ transform::Hash::Symbolize.new,
109
+ transform::Exception.new(Anima::Error, Project.public_method(:new))
110
+ ]
111
+ )
112
+ )
113
+ ]
114
+ )
115
+ )
102
116
 
103
- ALL = LOADER.call(YAML.load_file(ROOT.join('spec', 'integrations.yml')))
117
+ ALL = loader.apply(path).lmap(&:compact_message).from_right
104
118
  end
105
119
 
106
120
  Project::ALL.each do |project|
@@ -18,7 +18,7 @@
18
18
  - name: rubyspec
19
19
  repo_uri: 'https://github.com/ruby/spec.git'
20
20
  # Revision of rubyspec on the last CI build of unparser that passed
21
- repo_ref: 'origin/master'
21
+ repo_ref: 'b40189b88'
22
22
  exclude:
23
23
  - command_line/fixtures/bad_syntax.rb
24
24
  - core/array/pack/shared/float.rb
@@ -1,12 +1,34 @@
1
- require 'yaml'
1
+ require 'anima'
2
+ require 'mutant'
2
3
  require 'pathname'
4
+ require 'timeout'
3
5
  require 'unparser'
4
- require 'anima'
5
- require 'morpher'
6
- require 'devtools/spec_helper'
6
+ require 'yaml'
7
7
 
8
8
  require 'parser/current'
9
9
 
10
+ RSpec.configuration.around(file_path: %r{spec/unit}) do |example|
11
+ Timeout.timeout(0.1, &example)
12
+ end
13
+
14
+ RSpec.shared_examples_for 'a command method' do
15
+ it 'returns self' do
16
+ should equal(object)
17
+ end
18
+ end
19
+
20
+ RSpec.shared_examples_for 'an idempotent method' do
21
+ it 'is idempotent' do
22
+ first = subject
23
+ fail 'RSpec not configured for threadsafety' unless RSpec.configuration.threadsafe?
24
+ mutex = __memoized.instance_variable_get(:@mutex)
25
+ memoized = __memoized.instance_variable_get(:@memoized)
26
+
27
+ mutex.synchronize { memoized.delete(:subject) }
28
+ should equal(first)
29
+ end
30
+ end
31
+
10
32
  module SpecHelper
11
33
  def s(type, *children)
12
34
  Parser::AST::Node.new(type, children)
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Unparser::Color do
4
+ shared_examples 'actual color' do |code|
5
+ describe '#format' do
6
+
7
+ it 'returns formatted string' do
8
+ expect(apply).to eql("\e[#{code}mexample-string\e[0m")
9
+ end
10
+ end
11
+ end
12
+
13
+ describe '#format' do
14
+ let(:input) { 'example-string' }
15
+
16
+ def apply
17
+ object.format(input)
18
+ end
19
+
20
+ context 'RED' do
21
+ let(:object) { described_class::RED }
22
+
23
+ include_examples 'actual color', 31
24
+ end
25
+
26
+ context 'GREEN' do
27
+ let(:object) { described_class::GREEN }
28
+
29
+ include_examples 'actual color', 32
30
+ end
31
+
32
+ context 'NONE' do
33
+ let(:object) { described_class::NONE }
34
+
35
+ it 'returns original input' do
36
+ expect(apply).to be(input)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Unparser::Diff do
4
+ let(:object) { described_class }
5
+
6
+ describe '.build' do
7
+
8
+ subject { object.build(old_string, new_string) }
9
+
10
+ let(:old_string) { "foo\nbar" }
11
+ let(:new_string) { "bar\nbaz" }
12
+
13
+ it { should eql(described_class.new(%w[foo bar], %w[bar baz])) }
14
+
15
+ end
16
+
17
+ describe '#colorized_diff' do
18
+ let(:object) { described_class.new(old, new) }
19
+
20
+ subject { object.colorized_diff }
21
+
22
+ context 'when there is a diff at begin of hunk' do
23
+ let(:old) { %w[foo bar] }
24
+ let(:new) { %w[baz bar] }
25
+
26
+ let(:expectation) do
27
+ [
28
+ "@@ -1,3 +1,3 @@\n",
29
+ Unparser::Color::RED.format("-foo\n"),
30
+ Unparser::Color::GREEN.format("+baz\n"),
31
+ " bar\n"
32
+ ].join
33
+ end
34
+
35
+ it { should eql(expectation) }
36
+
37
+ it_should_behave_like 'an idempotent method'
38
+ end
39
+
40
+ context 'when there is no diff' do
41
+ let(:old) { '' }
42
+ let(:new) { '' }
43
+
44
+ it { should be(nil) }
45
+
46
+ it_should_behave_like 'an idempotent method'
47
+ end
48
+ end
49
+
50
+ describe '#diff' do
51
+ let(:object) { described_class.new(old, new) }
52
+
53
+ subject { object.diff }
54
+
55
+ context 'when there is a diff at begin and end' do
56
+ let(:old) { %w[foo bar foo] }
57
+ let(:new) { %w[baz bar baz] }
58
+
59
+ let(:expectation) do
60
+ <<~STR
61
+ @@ -1,4 +1,4 @@
62
+ -foo
63
+ +baz
64
+ bar
65
+ -foo
66
+ +baz
67
+ STR
68
+ end
69
+
70
+ it { should eql(expectation) }
71
+
72
+ it_should_behave_like 'an idempotent method'
73
+ end
74
+
75
+ context 'when there is a diff at begin of hunk' do
76
+ let(:old) { %w[foo bar] }
77
+ let(:new) { %w[baz bar] }
78
+
79
+ let(:expectation) do
80
+ <<~STR
81
+ @@ -1,3 +1,3 @@
82
+ -foo
83
+ +baz
84
+ bar
85
+ STR
86
+ end
87
+
88
+ it { should eql(expectation) }
89
+
90
+ it_should_behave_like 'an idempotent method'
91
+ end
92
+
93
+ context 'when there is a diff NOT at begin of hunk' do
94
+ let(:old) { %w[foo bar] }
95
+ let(:new) { %w[foo baz bar] }
96
+
97
+ let(:expectation) do
98
+ <<~STR
99
+ @@ -1,3 +1,4 @@
100
+ foo
101
+ +baz
102
+ bar
103
+ STR
104
+ end
105
+
106
+ it { should eql(expectation) }
107
+
108
+ it_should_behave_like 'an idempotent method'
109
+ end
110
+
111
+ context 'when the diff has a long context at begin' do
112
+ let(:old) { %w[foo bar baz boz a b c] }
113
+ let(:new) { %w[foo bar baz boz a b c other] }
114
+
115
+ let(:expectation) do
116
+ <<~STR
117
+ @@ -1,8 +1,9 @@
118
+ foo
119
+ bar
120
+ baz
121
+ boz
122
+ a
123
+ b
124
+ c
125
+ +other
126
+ STR
127
+ end
128
+
129
+ it { should eql(expectation) }
130
+
131
+ it_should_behave_like 'an idempotent method'
132
+ end
133
+
134
+ context 'when the diff has a long context at end, deleting' do
135
+ let(:old) { %w[other foo bar baz boz a b c] }
136
+ let(:new) { %w[foo bar baz boz a b c] }
137
+
138
+ let(:expectation) do
139
+ <<~STR
140
+ @@ -1,9 +1,8 @@
141
+ -other
142
+ foo
143
+ bar
144
+ baz
145
+ boz
146
+ a
147
+ b
148
+ c
149
+ STR
150
+ end
151
+
152
+ it { should eql(expectation) }
153
+
154
+ it_should_behave_like 'an idempotent method'
155
+ end
156
+
157
+ context 'when the diff has a long context at end, inserting' do
158
+ let(:old) { %w[foo bar baz boz a b c] }
159
+ let(:new) { %w[other foo bar baz boz a b c] }
160
+
161
+ let(:expectation) do
162
+ <<~STR
163
+ @@ -1,8 +1,9 @@
164
+ +other
165
+ foo
166
+ bar
167
+ baz
168
+ boz
169
+ a
170
+ b
171
+ c
172
+ STR
173
+ end
174
+
175
+ it { should eql(expectation) }
176
+
177
+ it_should_behave_like 'an idempotent method'
178
+ end
179
+
180
+ context 'when there is no diff' do
181
+ let(:old) { '' }
182
+ let(:new) { '' }
183
+
184
+ it { should be(nil) }
185
+
186
+ it_should_behave_like 'an idempotent method'
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,327 @@
1
+ require 'spec_helper'
2
+
3
+ describe Unparser::Validation do
4
+ let(:object) do
5
+ described_class.new(
6
+ identification: identification,
7
+ generated_node: generated_node,
8
+ generated_source: generated_source,
9
+ original_node: original_node,
10
+ original_source: original_source
11
+ )
12
+ end
13
+
14
+ def right(value)
15
+ MPrelude::Either::Right.new(value)
16
+ end
17
+
18
+ def left(value)
19
+ MPrelude::Either::Left.new(value)
20
+ end
21
+
22
+ let(:generated_node) { right(s(:send, s(:int, 1), :foo)) }
23
+ let(:generated_source) { right('1.foo') }
24
+ let(:identification) { 'example-identification' }
25
+ let(:original_node) { right(s(:send, s(:int, 1), :foo)) }
26
+ let(:original_source) { right('1.foo') }
27
+
28
+ let(:exception) do
29
+ left(
30
+ instance_double(
31
+ RuntimeError,
32
+ message: 'foo',
33
+ backtrace: Array.new(21, &'line-%02d'.method(:%))
34
+ )
35
+ )
36
+ end
37
+
38
+ let(:exception_report) do
39
+ <<~'REPORT'.strip
40
+ #<InstanceDouble(RuntimeError) (anonymous)>
41
+ line-00
42
+ line-01
43
+ line-02
44
+ line-03
45
+ line-04
46
+ line-05
47
+ line-06
48
+ line-07
49
+ line-08
50
+ line-09
51
+ line-10
52
+ line-11
53
+ line-12
54
+ line-13
55
+ line-14
56
+ line-15
57
+ line-16
58
+ line-17
59
+ line-18
60
+ line-19
61
+ REPORT
62
+ end
63
+
64
+ def report
65
+ object.report
66
+ end
67
+
68
+ shared_examples 'not successful' do
69
+ it 'is not successful' do
70
+ expect(object.success?).to be(false)
71
+ end
72
+ end
73
+
74
+ context 'on success' do
75
+ it 'is successful' do
76
+ expect(object.success?).to be(true)
77
+ end
78
+
79
+ it 'returns expected report' do
80
+ expect(report).to eql(<<~'REPORT'.strip)
81
+ example-identification
82
+ Original-Source:
83
+ 1.foo
84
+ Generated-Source:
85
+ 1.foo
86
+ Original-Node:
87
+ (send
88
+ (int 1) :foo)
89
+ Generated-Node:
90
+ (send
91
+ (int 1) :foo)
92
+ REPORT
93
+ end
94
+ end
95
+
96
+ context 'on failing to generate original source with exception' do
97
+ let(:original_source) { exception }
98
+
99
+ include_examples 'not successful'
100
+
101
+ it 'returns expected report' do
102
+ expect(report).to eql(<<~REPORT.strip)
103
+ example-identification
104
+ Original-Source:
105
+ #{exception_report}
106
+ Generated-Source:
107
+ 1.foo
108
+ Original-Node:
109
+ (send
110
+ (int 1) :foo)
111
+ Generated-Node:
112
+ (send
113
+ (int 1) :foo)
114
+ REPORT
115
+ end
116
+ end
117
+
118
+ context 'on failing to parse generated source due precondition error' do
119
+ let(:generated_node) { left(nil) }
120
+
121
+ include_examples 'not successful'
122
+
123
+ it 'returns expected report' do
124
+ expect(report).to eql(<<~REPORT.strip)
125
+ example-identification
126
+ Original-Source:
127
+ 1.foo
128
+ Generated-Source:
129
+ 1.foo
130
+ Original-Node:
131
+ (send
132
+ (int 1) :foo)
133
+ Generated-Node:
134
+ undefined
135
+ REPORT
136
+ end
137
+ end
138
+
139
+ context 'on failing to parse original source' do
140
+ let(:original_node) { exception }
141
+
142
+ include_examples 'not successful'
143
+
144
+ it 'returns expected report' do
145
+ expect(report).to eql(<<~REPORT.strip)
146
+ example-identification
147
+ Original-Source:
148
+ 1.foo
149
+ Generated-Source:
150
+ 1.foo
151
+ Original-Node:
152
+ #{exception_report}
153
+ Generated-Node:
154
+ (send
155
+ (int 1) :foo)
156
+ REPORT
157
+ end
158
+ end
159
+
160
+ context 'on failing to generate generated source' do
161
+ let(:generated_source) { exception }
162
+
163
+ include_examples 'not successful'
164
+
165
+ it 'returns expected report' do
166
+ expect(report).to eql(<<~REPORT.strip)
167
+ example-identification
168
+ Original-Source:
169
+ 1.foo
170
+ Generated-Source:
171
+ #{exception_report}
172
+ Original-Node:
173
+ (send
174
+ (int 1) :foo)
175
+ Generated-Node:
176
+ (send
177
+ (int 1) :foo)
178
+ REPORT
179
+ end
180
+ end
181
+
182
+ context 'on failing to parse generated source' do
183
+ let(:generated_node) { exception }
184
+
185
+ include_examples 'not successful'
186
+
187
+ it 'returns expected report' do
188
+ expect(report).to eql(<<~REPORT.strip)
189
+ example-identification
190
+ Original-Source:
191
+ 1.foo
192
+ Generated-Source:
193
+ 1.foo
194
+ Original-Node:
195
+ (send
196
+ (int 1) :foo)
197
+ Generated-Node:
198
+ #{exception_report}
199
+ REPORT
200
+ end
201
+ end
202
+
203
+ context 'on generating different node' do
204
+ let(:generated_node) { right(s(:send, s(:int, 1), :bar)) }
205
+
206
+ include_examples 'not successful'
207
+
208
+ it 'returns expected report' do
209
+ diff = [
210
+ Unparser::Color::NONE.format(" (send\n"),
211
+ Unparser::Color::RED.format("- (int 1) :foo)\n"),
212
+ Unparser::Color::GREEN.format("+ (int 1) :bar)\n")
213
+ ]
214
+
215
+ expect(report).to eql(<<~'REPORT' + diff.join)
216
+ example-identification
217
+ Original-Source:
218
+ 1.foo
219
+ Generated-Source:
220
+ 1.foo
221
+ Original-Node:
222
+ (send
223
+ (int 1) :foo)
224
+ Generated-Node:
225
+ (send
226
+ (int 1) :bar)
227
+ Node-Diff:
228
+ @@ -1,3 +1,3 @@
229
+ REPORT
230
+ end
231
+ end
232
+
233
+ describe '.from_path' do
234
+ def apply
235
+ described_class.from_path(path)
236
+ end
237
+
238
+ let(:path) { instance_double(Pathname, read: source, to_s: '/some/file') }
239
+ let(:source) { 'true' }
240
+
241
+ it 'returns expected validator' do
242
+ expect(apply).to eql(
243
+ described_class.new(
244
+ generated_node: right(s(:true)),
245
+ generated_source: right(source),
246
+ identification: '/some/file',
247
+ original_node: right(s(:true)),
248
+ original_source: right(source)
249
+ )
250
+ )
251
+ end
252
+ end
253
+
254
+ describe '.from_string' do
255
+ def apply
256
+ described_class.from_string(source)
257
+ end
258
+
259
+ let(:attributes) do
260
+ {
261
+ generated_node: right(s(:true)),
262
+ generated_source: right(source),
263
+ identification: '(string)',
264
+ original_node: right(s(:true)),
265
+ original_source: right(source)
266
+ }
267
+ end
268
+
269
+ context 'on valid original source' do
270
+ let(:source) { 'true' }
271
+
272
+ it 'returns expected validator' do
273
+ expect(apply).to eql(described_class.new(attributes))
274
+ end
275
+
276
+ context 'with unparsing error' do
277
+ let(:exception) { RuntimeError.new('example-error') }
278
+
279
+ before do
280
+ allow(Unparser).to receive(:unparse).and_raise(exception)
281
+ end
282
+
283
+ it 'returns expected validator' do
284
+ validator = apply
285
+
286
+ expect(validator.generated_node).to eql(left(nil))
287
+ expect(validator.generated_source.from_left.class).to be(RuntimeError)
288
+ expect(validator.original_source).to eql(right(source))
289
+ expect(validator.original_node).to eql(right(s(:true)))
290
+ end
291
+ end
292
+ end
293
+
294
+ # These are actually specifying the wrong behavior as the normalization is a conceptual mistake
295
+ # But for now we specify them to get this PR through.
296
+ #
297
+ # Removal in followup.
298
+ context 'on denormalized valid original source' do
299
+ let(:source) { '(true)' }
300
+
301
+ it 'returns expected validator' do
302
+ expect(apply).to eql(described_class.new(attributes.merge(generated_source: right('true'))))
303
+ end
304
+ end
305
+
306
+ context 'on very denormalized valid original source' do
307
+ let(:source) { '((true))' }
308
+
309
+ it 'returns expected validator' do
310
+ expect(apply).to eql(described_class.new(attributes.merge(generated_source: right('true'))))
311
+ end
312
+ end
313
+
314
+ context 'on invalid original source' do
315
+ let(:source) { '(' }
316
+
317
+ it 'returns expected validator' do
318
+ validator = apply
319
+
320
+ expect(validator.generated_node).to eql(left(nil))
321
+ expect(validator.generated_source).to eql(left(nil))
322
+ expect(validator.original_source).to eql(right(source))
323
+ expect(validator.original_node.from_left.class).to be(Parser::SyntaxError)
324
+ end
325
+ end
326
+ end
327
+ end