Skip to content

Commit

Permalink
Merge pull request #90 from invenia/tg/nested_apply
Browse files Browse the repository at this point in the history
Add support for nested calls to apply
  • Loading branch information
tpgillam authored Oct 4, 2021
2 parents a1a98dc + 4fe41c2 commit 9fd8321
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 2 deletions.
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ keywords = ["testing", "mocking"]
license = "MIT"
desc = "Allows Julia function calls to be temporarily overloaded for purpose of testing"
author = ["Curtis Vogt"]
version = "0.7.2"
version = "0.7.3"

[deps]
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
ExprTools = "e2ba6199-217a-4e67-a87a-7c52f15ade04"

[compat]
Compat = "3.9"
ExprTools = "0.1"
julia = "1"

Expand Down
1 change: 1 addition & 0 deletions src/Mocking.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Mocking

using Compat: mergewith
using ExprTools: splitdef, combinedef

export @patch, @mock, Patch, apply
Expand Down
70 changes: 69 additions & 1 deletion src/patch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,36 @@ end

PatchEnv(debug::Bool=false) = PatchEnv(Dict{Any,Vector{Function}}(), debug)

function Base.:(==)(pe1::PatchEnv, pe2::PatchEnv)
return pe1.mapping == pe2.mapping && pe1.debug == pe2.debug
end

"""
merge(pe1::PatchEnv, pe2::PatchEnv) -> PatchEnv
Merge the two `PatchEnv` instances.
This is done in such a way that the following always holds:
```
patches_1 = Patch[...]
patches_2 = Patch[...]
patches = vcat(patches_1, patches_2)
pe1 = PatchEnv(patches_1)
pe2 = PatchEnv(patches_2)
pe = PatchEnv(patches)
@assert pe == merge(pe1, pe2)
```
The `debug` flag will be set to true if either `pe1` or `pe2` have it set to true.
"""
function Base.merge(pe1::PatchEnv, pe2::PatchEnv)
mapping = mergewith(vcat, pe1.mapping, pe2.mapping)
return PatchEnv(mapping, pe1.debug || pe2.debug)
end

function apply!(pe::PatchEnv, p::Patch)
alternate_funcs = get!(Vector{Function}, pe.mapping, p.target)
push!(alternate_funcs, p.alternate)
Expand All @@ -55,11 +85,49 @@ function apply!(pe::PatchEnv, patches)
for p in patches
apply!(pe, p)
end
return pe
end

"""
apply(body::Function, patches; debug::Bool=false)
apply(body::Function, pe::PatchEnv)
Convenience function to run `body` in the context of the given `patches`.
This is intended to be used with do-block notation, e.g.:
```
patch = @patch ...
apply(patch) do
...
end
```
## Nesting
Note that calls to apply will nest the patches that are applied. If multiple patches
are made to the same method, the innermost patch takes precedence.
The following two examples are equivalent:
```
patch_2 = @patch ...
apply([patch, patch_2]) do
...
end
```
```
apply(patch) do
apply(patch_2) do
...
end
end
```
"""
function apply(body::Function, pe::PatchEnv)
prev_pe = get_active_env()
set_active_env(pe)
set_active_env(merge(prev_pe, pe))
try
return body()
finally
Expand Down
28 changes: 28 additions & 0 deletions test/merge.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@testset "merge PatchEnv instances" begin
multiply(x::Number) = 2x
multiply(x::Int) = 2x - 1
add(x::Number) = x + 2
add(x::Int) = x + 1

patches = Patch[
@patch multiply(x::Integer) = 3x
@patch multiply(x::Int) = 4x
@patch add(x::Int) = x + 4
]

@testset "simple" begin
pe1 = Mocking.PatchEnv(patches[1])
pe2 = Mocking.PatchEnv(patches[2:3])
pe = Mocking.PatchEnv(patches)

@test pe == merge(pe1, pe2)
end

@testset "debug flag" begin
pe1 = Mocking.PatchEnv(patches[1], true)
pe2 = Mocking.PatchEnv(patches[2:3])
pe = Mocking.PatchEnv(patches, true)

@test pe == merge(pe1, pe2)
end
end
78 changes: 78 additions & 0 deletions test/nested_apply.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Nesting calls to apply should take the appropriate union of patches.
@testset "nested apply calls" begin
multiply(x::Number) = 2x
multiply(x::Int) = 2x - 1
add(x::Number) = x + 2
add(x::Int) = x + 1

@testset "simple" begin
patches = Patch[
@patch multiply(x::Integer) = 3x
@patch multiply(x::Int) = 4x
@patch add(x::Int) = x + 4
]

apply(patches) do
@test (@mock multiply(2)) == 8
@test (@mock multiply(0x2)) == 0x6
@test (@mock multiply(2//1)) == 4//1
@test (@mock add(2//1)) == 4//1
@test (@mock add(2)) == 6
end

apply(patches[1]) do
@test (@mock multiply(2)) == 6
@test (@mock multiply(0x2)) == 0x6
@test (@mock multiply(2//1)) == 4//1
@test (@mock add(2//1)) == 4//1
@test (@mock add(2)) == 3

apply(patches[2]) do
@test (@mock multiply(2)) == 8
@test (@mock multiply(0x2)) == 0x6
@test (@mock multiply(2//1)) == 4//1
@test (@mock add(2//1)) == 4//1
@test (@mock add(2)) == 3

apply(patches[3]) do
@test (@mock multiply(2)) == 8
@test (@mock multiply(0x2)) == 0x6
@test (@mock multiply(2//1)) == 4//1
@test (@mock add(2//1)) == 4//1
@test (@mock add(2)) == 6
end

@test (@mock multiply(2)) == 8
@test (@mock multiply(0x2)) == 0x6
@test (@mock multiply(2//1)) == 4//1
@test (@mock add(2//1)) == 4//1
@test (@mock add(2)) == 3
end

@test (@mock multiply(2)) == 6
@test (@mock multiply(0x2)) == 0x6
@test (@mock multiply(2//1)) == 4//1
@test (@mock add(2//1)) == 4//1
@test (@mock add(2)) == 3
end
end

@testset "repeated patch" begin
patches = Patch[
@patch multiply(x::Integer) = 3x
@patch multiply(x::Integer) = 4x
]

apply(patches) do
@test (@mock multiply(2)) == 8
end

apply(patches[1]) do
@test (@mock multiply(2)) == 6
apply(patches[2]) do
@test (@mock multiply(2)) == 8
end
@test (@mock multiply(2)) == 6
end
end
end
2 changes: 2 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ using Mocking: anon_morespecific, anonymous_signature, dispatch, type_morespecif
include("anonymous-param.jl")
include("reuse.jl")
include("args.jl")
include("merge.jl")
include("nested_apply.jl")
end

2 comments on commit 9fd8321

@oxinabox
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/46037

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.7.3 -m "<description of version>" 9fd832159ee429b9198919622975527c29128db2
git push origin v0.7.3

Please sign in to comment.