Skip to content

Commit 4c3494f

Browse files
committed
Test infrastructure for limiting output to one method
When testing the IR generated by closures or complex function desugaring there can be a lot of methods generated and it can be hard to review changes. Add a `method_filter` to test cases to limit the output to a method of interest.
1 parent 003448a commit 4c3494f

File tree

3 files changed

+87
-83
lines changed

3 files changed

+87
-83
lines changed

src/utils.jl

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,46 @@ function subscript_str(i)
7575
"5"=>"", "6"=>"", "7"=>"", "8"=>"", "9"=>"")
7676
end
7777

78-
function print_ir(io::IO, ex, indent="")
78+
function _deref_ssa(stmts, ex)
79+
while kind(ex) == K"SSAValue"
80+
ex = stmts[ex.var_id]
81+
end
82+
ex
83+
end
84+
85+
function _find_method_lambda(ex, name)
86+
@assert kind(ex) == K"code_info"
87+
# Heuristic search through outer thunk for the method in question.
88+
method_found = false
89+
stmts = children(ex[1])
90+
for e in stmts
91+
if kind(e) == K"method" && numchildren(e) >= 2
92+
sig = _deref_ssa(stmts, e[2])
93+
@assert kind(sig) == K"call"
94+
arg_types = _deref_ssa(stmts, sig[2])
95+
@assert kind(arg_types) == K"call"
96+
self_type = _deref_ssa(stmts, arg_types[2])
97+
if kind(self_type) == K"globalref" && occursin(name, self_type.name_val)
98+
return e[3]
99+
end
100+
end
101+
end
102+
end
103+
104+
function print_ir(io::IO, ex, method_filter=nothing)
105+
@assert kind(ex) == K"code_info"
106+
if !isnothing(method_filter)
107+
filtered = _find_method_lambda(ex, method_filter)
108+
if isnothing(filtered)
109+
@warn "Method not found with method filter $method_filter"
110+
else
111+
ex = filtered
112+
end
113+
end
114+
_print_ir(io, ex, "")
115+
end
116+
117+
function _print_ir(io::IO, ex, indent)
79118
added_indent = " "
80119
@assert (kind(ex) == K"lambda" || kind(ex) == K"code_info") && kind(ex[1]) == K"block"
81120
if !ex.is_toplevel_thunk && kind(ex) == K"code_info"
@@ -105,7 +144,7 @@ function print_ir(io::IO, ex, indent="")
105144
print(io, indent, lno, " --- method ", string(e[1]), " ", string(e[2]))
106145
if kind(e[3]) == K"lambda" || kind(e[3]) == K"code_info"
107146
println(io)
108-
print_ir(io, e[3], indent*added_indent)
147+
_print_ir(io, e[3], indent*added_indent)
109148
else
110149
println(io, " ", string(e[3]))
111150
end
@@ -116,10 +155,10 @@ function print_ir(io::IO, ex, indent="")
116155
print(io, " ", e[i])
117156
end
118157
println(io)
119-
print_ir(io, e[5], indent*added_indent)
158+
_print_ir(io, e[5], indent*added_indent)
120159
elseif kind(e) == K"code_info"
121160
println(io, indent, lno, " --- ", e.is_toplevel_thunk ? "thunk" : "code_info")
122-
print_ir(io, e, indent*added_indent)
161+
_print_ir(io, e, indent*added_indent)
123162
else
124163
code = string(e)
125164
println(io, indent, lno, " ", code)

test/closures_ir.jl

Lines changed: 15 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -287,66 +287,26 @@ end
287287
########################################
288288
# Nested captures - here `g` captures `x` because it is needed to initialize
289289
# the closure `h` which captures both `x` and `y`.
290-
function f(x)
291-
function g(y)
292-
function h(z)
290+
# [method_filter: #f_nest#g_nest##0]
291+
function f_nest(x)
292+
function g_nest(y)
293+
function h_nest(z)
293294
(x,y,z)
294295
end
295296
end
296297
end
297298
#---------------------
298-
1 (method TestMod.f)
299-
2 (call core.svec :x)
300-
3 (call core.svec false)
301-
4 (call JuliaLowering.eval_closure_type TestMod :#f#g##4 %₂ %₃)
302-
5 (call core.svec :x :y)
303-
6 (call core.svec false false)
304-
7 (call JuliaLowering.eval_closure_type TestMod :#f#g#h##0 %₅ %₆)
305-
8 TestMod.#f#g#h##0
306-
9 (call core.svec %₈ core.Any)
307-
10 (call core.svec)
308-
11 SourceLocation::3:18
309-
12 (call core.svec %%₁₀ %₁₁)
310-
13 --- method core.nothing %₁₂
311-
slots: [slot₁/#self#(!read) slot₂/z]
312-
1 (call core.getfield slot₁/#self# :x)
313-
2 (call core.getfield slot₁/#self# :y)
314-
3 (call core.tuple %%₂ slot₂/z)
315-
4 (return %₃)
316-
14 TestMod.#f#g##4
317-
15 (call core.svec %₁₄ core.Any)
318-
16 (call core.svec)
319-
17 SourceLocation::2:14
320-
18 (call core.svec %₁₅ %₁₆ %₁₇)
321-
19 --- method core.nothing %₁₈
322-
slots: [slot₁/#self#(!read) slot₂/y(!read) slot₃/h]
323-
1 TestMod.#f#g#h##0
324-
2 (call core.getfield slot₁/#self# :x)
325-
3 (call core.typeof %₂)
326-
4 (call core.typeof slot₂/y)
327-
5 (call core.apply_type %%%₄)
328-
6 (call core.getfield slot₁/#self# :x)
329-
7 (new %%₆ slot₂/y)
330-
8 (= slot₃/h %₇)
331-
9 slot₃/h
332-
10 (return %₉)
333-
20 TestMod.f
334-
21 (call core.Typeof %₂₀)
335-
22 (call core.svec %₂₁ core.Any)
336-
23 (call core.svec)
337-
24 SourceLocation::1:10
338-
25 (call core.svec %₂₂ %₂₃ %₂₄)
339-
26 --- method core.nothing %₂₅
340-
slots: [slot₁/#self#(!read) slot₂/x(!read) slot₃/g]
341-
1 TestMod.#f#g##4
342-
2 (call core.typeof slot₂/x)
343-
3 (call core.apply_type %%₂)
344-
4 (new %₃ slot₂/x)
345-
5 (= slot₃/g %₄)
346-
6 slot₃/g
347-
7 (return %₆)
348-
27 TestMod.f
349-
28 (return %₂₇)
299+
slots: [slot₁/#self#(!read) slot₂/y(!read) slot₃/h_nest]
300+
1 TestMod.#f_nest#g_nest#h_nest##0
301+
2 (call core.getfield slot₁/#self# :x)
302+
3 (call core.typeof %₂)
303+
4 (call core.typeof slot₂/y)
304+
5 (call core.apply_type %%%₄)
305+
6 (call core.getfield slot₁/#self# :x)
306+
7 (new %%₆ slot₂/y)
307+
8 (= slot₃/h_nest %₇)
308+
9 slot₃/h_nest
309+
10 (return %₉)
350310

351311
########################################
352312
# Global method capturing local variables

test/utils.jl

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,13 @@ function match_ir_test_case(case_str)
126126
error("Too many sections in IR test case")
127127
expect_error = startswith(description, "Error")
128128
is_broken = startswith(description, "FIXME")
129+
method_filter = begin
130+
mf = match(r"\[method_filter: *(.*)\]", description)
131+
isnothing(mf) ? nothing : strip(mf[1])
132+
end
129133
(; expect_error=expect_error, is_broken=is_broken,
130134
description=strip(description),
135+
method_filter=method_filter,
131136
input=strip(input), output=strip(output))
132137
end
133138

@@ -152,49 +157,49 @@ function setup_ir_test_module(preamble)
152157
test_mod
153158
end
154159

155-
function format_ir_for_test(mod, description, input, expect_error=false, is_broken=false)
156-
ex = parsestmt(SyntaxTree, input)
160+
function format_ir_for_test(mod, case)
161+
ex = parsestmt(SyntaxTree, case.input)
157162
try
158163
if kind(ex) == K"macrocall" && kind(ex[1]) == K"MacroName" && ex[1].name_val == "@ast_"
159164
# Total hack, until @ast_ can be implemented in terms of new-style
160165
# macros.
161166
ex = JuliaLowering.eval(mod, Expr(ex))
162167
end
163168
x = JuliaLowering.lower(mod, ex)
164-
if expect_error
165-
error("Expected a lowering error in test case \"$description\"")
169+
if case.expect_error
170+
error("Expected a lowering error in test case \"$(case.description)\"")
166171
end
167-
ir = strip(sprint(JuliaLowering.print_ir, x))
172+
ir = strip(sprint(JuliaLowering.print_ir, x, case.method_filter))
168173
return replace(ir, string(mod)=>"TestMod")
169174
catch exc
170175
if exc isa InterruptException
171176
rethrow()
172-
elseif expect_error && (exc isa LoweringError)
177+
elseif case.expect_error && (exc isa LoweringError)
173178
return sprint(io->Base.showerror(io, exc, show_detail=false))
174-
elseif expect_error && (exc isa MacroExpansionError)
179+
elseif case.expect_error && (exc isa MacroExpansionError)
175180
return sprint(io->Base.showerror(io, exc))
176-
elseif is_broken
181+
elseif case.is_broken
177182
return sprint(io->Base.showerror(io, exc))
178183
else
179-
throw("Error in test case \"$description\"")
184+
throw("Error in test case \"$(case.description)\"")
180185
end
181186
end
182187
end
183188

184189
function test_ir_cases(filename::AbstractString)
185190
preamble, cases = read_ir_test_cases(filename)
186191
test_mod = setup_ir_test_module(preamble)
187-
for (expect_error, is_broken, description, input, ref) in cases
188-
if is_broken
192+
for case in cases
193+
if case.is_broken
189194
continue
190195
end
191-
output = format_ir_for_test(test_mod, description, input, expect_error)
192-
@testset "$description" begin
193-
if output != ref
196+
output = format_ir_for_test(test_mod, case)
197+
@testset "$(case.description)" begin
198+
if output != case.output
194199
# Do additional error dumping, as @test will not format errors in a nice way
195-
@error "Test \"$description\" failed" output=Text(output) ref=Text(ref)
200+
@error "Test \"$(case.description)\" failed" output=Text(output) ref=Text(case.output)
196201
end
197-
@test output == ref
202+
@test output == case.output
198203
end
199204
end
200205
end
@@ -213,20 +218,20 @@ function refresh_ir_test_cases(filename, pattern=nothing)
213218
println(io, preamble, "\n")
214219
println(io, "#*******************************************************************************")
215220
end
216-
for (expect_error, is_broken, description, input, ref) in cases
217-
if isnothing(pattern) || occursin(pattern, description)
218-
ir = format_ir_for_test(test_mod, description, input, expect_error, is_broken)
219-
if rstrip(ir) != ref
220-
@info "Refreshing test case $(repr(description)) in $filename"
221+
for case in cases
222+
if isnothing(pattern) || occursin(pattern, case.description)
223+
ir = format_ir_for_test(test_mod, case)
224+
if rstrip(ir) != case.output
225+
@info "Refreshing test case $(repr(case.description)) in $filename"
221226
end
222227
else
223-
ir = ref
228+
ir = case.output
224229
end
225230
println(io,
226231
"""
227232
########################################
228-
$(comment_description(description))
229-
$(strip(input))
233+
$(comment_description(case.description))
234+
$(strip(case.input))
230235
#---------------------
231236
$ir
232237
"""

0 commit comments

Comments
 (0)