Skip to content

Commit 6d351d4

Browse files
omusoxinabox
andauthored
Introduce ScopedValue support in a breaking change (#129)
* Use `ScopedValue` to fix async issue (#112) * Use ContextVariablesX to fix async issue * Use ScopedValues rather than ContextVariables * Define missing PATCH_ENV * Refactor tasks testset into async scope * Import ScopedValue alongside other imports * Use VERSION check instead --------- Co-authored-by: Curtis Vogt <[email protected]> * Test against Julia 1.11 * Update async-world-ages tests --------- Co-authored-by: Frames White <[email protected]>
1 parent 6a0c9b9 commit 6d351d4

File tree

8 files changed

+66
-29
lines changed

8 files changed

+66
-29
lines changed

.github/workflows/CI.yml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
- "1.0" # Oldest supported version
2222
- "1.6" # LTS
2323
- "1" # Latest Release
24+
- "pre" # ScopedValue support introduced in Julia 1.11
2425
os:
2526
- ubuntu-latest
2627
arch:

src/Mocking.jl

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ module Mocking
33
using Compat: mergewith
44
using ExprTools: splitdef, combinedef
55

6+
# Available in Julia 1.11+: https://github.com/JuliaLang/julia/pull/50958
7+
# We cannot use ScopedValues.jl for backwards compatability as that implementation breaks
8+
# `@test_logs`.
9+
if VERSION >= v"1.11.0-DEV.482"
10+
using Base: ScopedValue, with
11+
end
12+
613
export @patch, @mock, Patch, apply
714

815
include("expr.jl")

src/mock.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ function get_alternate(pe::PatchEnv, target, args...)
7373
end
7474
end
7575

76-
get_alternate(target, args...) = get_alternate(get_active_env(), target, args...)
76+
get_alternate(target, args...) = get_alternate(PATCH_ENV[], target, args...)
7777

7878
function _debug_msg(method::Union{Method,Nothing}, target, args)
7979
call = "$target($(join(map(arg -> "::$(Core.Typeof(arg))", args), ", ")))"

src/patch.jl

+19-10
Original file line numberDiff line numberDiff line change
@@ -197,19 +197,28 @@ julia> apply(p1) do
197197
function apply end
198198

199199
function apply(body::Function, pe::PatchEnv)
200-
prev_pe = get_active_env()
201-
set_active_env(merge(prev_pe, pe))
202-
try
203-
return body()
204-
finally
205-
set_active_env(prev_pe)
206-
end
200+
merged_pe = merge(PATCH_ENV[], pe)
201+
return with_active_env(body, merged_pe)
207202
end
208203

209204
function apply(body::Function, patches; debug::Bool=false)
210205
return apply(body, PatchEnv(patches, debug))
211206
end
212207

213-
const PATCH_ENV = Ref{PatchEnv}(PatchEnv())
214-
set_active_env(pe::PatchEnv) = (PATCH_ENV[] = pe)
215-
get_active_env() = PATCH_ENV[]
208+
# https://github.com/JuliaLang/julia/pull/50958
209+
if VERSION >= v"1.11.0-DEV.482"
210+
const PATCH_ENV = ScopedValue(PatchEnv())
211+
with_active_env(body::Function, pe::PatchEnv) = with(body, PATCH_ENV => pe)
212+
else
213+
const PATCH_ENV = Ref{PatchEnv}(PatchEnv())
214+
215+
function with_active_env(body::Function, pe::PatchEnv)
216+
old_pe = PATCH_ENV[]
217+
try
218+
PATCH_ENV[] = pe
219+
body()
220+
finally
221+
PATCH_ENV[] = old_pe
222+
end
223+
end
224+
end

test/async.jl renamed to test/async-scope.jl

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
@testset "tasks" begin
1+
# Async tasks should consistently use the patch environment (if any) they started with.
2+
@testset "async scope" begin
23
c = Condition()
34
ch = Channel{String}(1)
45
f() = "original"
@@ -20,7 +21,12 @@
2021
@test (@mock f()) == "mocked"
2122

2223
notify(c)
23-
@test_broken take!(ch) == "original"
24+
# https://github.com/JuliaLang/julia/pull/50958
25+
if VERSION >= v"1.11.0-DEV.482"
26+
@test take!(ch) == "original"
27+
else
28+
@test_broken take!(ch) == "original"
29+
end
2430

2531
# Task started inside patched context should call patched functions.
2632
@async background()
@@ -35,6 +41,11 @@
3541
end
3642

3743
notify(c)
38-
@test_broken take!(ch) == "mocked"
44+
# https://github.com/JuliaLang/julia/pull/50958
45+
if VERSION >= v"1.11.0-DEV.482"
46+
@test take!(ch) == "mocked"
47+
else
48+
@test_broken take!(ch) == "mocked"
49+
end
3950
end
4051
end

test/async-world-ages.jl

+13-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ using Test
22
using Mocking
33

44
# Issue #108
5+
# TODO: Test is mostly redundant with "async-scope.jl". We may want to update those tests to
6+
# also validate we can run in a later world age.
57
@testset "patching an async task from an earlier world age" begin
68
function foo(x)
79
@mock bar(x)
@@ -17,9 +19,10 @@ using Mocking
1719
intial_world_age = Base.get_world_counter()
1820
end
1921

20-
# Start a background, async task which blocks until bar() is patched, so that we
21-
# can test that patches to functions defined in later world ages can be called
22-
# from mocks in a Task running in an earlier world age.
22+
# Start a background async task. For Julia 1.11+ this task will consistently use the
23+
# patch environment which it was started in. In earlier versions of Julia we can patch
24+
# this task while it's running can call functions defined in a later world age than the
25+
# world age of this task.
2326
ch = Channel() do ch
2427
# Wait until there is data in the channel
2528
v1 = take!(ch)
@@ -40,6 +43,12 @@ using Mocking
4043
# Release the background task
4144
put!(ch, 2)
4245
# Fetch the task's result
43-
@test take!(ch) == 20
46+
47+
# https://github.com/JuliaLang/julia/pull/50958
48+
if VERSION >= v"1.11.0-DEV.482"
49+
@test take!(ch) == 2
50+
else
51+
@test_broken take!(ch) == 2
52+
end
4453
end
4554
end

test/concept.jl

+10-10
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@
2020
for p in patches
2121
Mocking.apply!(pe, p)
2222
end
23-
Mocking.set_active_env(pe)
2423

25-
@test (@mock multiply(2)) == 8 # calls mocked `multiply(::Int)`
26-
@test (@mock multiply(0x2)) == 0x6 # calls mocked `multiply(::Integer)`
27-
@test (@mock multiply(2//1)) == 4//1 # calls original `multiply(::Number)`
24+
Mocking.apply(pe) do
25+
@test (@mock multiply(2)) == 8 # calls mocked `multiply(::Int)`
26+
@test (@mock multiply(0x2)) == 0x6 # calls mocked `multiply(::Integer)`
27+
@test (@mock multiply(2//1)) == 4//1 # calls original `multiply(::Number)`
2828

29-
@test (@mock multiply(2)) != multiply(2)
30-
@test (@mock multiply(0x2)) != multiply(0x2)
31-
@test (@mock multiply(2//1)) == multiply(2//1)
29+
@test (@mock multiply(2)) != multiply(2)
30+
@test (@mock multiply(0x2)) != multiply(0x2)
31+
@test (@mock multiply(2//1)) == multiply(2//1)
32+
end
3233

33-
# Clean env
34-
pe = Mocking.PatchEnv()
35-
Mocking.set_active_env(pe)
34+
# Patch environment has been reset back to original clean state
35+
@test Mocking.PATCH_ENV[] == Mocking.PatchEnv()
3636

3737
# Ensure that original behaviour is restored
3838
@test (@mock multiply(2)) == 3

test/runtests.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Mocking.activate()
3434
include("args.jl")
3535
include("merge.jl")
3636
include("nested_apply.jl")
37-
include("async.jl")
37+
include("async-scope.jl")
3838
include("issues.jl")
3939
include("activate.jl")
4040
include("async-world-ages.jl")

0 commit comments

Comments
 (0)