Skip to content

Commit 0fc24ef

Browse files
authored
Replace bacon with rspec (#41)
* Replace bacon with rspec * Run `rake spec` in CI test run
1 parent d1d0c84 commit 0fc24ef

File tree

6 files changed

+359
-313
lines changed

6 files changed

+359
-313
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ jobs:
2727
- '3.3'
2828
- jruby-9.2
2929
- jruby-9.4
30-
command: ["bundle exec rake test"]
30+
command: ["bundle exec rake spec"]
3131
include:
3232
- ruby: "head"
33-
command: "bundle exec rake test || true"
33+
command: "bundle exec rake spec || true"
3434
- ruby: "truffleruby"
35-
command: "bundle exec rake test || true"
35+
command: "bundle exec rake spec || true"
3636
steps:
3737
- name: checkout
3838
uses: actions/checkout@v4

Rakefile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
require 'bundler/gem_tasks'
22
require 'bundler/setup'
3+
require 'rspec/core'
4+
require 'rspec/core/rake_task'
35

4-
task :default => :test
6+
task :default => :spec
57

68
desc "Run test suite"
7-
task :test do
8-
sh "bacon -Itest -a"
9+
RSpec::Core::RakeTask.new(:spec) do |t|
10+
t.verbose = true
911
end
1012

1113
PAGES_REPO = '[email protected]:whitequark/ast'

ast.gemspec

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ Gem::Specification.new do |s|
1111
s.files = %w{LICENSE.MIT README.YARD.md} + Dir.glob("lib/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) }
1212
s.require_paths = ["lib"]
1313

14-
s.add_development_dependency 'rake', '~> 12.3'
14+
s.add_development_dependency 'rake', '~> 13.2'
1515

16-
s.add_development_dependency 'bacon', '~> 1.2'
17-
s.add_development_dependency 'bacon-colored_output'
16+
s.add_development_dependency 'rspec', '~> 3.13'
1817
s.add_development_dependency 'simplecov'
1918

2019
s.add_development_dependency 'coveralls', '~> 0.8.23'

spec/ast_spec.rb

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
require 'helper'
2+
3+
RSpec.describe AST::Node do
4+
extend AST::Sexp
5+
6+
class MetaNode < AST::Node
7+
attr_reader :meta
8+
end
9+
10+
class SubclassNode < AST::Node
11+
def initialize(*)
12+
super
13+
nil
14+
end
15+
end
16+
17+
before do
18+
@node = AST::Node.new(:node, [ 0, 1 ])
19+
@metanode = MetaNode.new(:node, [ 0, 1 ], :meta => 'value')
20+
@subclass_node = SubclassNode.new(:node, [ 0, 1 ])
21+
end
22+
23+
it 'should have accessors for type and children' do
24+
expect(@node.type).to eq :node
25+
expect(@node.children).to eq [0, 1]
26+
end
27+
28+
it 'should set metadata' do
29+
expect(@metanode.meta).to eq 'value'
30+
end
31+
32+
it 'should be frozen' do
33+
expect(@node.frozen?).to be true
34+
expect(@node.children.frozen?).to be true
35+
end
36+
37+
it 'should return self when duping' do
38+
expect(@node.dup).to be @node
39+
end
40+
41+
it 'should return self when cloning' do
42+
expect(@node.clone).to be @node
43+
end
44+
45+
it 'should return an updated node, but only if needed' do
46+
expect(@node.updated()).to be @node
47+
expect(@node.updated(:node)).to be @node
48+
expect(@node.updated(nil, [0, 1])).to be @node
49+
50+
updated = @node.updated(:other_node)
51+
expect(updated).to_not be @node
52+
expect(updated.type).to eq :other_node
53+
expect(updated.children).to eq @node.children
54+
55+
expect(updated.frozen?).to eq true
56+
57+
updated = @node.updated(nil, [1, 1])
58+
expect(updated).to_not be @node
59+
expect(updated.type).to eq @node.type
60+
expect(updated.children).to eq [1, 1]
61+
62+
updated = @metanode.updated(nil, nil, :meta => 'other_value')
63+
expect(updated.meta).to eq 'other_value'
64+
end
65+
66+
it 'returns updated node for subclasses that override constructor' do
67+
updated = @subclass_node.updated(nil, [2])
68+
expect(updated.type).to eq :node
69+
expect(updated.children).to eq [2]
70+
end
71+
72+
it 'should format to_sexp correctly' do
73+
a = AST::Node.new(:a, [ :sym, [ 1, 2 ] ]).to_sexp
74+
expect(a).to eq '(a :sym [1, 2])'
75+
b = AST::Node.new(:a, [ :sym, @node ]).to_sexp
76+
expect(b).to eq "(a :sym\n (node 0 1))"
77+
c = AST::Node.new(:a, [ :sym,
78+
AST::Node.new(:b, [ @node, @node ])
79+
]).to_sexp
80+
expect(c).to eq "(a :sym\n (b\n (node 0 1)\n (node 0 1)))"
81+
end
82+
83+
it 'should format to_s correctly' do
84+
a = AST::Node.new(:a, [ :sym, [ 1, 2 ] ]).to_s
85+
expect(a).to eq '(a :sym [1, 2])'
86+
b = AST::Node.new(:a, [ :sym, @node ]).to_s
87+
expect(b).to eq "(a :sym\n (node 0 1))"
88+
c = AST::Node.new(:a, [ :sym,
89+
AST::Node.new(:b, [ @node, @node ])
90+
]).to_s
91+
expect(c).to eq "(a :sym\n (b\n (node 0 1)\n (node 0 1)))"
92+
end
93+
94+
it 'should format inspect correctly' do
95+
a = AST::Node.new(:a, [ :sym, [ 1, 2 ] ]).inspect
96+
expect(a).to eq "s(:a, :sym, [1, 2])"
97+
b = AST::Node.new(:a, [ :sym,
98+
AST::Node.new(:b, [ @node, @node ])
99+
]).inspect
100+
expect(b).to eq "s(:a, :sym,\n s(:b,\n s(:node, 0, 1),\n s(:node, 0, 1)))"
101+
end
102+
103+
context do
104+
simple_node = AST::Node.new(:a, [ :sym, [ 1, 2 ] ])
105+
a = eval(simple_node.inspect)
106+
107+
it 'should recreate inspect output' do
108+
expect(a).to eq simple_node
109+
end
110+
end
111+
112+
context do
113+
complex_node = s(:a , :sym, s(:b, s(:node, 0, 1), s(:node, 0, 1)))
114+
b = eval(complex_node.inspect)
115+
116+
it 'should recreate inspect output' do
117+
expect(b).to eq complex_node
118+
end
119+
end
120+
121+
it 'should return self in to_ast' do
122+
expect(@node.to_ast).to be @node
123+
end
124+
125+
it 'should produce to_sexp_array correctly' do
126+
a = AST::Node.new(:a, [ :sym, [ 1, 2 ] ]).to_sexp_array
127+
expect(a).to eq [:a, :sym, [1, 2]]
128+
b = AST::Node.new(:a, [ :sym,
129+
AST::Node.new(:b, [ @node, @node ])
130+
]).to_sexp_array
131+
expect(b).to eq [:a, :sym, [:b, [:node, 0, 1], [:node, 0, 1]]]
132+
end
133+
134+
it 'should only use type and children to compute #hash' do
135+
expect(@node.hash).to eq([@node.type, @node.children, @node.class].hash)
136+
end
137+
138+
it 'should only use type and children in #eql? comparisons' do
139+
# Not identical but equivalent
140+
expect(@node.eql?(AST::Node.new(:node, [0, 1]))).to eq true
141+
# Not identical and not equivalent
142+
expect(@node.eql?(AST::Node.new(:other, [0, 1]))).to eq false
143+
# Not identical and not equivalent because of differend class
144+
expect(@node.eql?(@metanode)).to eq false
145+
end
146+
147+
it 'should only use type and children in #== comparisons' do
148+
expect(@node).to eq @node
149+
expect(@node).to eq @metanode
150+
expect(@node).to_not eq :foo
151+
152+
mock_node = Object.new.tap do |obj|
153+
def obj.to_ast
154+
self
155+
end
156+
157+
def obj.type
158+
:node
159+
end
160+
161+
def obj.children
162+
[ 0, 1 ]
163+
end
164+
end
165+
expect(@node).to eq mock_node
166+
end
167+
168+
context do
169+
node = s(:gasgn, :$foo, s(:integer, 1))
170+
var_name, value = *node
171+
expected = s(:integer, 1)
172+
173+
it 'should allow to decompose nodes with a, b = *node' do
174+
expect(var_name).to eq :$foo
175+
expect(value).to eq expected
176+
end
177+
end
178+
179+
context do
180+
node = s(:gasgn, :$foo)
181+
array = [s(:integer, 1)]
182+
expected = s(:gasgn, :$foo, s(:integer, 1))
183+
184+
it 'should concatenate with arrays' do
185+
expect(node + array).to eq expected
186+
end
187+
end
188+
189+
context do
190+
node = s(:array)
191+
a = s(:integer, 1)
192+
b = s(:string, "foo")
193+
expected = s(:array, s(:integer, 1), s(:string, "foo"))
194+
195+
it 'should append elements' do
196+
expect(node << a << b).to eq expected
197+
end
198+
end
199+
200+
begin
201+
eval <<-CODE
202+
context do
203+
bar = [ s(:bar, 1) ]
204+
baz = s(:baz, 2)
205+
value = s(:foo, *bar, baz)
206+
expected = s(:foo, s(:bar, 1), s(:baz, 2))
207+
208+
it 'should not trigger a rubinius bug' do
209+
expect(value).to eq expected
210+
end
211+
end
212+
CODE
213+
rescue SyntaxError
214+
# Running on 1.8, ignore.
215+
end
216+
217+
begin
218+
eval <<-CODE
219+
context do
220+
baz = s(:baz, s(:bar, 1), 2)
221+
222+
it 'should be matchable' do
223+
r = case baz
224+
in [:baz, [:bar, val], Integer] then val
225+
else
226+
:no_match
227+
end
228+
expect(r).to eq 1
229+
end
230+
end
231+
CODE
232+
rescue SyntaxError
233+
# Running on < 2.7, ignore.
234+
end
235+
end
236+
237+
describe AST::Processor do
238+
extend AST::Sexp
239+
240+
def have_sexp(text)
241+
text = text.lines.map { |line| line.sub /^ +\|(.+)/, '\1' }.join.rstrip
242+
lambda { |ast| ast.to_sexp == text }
243+
end
244+
245+
class MockProcessor < AST::Processor
246+
attr_reader :counts
247+
248+
def initialize
249+
@counts = Hash.new(0)
250+
end
251+
252+
def on_root(node)
253+
count_node(node)
254+
node.updated(nil, process_all(node.children))
255+
end
256+
alias on_body on_root
257+
258+
def on_def(node)
259+
count_node(node)
260+
name, arglist, body = node.children
261+
node.updated(:def, [ name, process(arglist), process(body) ])
262+
end
263+
264+
def handler_missing(node)
265+
count_node(node)
266+
end
267+
268+
def count_node(node)
269+
@counts[node.type] += 1; nil
270+
end
271+
end
272+
273+
before do
274+
@ast = AST::Node.new(:root, [
275+
AST::Node.new(:def, [ :func,
276+
AST::Node.new(:arglist, [ :foo, :bar ]),
277+
AST::Node.new(:body, [
278+
AST::Node.new(:invoke, [ :puts, "Hello world" ])
279+
])
280+
]),
281+
AST::Node.new(:invoke, [ :func ])
282+
])
283+
284+
@processor = MockProcessor.new
285+
end
286+
287+
it 'should visit every node' do
288+
expect(@processor.process(@ast)).to eq @ast
289+
expect(@processor.counts).to eq({
290+
:root => 1,
291+
:def => 1,
292+
:arglist => 1,
293+
:body => 1,
294+
:invoke => 2,
295+
})
296+
end
297+
298+
it 'should be able to replace inner nodes' do
299+
def @processor.on_arglist(node)
300+
node.updated(:new_fancy_arglist)
301+
end
302+
303+
expect(have_sexp(<<-SEXP).call(@processor.process(@ast))).to be true
304+
|(root
305+
| (def :func
306+
| (new-fancy-arglist :foo :bar)
307+
| (body
308+
| (invoke :puts "Hello world")))
309+
| (invoke :func))
310+
SEXP
311+
end
312+
313+
context do
314+
a = s(:add,
315+
s(:integer, 1),
316+
s(:multiply,
317+
s(:integer, 2),
318+
s(:integer, 3)))
319+
it 'should build sexps' do
320+
expect(have_sexp(<<-SEXP).call(a)).to be true
321+
|(add
322+
| (integer 1)
323+
| (multiply
324+
| (integer 2)
325+
| (integer 3)))
326+
SEXP
327+
end
328+
end
329+
330+
it 'should return nil if passed nil' do
331+
expect(@processor.process(nil)).to eq nil
332+
end
333+
334+
it 'should refuse to process non-nodes' do
335+
expect { @processor.process([]) }.to raise_error NoMethodError, %r|to_ast|
336+
end
337+
338+
context do
339+
value = s(:foo, s(:bar), s(:integer, 1))
340+
341+
it 'should allow to visit nodes with process_all(node)' do
342+
@processor.process_all value
343+
expect(@processor.counts).to eq({
344+
:bar => 1,
345+
:integer => 1,
346+
})
347+
end
348+
end
349+
end

0 commit comments

Comments
 (0)