Skip to content

Commit 4572de4

Browse files
authored
Fix async behaviour with patch contexts (#91)
1 parent 9fd8321 commit 4572de4

File tree

7 files changed

+60
-21
lines changed

7 files changed

+60
-21
lines changed

Project.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ keywords = ["testing", "mocking"]
44
license = "MIT"
55
desc = "Allows Julia function calls to be temporarily overloaded for purpose of testing"
66
author = ["Curtis Vogt"]
7-
version = "0.7.3"
7+
version = "0.7.4"
88

99
[deps]
1010
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
11+
ContextVariablesX = "6add18c4-b38d-439d-96f6-d6bc489c04c5"
1112
ExprTools = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
1213

1314
[compat]
1415
Compat = "3.9"
16+
ContextVariablesX = "0.1.2"
1517
ExprTools = "0.1"
1618
julia = "1"
1719

src/Mocking.jl

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Mocking
22

33
using Compat: mergewith
4+
using ContextVariablesX: @contextvar, with_context
45
using ExprTools: splitdef, combinedef
56

67
export @patch, @mock, Patch, apply

src/mock.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function get_alternate(pe::PatchEnv, target, args...)
5252
end
5353
end
5454

55-
get_alternate(target, args...) = get_alternate(get_active_env(), target, args...)
55+
get_alternate(target, args...) = get_alternate(patch_env[], target, args...)
5656

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

src/patch.jl

+5-10
Original file line numberDiff line numberDiff line change
@@ -126,19 +126,14 @@ end
126126
```
127127
"""
128128
function apply(body::Function, pe::PatchEnv)
129-
prev_pe = get_active_env()
130-
set_active_env(merge(prev_pe, pe))
131-
try
132-
return body()
133-
finally
134-
set_active_env(prev_pe)
135-
end
129+
merged_pe = merge(patch_env[], pe)
130+
return with_context(body, patch_env => merged_pe)
136131
end
137132

138133
function apply(body::Function, patches; debug::Bool=false)
139134
return apply(body, PatchEnv(patches, debug))
140135
end
141136

142-
const PATCH_ENV = Ref{PatchEnv}(PatchEnv())
143-
set_active_env(pe::PatchEnv) = (PATCH_ENV[] = pe)
144-
get_active_env() = PATCH_ENV[]
137+
@contextvar patch_env = PatchEnv()
138+
set_active_env(body::Function, pe::PatchEnv) = with_context(body, patch_env => pe)
139+
get_active_env() = patch_env[]

test/async.jl

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
@testset "tasks" begin
2+
c = Condition()
3+
ch = Channel{String}(1)
4+
f() = "original"
5+
function background()
6+
# Wait until notified allowing us to control when this async code is executed
7+
wait(c)
8+
put!(ch, @mock f())
9+
return nothing
10+
end
11+
12+
p = @patch f() = "mocked"
13+
14+
@sync begin
15+
# Task started outside patched context should not call patched functions.
16+
@async background()
17+
yield()
18+
19+
apply(p) do
20+
@test (@mock f()) == "mocked"
21+
22+
notify(c)
23+
@test take!(ch) == "original"
24+
25+
# Task started inside patched context should call patched functions.
26+
@async background()
27+
yield()
28+
notify(c)
29+
@test take!(ch) == "mocked"
30+
31+
# Task started inside patched context should call patched functions even when
32+
# execution finishes outside of patched context.
33+
@async background()
34+
yield()
35+
end
36+
37+
notify(c)
38+
@test take!(ch) == "mocked"
39+
end
40+
end

test/concept.jl

+9-9
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.set_active_env(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

3334
# Clean env
34-
pe = Mocking.PatchEnv()
35-
Mocking.set_active_env(pe)
35+
@test Mocking.get_active_env() == Mocking.PatchEnv()
3636

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

test/runtests.jl

+1
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ using Mocking: anon_morespecific, anonymous_signature, dispatch, type_morespecif
2828
include("args.jl")
2929
include("merge.jl")
3030
include("nested_apply.jl")
31+
include("async.jl")
3132
end

0 commit comments

Comments
 (0)