Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,19 @@ macro test_trixi_include_base(elixir, args...)
# on Julia >= 1.12, @isdefined returns false because bindings set inside
# Base.include have a newer world age; on older Julia the value is visible and
# also correct (same as the elixir default).
# For non-Symbol expressions (closures, calls, literals), always evaluate at call site.
# For non-Symbol expressions, there are two cases:
# 4. Literals (numbers, strings, ...): the value is the same regardless of the
# scope, so we can simply pass it on.
# 5. Compound expressions (calls, tuples, array/closure literals, e.g.
# `surface_flux=FluxLaxFriedrichs(max_abs_speed)`): these typically reference
# names that are only available *inside* the elixir's scope (e.g. brought in by
# the elixir's own `using Trixi`) and are not defined at the macro call site
# (the testset module). Hence, we must NOT evaluate them at the call site but
# pass the unevaluated expression through to `trixi_include`, which splices it
# into the elixir and evaluates it in the elixir's scope.
# We achieve both of the above by passing the (quoted) expression on unevaluated via
# a `QuoteNode`, which reproduces the behavior before locally-defined Symbol values
# were supported.
local kwarg_keys = Set(arg.args[1]
for arg in args
if arg.head == :(=) &&
Expand All @@ -107,7 +119,9 @@ macro test_trixi_include_base(elixir, args...)
Expr(:kw, key,
esc(:((@isdefined $val) ? $val : $(QuoteNode(val))))))
else
push!(kwarg_exprs, Expr(:kw, key, esc(val)))
# Cases 4 & 5: pass the unevaluated expression through to
# `trixi_include` for resolution in the elixir's scope
push!(kwarg_exprs, Expr(:kw, key, QuoteNode(val)))
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
[deps]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TrixiBase = "9a0f1c46-06d5-4909-a5a3-ce25d3fa3284"

[compat]
Aqua = "0.8"
LinearAlgebra = "1"
Test = "1"
TrixiBase = "0.1.6"
61 changes: 61 additions & 0 deletions test/test_test_trixi_include.jl
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,67 @@ end
end
end

@trixi_testset "compound (non-Symbol) override values" begin
# Regression test for a bug where compound kwarg values (e.g. function
# calls) were evaluated in the testset module from which the macro is
# called instead of in the elixir's scope. This broke values referencing
# names that are only available *inside* the elixir, such as
# `surface_flux=FluxLaxFriedrichs(max_abs_speed)` in Trixi.jl, where
# `FluxLaxFriedrichs` and `max_abs_speed` come from the elixir's own
# `using Trixi` and are not defined in the (inner) testset module.
#
# We mimic this here with `norm` from `LinearAlgebra`: the elixir brings
# it in via its own `using LinearAlgebra`, while the testset module from
# which the macros are called below does *not* have `LinearAlgebra` (and
# `norm` is therefore not defined there).
example = """
using LinearAlgebra
x = norm([3.0, 4.0])
t = (1, 2)
s = "default"
"""

mktemp() do path, io
write(io, example)
close(io)
mod = @__MODULE__

# `norm` is intentionally not available in this testset module, so
# evaluating the override value here (instead of in the elixir's
# scope) would throw an `UndefVarError`.
@test !(@invokelatest isdefined(mod, :norm))

# Compound call expression referencing an elixir-internal name
@test_trixi_include_base(path, x=norm([6.0, 8.0]))
@test (@invokelatest mod.x) ≈ 10.0

@test_trixi_include(path, x=norm([6.0, 8.0]))
@test (@invokelatest mod.x) ≈ 10.0

# Tuple expression
@test_trixi_include_base(path, t=(3, 4))
@test (@invokelatest mod.t) == (3, 4)

@test_trixi_include(path, t=(3, 4))
@test (@invokelatest mod.t) == (3, 4)

# String literal
@test_trixi_include_base(path, s="override")
@test (@invokelatest mod.s) == "override"

# Numeric literal still works through the same code path
@test_trixi_include_base(path, x=7)
@test (@invokelatest mod.x) == 7

# Combining a compound override with a chained Symbol override:
# `t=(x, x)` must be resolved in the elixir's scope *after* the
# `x` override has been applied there.
@test_trixi_include_base(path, x=norm([5.0, 12.0]), t=(x, x))
@test (@invokelatest mod.x) ≈ 13.0
@test all((@invokelatest mod.t) .≈ (13.0, 13.0))
end
end

@trixi_testset "additional_ignore_content" begin
example = """
@warn "Test warning"
Expand Down
Loading