From a660816d5b84f90f180b22914c3b324f90c51f4b Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 24 Nov 2025 16:00:33 -0600 Subject: [PATCH 01/16] Backwards compatible `delete_method` --- src/Mocking.jl | 1 + src/compat.jl | 47 +++++++++++++++++++++ test/compat.jl | 107 +++++++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 6 +-- 4 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 src/compat.jl create mode 100644 test/compat.jl diff --git a/src/Mocking.jl b/src/Mocking.jl index 5e7ffd2..70c441c 100644 --- a/src/Mocking.jl +++ b/src/Mocking.jl @@ -12,6 +12,7 @@ end export @patch, @mock, Patch, apply +include("compat.jl") include("expr.jl") include("dispatch.jl") include("debug.jl") diff --git a/src/compat.jl b/src/compat.jl new file mode 100644 index 0000000..fcc8207 --- /dev/null +++ b/src/compat.jl @@ -0,0 +1,47 @@ +const MAX_WORLD_AGE = typemax(UInt64) + +function delete_method(m::Method) + @static if VERSION >= v"1.12.0" + # On Julia 1.12 deleting a method re-activates the previous version of method + Base.delete_method(m) + else + # The method table associated with the generic function + mt = Base.get_methodtable(m) + + world_age = Base.get_world_counter() + current_method = nothing + old_method = nothing + + # The `Core.MethodTable` stores each method as a linked list with the newest method + # definitions occurring first. + def = mt.defs + while !isnothing(def) + if def.sig == m.sig + if def.max_world == MAX_WORLD_AGE + current_method = def.func + else + old_method = def.func + break + end + end + def = def.next + end + + # When the method table contains 2+ methods for the signature we'll restore the previous + # method definition. Otherwise, we'll just limit the world age for the only existing + # method. + if !isnothing(old_method) + # Using `primary_world == 1` causes Julia to increase the world counter + replacement_method = deepcopy(old_method) + replacement_method.primary_world = 1 + replacement_method.deleted_world = MAX_WORLD_AGE + + ccall(:jl_method_table_insert, Cvoid, (Any, Any, Any), mt, replacement_method, replacement_method.sig) + else + # On Julia versions below 1.12 just limits the world age specified method. + Base.delete_method(m) + end + end + + return nothing +end diff --git a/test/compat.jl b/test/compat.jl new file mode 100644 index 0000000..1f59003 --- /dev/null +++ b/test/compat.jl @@ -0,0 +1,107 @@ +get_methodtable(m::Method) = Base.get_methodtable(m) +get_methodtable(f::Function) = get_methodtable(first(methods(f))) + +function show_mt(mt) + def = mt.defs + println("---") + while !isnothing(def) + Base.show_method(stdout, def.func) + println(stdout, "\n World Age: ", sprint(show, def.min_world), " - ", sprint(show, def.max_world)) + def = def.next + end +end + +show_mt(m::Method) = show_mt(Base.get_methodtable(m)) +show_mt(f::Function) = show_mt(first(methods(f))) + +@testset "delete_method" begin + @testset "delete and restore" begin + foo(::Int) = :original + original_world_age = Base.get_world_counter() + + foo(::Int) = :replaced + replaced_world_age = Base.get_world_counter() + + @test length(methods(foo)) == 1 + @test length(get_methodtable(foo)) == 2 + @test original_world_age < replaced_world_age + + m = first(methods(foo, Tuple{Int})) + @test Mocking.delete_method(m) === nothing + deleted_world_age = Base.get_world_counter() + + mt = get_methodtable(foo) + @test length(methods(foo)) == 1 + @test length(mt) == 3 + @test replaced_world_age < deleted_world_age + + @test Base.invoke_in_world(original_world_age, foo, 1) === :original + @test Base.invoke_in_world(replaced_world_age, foo, 1) === :replaced + @test Base.invoke_in_world(deleted_world_age, foo, 1) === :original + + def = mt.defs + count = 0 + while !isnothing(def) + count += 1 + + if count == 1 + @test def.min_world == deleted_world_age + @test def.max_world == typemax(UInt64) + elseif count == 2 + @test def.min_world == replaced_world_age + @test def.max_world == replaced_world_age + elseif count == 3 + @test def.min_world == original_world_age + @test def.max_world == original_world_age + end + + def = def.next + end + + ml = Base.MethodList(mt) + @test ml[1].primary_world == deleted_world_age + @test ml[1].deleted_world == typemax(UInt64) + @test ml[2].primary_world == replaced_world_age + @test ml[2].deleted_world == replaced_world_age + @test_broken ml[3].primary_world == original_world_age + @test_broken ml[3].deleted_world == original_world_age + end + + @testset "delete only" begin + foo(::Int) = :original + original_world_age = Base.get_world_counter() + + @test length(methods(foo)) == 1 + @test length(get_methodtable(foo)) == 1 + + m = first(methods(foo, Tuple{Int})) + @test Mocking.delete_method(m) === nothing + deleted_world_age = Base.get_world_counter() + + mt = get_methodtable(m) + @test length(methods(foo)) == 0 + @test length(mt) == 1 + @test original_world_age < deleted_world_age + + @test Base.invoke_in_world(original_world_age, foo, 1) === :original + @test_throws MethodError Base.invoke_in_world(deleted_world_age, foo, 1) + + def = mt.defs + count = 0 + while !isnothing(def) + count += 1 + + if count == 1 + @test def.min_world == original_world_age + @test def.max_world == original_world_age + end + + def = def.next + end + + ml = Base.MethodList(mt) + @test ml[1].primary_world == original_world_age + @test ml[1].deleted_world == original_world_age + end +end + diff --git a/test/runtests.jl b/test/runtests.jl index 1e98a93..3f04930 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -15,11 +15,7 @@ Mocking.activate() Aqua.test_all(Mocking; deps_compat=(; check_extras=(; ignore=stdlibs))) end - include("dispatch.jl") - include("mock.jl") - include("patch.jl") - include("debug.jl") - + include("compat.jl") include("concept.jl") include("targets.jl") include("functions.jl") From c262b61080c73f218ec766e2b02a45916ff253b1 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 24 Nov 2025 21:31:01 -0600 Subject: [PATCH 02/16] Formatting --- src/compat.jl | 9 ++++++++- test/compat.jl | 12 ++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/compat.jl b/src/compat.jl index fcc8207..4367eac 100644 --- a/src/compat.jl +++ b/src/compat.jl @@ -36,7 +36,14 @@ function delete_method(m::Method) replacement_method.primary_world = 1 replacement_method.deleted_world = MAX_WORLD_AGE - ccall(:jl_method_table_insert, Cvoid, (Any, Any, Any), mt, replacement_method, replacement_method.sig) + ccall( + :jl_method_table_insert, + Cvoid, + (Any, Any, Any), + mt, + replacement_method, + replacement_method.sig, + ) else # On Julia versions below 1.12 just limits the world age specified method. Base.delete_method(m) diff --git a/test/compat.jl b/test/compat.jl index 1f59003..2f0efa5 100644 --- a/test/compat.jl +++ b/test/compat.jl @@ -1,18 +1,19 @@ get_methodtable(m::Method) = Base.get_methodtable(m) get_methodtable(f::Function) = get_methodtable(first(methods(f))) -function show_mt(mt) +function show_methodtable(io::IO, mt) def = mt.defs println("---") while !isnothing(def) - Base.show_method(stdout, def.func) - println(stdout, "\n World Age: ", sprint(show, def.min_world), " - ", sprint(show, def.max_world)) + Base.show_method(io, def.func) + println(io, "\n World Age: ", repr(def.min_world), " - ", repr(def.max_world)) def = def.next end end -show_mt(m::Method) = show_mt(Base.get_methodtable(m)) -show_mt(f::Function) = show_mt(first(methods(f))) +show_methodtable(io::IO, m::Method) = show_methodtable(io, Base.get_methodtable(m)) +show_methodtable(io::IO, f::Function) = show_methodtable(io, first(methods(f))) +show_methodtable(x) = show_methodtable(stdout, x) @testset "delete_method" begin @testset "delete and restore" begin @@ -104,4 +105,3 @@ show_mt(f::Function) = show_mt(first(methods(f))) @test ml[1].deleted_world == original_world_age end end - From fad774419a4642077b93313c80b1760fc3ba5a5c Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 24 Nov 2025 21:31:15 -0600 Subject: [PATCH 03/16] Compat for `get_world_counter` --- src/compat.jl | 10 ++++++++-- test/compat.jl | 10 +++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/compat.jl b/src/compat.jl index 4367eac..50c05ce 100644 --- a/src/compat.jl +++ b/src/compat.jl @@ -1,4 +1,10 @@ -const MAX_WORLD_AGE = typemax(UInt64) +const MAX_WORLD_AGE = typemax(UInt) + +if VERSION >= v"1.2.0-DEV.573" + get_world_counter() = Base.get_world_counter +else + get_world_counter() = ccall(:jl_get_world_counter, UInt, ()) +end function delete_method(m::Method) @static if VERSION >= v"1.12.0" @@ -8,7 +14,7 @@ function delete_method(m::Method) # The method table associated with the generic function mt = Base.get_methodtable(m) - world_age = Base.get_world_counter() + world_age = get_world_counter() current_method = nothing old_method = nothing diff --git a/test/compat.jl b/test/compat.jl index 2f0efa5..1f5a5c2 100644 --- a/test/compat.jl +++ b/test/compat.jl @@ -18,10 +18,10 @@ show_methodtable(x) = show_methodtable(stdout, x) @testset "delete_method" begin @testset "delete and restore" begin foo(::Int) = :original - original_world_age = Base.get_world_counter() + original_world_age = Mocking.get_world_counter() foo(::Int) = :replaced - replaced_world_age = Base.get_world_counter() + replaced_world_age = Mocking.get_world_counter() @test length(methods(foo)) == 1 @test length(get_methodtable(foo)) == 2 @@ -29,7 +29,7 @@ show_methodtable(x) = show_methodtable(stdout, x) m = first(methods(foo, Tuple{Int})) @test Mocking.delete_method(m) === nothing - deleted_world_age = Base.get_world_counter() + deleted_world_age = Mocking.get_world_counter() mt = get_methodtable(foo) @test length(methods(foo)) == 1 @@ -70,14 +70,14 @@ show_methodtable(x) = show_methodtable(stdout, x) @testset "delete only" begin foo(::Int) = :original - original_world_age = Base.get_world_counter() + original_world_age = Mocking.get_world_counter() @test length(methods(foo)) == 1 @test length(get_methodtable(foo)) == 1 m = first(methods(foo, Tuple{Int})) @test Mocking.delete_method(m) === nothing - deleted_world_age = Base.get_world_counter() + deleted_world_age = Mocking.get_world_counter() mt = get_methodtable(m) @test length(methods(foo)) == 0 From d92b882d9b72784d7752c0d451ff16218b0fcfb8 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 24 Nov 2025 22:10:26 -0600 Subject: [PATCH 04/16] Fixing compat --- .github/workflows/CI.yml | 2 + src/compat.jl | 16 +++++-- test/compat.jl | 100 ++++++++++++++++++++++----------------- 3 files changed, 69 insertions(+), 49 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 8672def..de8fc68 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -20,6 +20,8 @@ jobs: version: - "min" # Oldest supported version - "lts" # LTS + - "1.11" + - "1.12" - "1" # Latest Release os: - ubuntu-latest diff --git a/src/compat.jl b/src/compat.jl index 50c05ce..fe7c633 100644 --- a/src/compat.jl +++ b/src/compat.jl @@ -1,7 +1,7 @@ const MAX_WORLD_AGE = typemax(UInt) if VERSION >= v"1.2.0-DEV.573" - get_world_counter() = Base.get_world_counter + get_world_counter() = Base.get_world_counter() else get_world_counter() = ccall(:jl_get_world_counter, UInt, ()) end @@ -21,7 +21,7 @@ function delete_method(m::Method) # The `Core.MethodTable` stores each method as a linked list with the newest method # definitions occurring first. def = mt.defs - while !isnothing(def) + while def !== nothing if def.sig == m.sig if def.max_world == MAX_WORLD_AGE current_method = def.func @@ -36,11 +36,17 @@ function delete_method(m::Method) # When the method table contains 2+ methods for the signature we'll restore the previous # method definition. Otherwise, we'll just limit the world age for the only existing # method. - if !isnothing(old_method) + if old_method !== nothing # Using `primary_world == 1` causes Julia to increase the world counter replacement_method = deepcopy(old_method) - replacement_method.primary_world = 1 - replacement_method.deleted_world = MAX_WORLD_AGE + + @static if VERSION >= v"1.11" + @atomic replacement_method.primary_world = 1 + @atomic replacement_method.deleted_world = MAX_WORLD_AGE + else + replacement_method.primary_world = 1 + replacement_method.deleted_world = MAX_WORLD_AGE + end ccall( :jl_method_table_insert, diff --git a/test/compat.jl b/test/compat.jl index 1f5a5c2..671a4f8 100644 --- a/test/compat.jl +++ b/test/compat.jl @@ -4,14 +4,22 @@ get_methodtable(f::Function) = get_methodtable(first(methods(f))) function show_methodtable(io::IO, mt) def = mt.defs println("---") - while !isnothing(def) + while Base.MethodList(mt) Base.show_method(io, def.func) println(io, "\n World Age: ", repr(def.min_world), " - ", repr(def.max_world)) - def = def.next end end -show_methodtable(io::IO, m::Method) = show_methodtable(io, Base.get_methodtable(m)) +function show_methodtable(io::IO, m::Method) + println("---") + for el in Base.MethodList(get_methodtable(m)) + el.sig == m.sig || continue + show(io, el) + println(io, "\n World Age: ", repr(el.primary_world)) + # , " - ", repr(el.deleted_world)) + end +end + show_methodtable(io::IO, f::Function) = show_methodtable(io, first(methods(f))) show_methodtable(x) = show_methodtable(stdout, x) @@ -23,85 +31,89 @@ show_methodtable(x) = show_methodtable(stdout, x) foo(::Int) = :replaced replaced_world_age = Mocking.get_world_counter() + @test foo(1) === :replaced @test length(methods(foo)) == 1 - @test length(get_methodtable(foo)) == 2 @test original_world_age < replaced_world_age m = first(methods(foo, Tuple{Int})) @test Mocking.delete_method(m) === nothing deleted_world_age = Mocking.get_world_counter() - mt = get_methodtable(foo) + @test foo(1) === :original @test length(methods(foo)) == 1 - @test length(mt) == 3 @test replaced_world_age < deleted_world_age @test Base.invoke_in_world(original_world_age, foo, 1) === :original @test Base.invoke_in_world(replaced_world_age, foo, 1) === :replaced @test Base.invoke_in_world(deleted_world_age, foo, 1) === :original - def = mt.defs - count = 0 - while !isnothing(def) - count += 1 - - if count == 1 - @test def.min_world == deleted_world_age - @test def.max_world == typemax(UInt64) - elseif count == 2 - @test def.min_world == replaced_world_age - @test def.max_world == replaced_world_age - elseif count == 3 - @test def.min_world == original_world_age - @test def.max_world == original_world_age + @static if VERSION < v"1.12" + mt = get_methodtable(m) + def = mt.defs + count = 0 + while def !== nothing + count += 1 + + if count == 1 + @test def.min_world == deleted_world_age + @test def.max_world == typemax(UInt64) + elseif count == 2 + @test def.min_world == replaced_world_age + @test def.max_world == replaced_world_age + elseif count == 3 + @test def.min_world == original_world_age + @test def.max_world == original_world_age + end + + def = def.next end - def = def.next + ml = Base.MethodList(mt) + @test ml[1].primary_world == deleted_world_age + @test ml[1].deleted_world == typemax(UInt64) + @test ml[2].primary_world == replaced_world_age + @test ml[2].deleted_world == replaced_world_age + @test_broken ml[3].primary_world == original_world_age + @test_broken ml[3].deleted_world == original_world_age end - - ml = Base.MethodList(mt) - @test ml[1].primary_world == deleted_world_age - @test ml[1].deleted_world == typemax(UInt64) - @test ml[2].primary_world == replaced_world_age - @test ml[2].deleted_world == replaced_world_age - @test_broken ml[3].primary_world == original_world_age - @test_broken ml[3].deleted_world == original_world_age end @testset "delete only" begin foo(::Int) = :original original_world_age = Mocking.get_world_counter() + @test foo(1) === :original @test length(methods(foo)) == 1 - @test length(get_methodtable(foo)) == 1 m = first(methods(foo, Tuple{Int})) @test Mocking.delete_method(m) === nothing deleted_world_age = Mocking.get_world_counter() - mt = get_methodtable(m) + @test_throws MethodError foo(1) @test length(methods(foo)) == 0 - @test length(mt) == 1 @test original_world_age < deleted_world_age @test Base.invoke_in_world(original_world_age, foo, 1) === :original @test_throws MethodError Base.invoke_in_world(deleted_world_age, foo, 1) - def = mt.defs - count = 0 - while !isnothing(def) - count += 1 + @static if VERSION < v"1.12" + mt = get_methodtable(m) + def = mt.defs + count = 0 + while def !== nothing + count += 1 + + if count == 1 + @test def.min_world == original_world_age + @test def.max_world == original_world_age + end - if count == 1 - @test def.min_world == original_world_age - @test def.max_world == original_world_age + def = def.next end - def = def.next + ml = Base.MethodList(mt) + @test ml[1].primary_world == original_world_age + @test ml[1].deleted_world == original_world_age end - - ml = Base.MethodList(mt) - @test ml[1].primary_world == original_world_age - @test ml[1].deleted_world == original_world_age end end From 26ced96c41147fe96a81cfe94977556376cf8034 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 24 Nov 2025 22:13:00 -0600 Subject: [PATCH 05/16] Use `julia-version` --- .github/workflows/CI.yml | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index de8fc68..a2566d4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,18 +11,35 @@ on: - "Project.toml" - ".github/workflows/CI.yml" jobs: + version: + name: Resolve Julia Versions + # These permissions are needed to: + # - Checkout the Git repository (`contents: read`) + permissions: + contents: read + runs-on: ubuntu-latest + outputs: + json: ${{ steps.julia-version.outputs.resolved-json }} + steps: + - uses: actions/checkout@v6 # Needed for "min" to access the Project.toml + - uses: julia-actions/julia-version@v1 + id: julia-version + with: + versions: | + - min # Oldest supported version + - lts # Long-Term Stable + - 1.11 + - 1.12 + - 1 # Latest release + test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + needs: version runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - version: - - "min" # Oldest supported version - - "lts" # LTS - - "1.11" - - "1.12" - - "1" # Latest Release + version: ${{ fromJSON(needs.version.outputs.json) }} os: - ubuntu-latest arch: From f1456dd7c4f6b17a9875eb539e78c4828a09a21c Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 24 Nov 2025 22:14:44 -0600 Subject: [PATCH 06/16] Try Julia 1.1 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 5826b2f..8e52601 100644 --- a/Project.toml +++ b/Project.toml @@ -14,7 +14,7 @@ ExprTools = "e2ba6199-217a-4e67-a87a-7c52f15ade04" Aqua = "0.8.7" Compat = "3.9, 4" ExprTools = "0.1" -julia = "1" +julia = "1.1" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" From 15aff0d21c7f10e95324ea85c04358c3f704c477 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 24 Nov 2025 22:15:35 -0600 Subject: [PATCH 07/16] fixup! Use `julia-version` --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a2566d4..d1178ee 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -22,7 +22,7 @@ jobs: json: ${{ steps.julia-version.outputs.resolved-json }} steps: - uses: actions/checkout@v6 # Needed for "min" to access the Project.toml - - uses: julia-actions/julia-version@v1 + - uses: julia-actions/julia-version@v0.1 id: julia-version with: versions: | From b8e3113f8cadb0abf6e86b787bdfc85676398ecc Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 24 Nov 2025 22:16:09 -0600 Subject: [PATCH 08/16] fixup! Use `julia-version` --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d1178ee..bf4be68 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -22,7 +22,7 @@ jobs: json: ${{ steps.julia-version.outputs.resolved-json }} steps: - uses: actions/checkout@v6 # Needed for "min" to access the Project.toml - - uses: julia-actions/julia-version@v0.1 + - uses: julia-actions/julia-version@v0.1.0 id: julia-version with: versions: | From d6a1738b9de2682553385bd27eb70b82168226ad Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 24 Nov 2025 22:17:44 -0600 Subject: [PATCH 09/16] Try all minor Julia versions --- .github/workflows/CI.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index bf4be68..46a0d73 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -31,6 +31,14 @@ jobs: - 1.11 - 1.12 - 1 # Latest release + - 1.2 + - 1.3 + - 1.4 + - 1.5 + - 1.6 + - 1.7 + - 1.8 + - 1.9 test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} From 49d48cb30a6285f8a6313dd9c9f0d8ea167a9107 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 24 Nov 2025 22:41:13 -0600 Subject: [PATCH 10/16] Require Julia 1.6 --- .github/workflows/CI.yml | 9 +++------ Project.toml | 2 +- src/compat.jl | 8 +------- test/compat.jl | 14 +++++++------- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 46a0d73..64abcbe 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -28,17 +28,14 @@ jobs: versions: | - min # Oldest supported version - lts # Long-Term Stable - - 1.11 - - 1.12 - 1 # Latest release - - 1.2 - - 1.3 - - 1.4 - - 1.5 - 1.6 - 1.7 - 1.8 - 1.9 + - 1.10 + - 1.11 + - 1.12 test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} diff --git a/Project.toml b/Project.toml index 8e52601..83c2ca5 100644 --- a/Project.toml +++ b/Project.toml @@ -14,7 +14,7 @@ ExprTools = "e2ba6199-217a-4e67-a87a-7c52f15ade04" Aqua = "0.8.7" Compat = "3.9, 4" ExprTools = "0.1" -julia = "1.1" +julia = "1.6" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" diff --git a/src/compat.jl b/src/compat.jl index fe7c633..bb8f9e9 100644 --- a/src/compat.jl +++ b/src/compat.jl @@ -1,11 +1,5 @@ const MAX_WORLD_AGE = typemax(UInt) -if VERSION >= v"1.2.0-DEV.573" - get_world_counter() = Base.get_world_counter() -else - get_world_counter() = ccall(:jl_get_world_counter, UInt, ()) -end - function delete_method(m::Method) @static if VERSION >= v"1.12.0" # On Julia 1.12 deleting a method re-activates the previous version of method @@ -14,7 +8,7 @@ function delete_method(m::Method) # The method table associated with the generic function mt = Base.get_methodtable(m) - world_age = get_world_counter() + world_age = Base.get_world_counter() current_method = nothing old_method = nothing diff --git a/test/compat.jl b/test/compat.jl index 671a4f8..ddc238e 100644 --- a/test/compat.jl +++ b/test/compat.jl @@ -26,10 +26,10 @@ show_methodtable(x) = show_methodtable(stdout, x) @testset "delete_method" begin @testset "delete and restore" begin foo(::Int) = :original - original_world_age = Mocking.get_world_counter() + original_world_age = Base.get_world_counter() foo(::Int) = :replaced - replaced_world_age = Mocking.get_world_counter() + replaced_world_age = Base.get_world_counter() @test foo(1) === :replaced @test length(methods(foo)) == 1 @@ -37,7 +37,7 @@ show_methodtable(x) = show_methodtable(stdout, x) m = first(methods(foo, Tuple{Int})) @test Mocking.delete_method(m) === nothing - deleted_world_age = Mocking.get_world_counter() + deleted_world_age = Base.get_world_counter() @test foo(1) === :original @test length(methods(foo)) == 1 @@ -68,7 +68,7 @@ show_methodtable(x) = show_methodtable(stdout, x) def = def.next end - ml = Base.MethodList(mt) + ml = collect(Base.MethodList(mt)) @test ml[1].primary_world == deleted_world_age @test ml[1].deleted_world == typemax(UInt64) @test ml[2].primary_world == replaced_world_age @@ -80,14 +80,14 @@ show_methodtable(x) = show_methodtable(stdout, x) @testset "delete only" begin foo(::Int) = :original - original_world_age = Mocking.get_world_counter() + original_world_age = Base.get_world_counter() @test foo(1) === :original @test length(methods(foo)) == 1 m = first(methods(foo, Tuple{Int})) @test Mocking.delete_method(m) === nothing - deleted_world_age = Mocking.get_world_counter() + deleted_world_age = Base.get_world_counter() @test_throws MethodError foo(1) @test length(methods(foo)) == 0 @@ -111,7 +111,7 @@ show_methodtable(x) = show_methodtable(stdout, x) def = def.next end - ml = Base.MethodList(mt) + ml = collect(Base.MethodList(mt)) @test ml[1].primary_world == original_world_age @test ml[1].deleted_world == original_world_age end From 48bdc844f681ca47e9ee6d126b06e2c29a615674 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 24 Nov 2025 22:45:23 -0600 Subject: [PATCH 11/16] Reduce tested versions --- .github/workflows/CI.yml | 6 +----- src/compat.jl | 9 +++++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 64abcbe..ff0fb47 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -29,11 +29,7 @@ jobs: - min # Oldest supported version - lts # Long-Term Stable - 1 # Latest release - - 1.6 - - 1.7 - - 1.8 - - 1.9 - - 1.10 + # Versions to validate backwards compatibility logic - 1.11 - 1.12 diff --git a/src/compat.jl b/src/compat.jl index bb8f9e9..ba05edd 100644 --- a/src/compat.jl +++ b/src/compat.jl @@ -27,9 +27,9 @@ function delete_method(m::Method) def = def.next end - # When the method table contains 2+ methods for the signature we'll restore the previous - # method definition. Otherwise, we'll just limit the world age for the only existing - # method. + # When the method table contains 2+ methods for the signature we'll restore the + # previous method definition. Otherwise, we'll just limit the world age for the only + # existing method. if old_method !== nothing # Using `primary_world == 1` causes Julia to increase the world counter replacement_method = deepcopy(old_method) @@ -51,7 +51,8 @@ function delete_method(m::Method) replacement_method.sig, ) else - # On Julia versions below 1.12 just limits the world age specified method. + # On Julia versions below 1.12 this just limits the world age for the specified + # method. Base.delete_method(m) end end From b1c9e1f1bace688757e6dce5d4624ba2feb7339f Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Mon, 24 Nov 2025 22:45:36 -0600 Subject: [PATCH 12/16] Make codecov only informational --- .codecov.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .codecov.yaml diff --git a/.codecov.yaml b/.codecov.yaml new file mode 100644 index 0000000..050addd --- /dev/null +++ b/.codecov.yaml @@ -0,0 +1,10 @@ +--- +comment: false +coverage: + status: + project: + default: + informational: true + patch: + default: + informational: true From ca12bc88d03a53c03cae81fa4ede0fe6354f63a5 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Tue, 25 Nov 2025 10:07:37 -0600 Subject: [PATCH 13/16] fixup --- .github/workflows/CI.yaml | 2 +- test/runtests.jl | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 8cd1396..f2a8c8f 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -28,7 +28,7 @@ jobs: id: julia-version with: versions: | - - min # Oldest supported version + - min # Project's oldest supported version - lts # Long-Term Stable - 1 # Latest release # Versions to validate backwards compatibility logic diff --git a/test/runtests.jl b/test/runtests.jl index 3f04930..155025a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -16,6 +16,11 @@ Mocking.activate() end include("compat.jl") + include("dispatch.jl") + include("mock.jl") + include("patch.jl") + include("debug.jl") + include("concept.jl") include("targets.jl") include("functions.jl") From 5beb3fd0ca18aea9500cdc1a637c427ce0b159f4 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Tue, 25 Nov 2025 11:03:32 -0600 Subject: [PATCH 14/16] Support deleting the non-latest method --- src/compat.jl | 12 +++++----- test/compat.jl | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/compat.jl b/src/compat.jl index ba05edd..99f706c 100644 --- a/src/compat.jl +++ b/src/compat.jl @@ -5,7 +5,7 @@ function delete_method(m::Method) # On Julia 1.12 deleting a method re-activates the previous version of method Base.delete_method(m) else - # The method table associated with the generic function + # The method table associated with the generic function. mt = Base.get_methodtable(m) world_age = Base.get_world_counter() @@ -17,9 +17,9 @@ function delete_method(m::Method) def = mt.defs while def !== nothing if def.sig == m.sig - if def.max_world == MAX_WORLD_AGE + if def.min_world == m.primary_world current_method = def.func - else + elseif current_method !== nothing old_method = def.func break end @@ -42,6 +42,8 @@ function delete_method(m::Method) replacement_method.deleted_world = MAX_WORLD_AGE end + # Adding a new method into the function's method table will increase world age + # (requires `primary_world == 1`) and invalidate backedges. ccall( :jl_method_table_insert, Cvoid, @@ -51,8 +53,8 @@ function delete_method(m::Method) replacement_method.sig, ) else - # On Julia versions below 1.12 this just limits the world age for the specified - # method. + # On Julia versions below 1.12 this simply limits the world age for the + # specified method. Base.delete_method(m) end end diff --git a/test/compat.jl b/test/compat.jl index ddc238e..78933d7 100644 --- a/test/compat.jl +++ b/test/compat.jl @@ -67,8 +67,10 @@ show_methodtable(x) = show_methodtable(stdout, x) def = def.next end + @test count == 3 ml = collect(Base.MethodList(mt)) + @test length(ml) == 3 @test ml[1].primary_world == deleted_world_age @test ml[1].deleted_world == typemax(UInt64) @test ml[2].primary_world == replaced_world_age @@ -110,10 +112,68 @@ show_methodtable(x) = show_methodtable(stdout, x) def = def.next end + @test count == 1 ml = collect(Base.MethodList(mt)) + @test length(ml) == 1 @test ml[1].primary_world == original_world_age @test ml[1].deleted_world == original_world_age end end + + @testset "delete non-latest" begin + foo(::Int) = :original + original_world_age = Base.get_world_counter() + original_method = first(methods(foo, Tuple{Int})) + + foo(::Int) = :replaced + replaced_world_age = Base.get_world_counter() + replaced_method = first(methods(foo, Tuple{Int})) + + @test original_method != replaced_method + + @test foo(1) === :replaced + @test length(methods(foo)) == 1 + @test original_world_age < replaced_world_age + + @test Mocking.delete_method(original_method) === nothing + deleted_world_age = Base.get_world_counter() + + @test foo(1) === :replaced + @test length(methods(foo)) == 1 + @test replaced_world_age < deleted_world_age + + @test Base.invoke_in_world(original_world_age, foo, 1) === :original + @test Base.invoke_in_world(replaced_world_age, foo, 1) === :replaced + @test Base.invoke_in_world(deleted_world_age, foo, 1) === :replaced + + @static if VERSION < v"1.12" + @show original_world_age replaced_world_age deleted_world_age + show_methodtable(foo) + mt = get_methodtable(original_method) + def = mt.defs + count = 0 + while def !== nothing + count += 1 + + if count == 1 + @test def.min_world == replaced_world_age + @test def.max_world == Mocking.MAX_WORLD_AGE + elseif count == 2 + @test def.min_world == original_world_age + @test def.max_world == replaced_world_age + end + + def = def.next + end + @test count == 2 + + ml = collect(Base.MethodList(mt)) + @test length(ml) == 2 + @test ml[1].primary_world == replaced_world_age + @test ml[1].deleted_world == Mocking.MAX_WORLD_AGE + @test ml[2].primary_world == original_world_age + @test ml[2].deleted_world == replaced_world_age + end + end end From 2aadb51965c0424d3f7c7632ad4da35a09dbcd5d Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Tue, 25 Nov 2025 13:57:25 -0600 Subject: [PATCH 15/16] Improve test suite --- test/compat.jl | 346 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 280 insertions(+), 66 deletions(-) diff --git a/test/compat.jl b/test/compat.jl index 78933d7..54e09d0 100644 --- a/test/compat.jl +++ b/test/compat.jl @@ -1,44 +1,129 @@ -get_methodtable(m::Method) = Base.get_methodtable(m) -get_methodtable(f::Function) = get_methodtable(first(methods(f))) +# To validate `delete_method` we need to check the world age range associated with a method. +# In Julia 1.11 and below we could use `Base.get_methodtable` to view all of the current and +# outdated methods associated with the generic function. In Julia 1.12 this function now +# returns a method table for all generic functions. The `get_methodtableentry` function is +# an attempt to provide a common interface between various versions of Julia. +if VERSION >= v"1.12" + function get_methodtableentry(m::Method) + mt = Base.get_methodtable(m) + func = Base.unwrap_unionall(m.sig).types[1] + return mt.defs !== nothing ? _get_methodtableentry(mt.defs, func) : nothing + end -function show_methodtable(io::IO, mt) - def = mt.defs - println("---") - while Base.MethodList(mt) - Base.show_method(io, def.func) - println(io, "\n World Age: ", repr(def.min_world), " - ", repr(def.max_world)) + # Adapted from the `Base.visit` function: + # https://github.com/JuliaLang/julia/blob/a4d2b6a358aeaa9814c37c0644fe3c56f3d90823/base/runtime_internals.jl#L1806-L1842 + function _get_methodtableentry(mc::Core.TypeMapLevel, ft::Type) + function avisit(e::Memory{Any}) + for i in 2:2:length(e) + isassigned(e, i) || continue + ei = e[i] + if ei isa Memory{Any} + for j in 2:2:length(ei) + isassigned(ei, j) || continue + mte = _get_methodtableentry(ei[j], ft) + mte === nothing || return mte + end + else + mte = _get_methodtableentry(ei, ft) + mte === nothing || return mte + end + end + return nothing + end + if mc.targ !== nothing + mte = avisit(mc.targ::Memory{Any}) + mte === nothing || return mte + end + if mc.arg1 !== nothing + mte = avisit(mc.arg1::Memory{Any}) + mte === nothing || return mte + end + if mc.tname !== nothing + mte = avisit(mc.tname::Memory{Any}) + mte === nothing || return mte + end + if mc.name1 !== nothing + mte = avisit(mc.name1::Memory{Any}) + mte === nothing || return mte + end + if mc.list !== nothing + mte = _get_methodtableentry(mc.list, ft) + mte === nothing || return mte + end + if mc.any !== nothing + mte = _get_methodtableentry(mc.any, ft) + mte === nothing || return mte + end + return nothing + end + + function _get_methodtableentry(d::Core.TypeMapEntry, ft::Type) + while d !== nothing + Base.unwrap_unionall(d.func.sig).types[1] == ft && return d + d = d.next + end + end +else + function get_methodtableentry(m::Method) + mt = Base.get_methodtable(m) + return mt.defs !== nothing ? mt.defs : nothing end end -function show_methodtable(io::IO, m::Method) +function get_methodlist(m::Method) + mt = Base.get_methodtable(m) + ml = Base.MethodList(mt) + return if VERSION >= v"1.12" + func_type = Base.unwrap_unionall(m.sig).types[1] + filter(el -> Base.unwrap_unionall(el.sig).types[1] == func_type, ml) + else + collect(ml) + end +end + +function show_methodtableentry(io::IO, def) println("---") - for el in Base.MethodList(get_methodtable(m)) - el.sig == m.sig || continue - show(io, el) - println(io, "\n World Age: ", repr(el.primary_world)) - # , " - ", repr(el.deleted_world)) + while !isnothing(def) + Base.show_method(io, def.func) + print(io, "\n World Age: ") + println(io, repr(def.min_world), " - ", repr(def.max_world)) + def = def.next end + return nothing end -show_methodtable(io::IO, f::Function) = show_methodtable(io, first(methods(f))) -show_methodtable(x) = show_methodtable(stdout, x) +function show_methodtableentry(io::IO, m::Method) + return show_methodtableentry(io, get_methodtableentry(m)) +end + +show_methodtableentry(x) = show_methodtableentry(stdout, x) @testset "delete_method" begin @testset "delete and restore" begin foo(::Int) = :original original_world_age = Base.get_world_counter() + original_method = first(methods(foo)) + + # @show original_world_age + # show_methodtableentry(original_method) foo(::Int) = :replaced replaced_world_age = Base.get_world_counter() + replaced_method = first(methods(foo)) + + # @show replaced_world_age + # show_methodtableentry(original_method) @test foo(1) === :replaced @test length(methods(foo)) == 1 @test original_world_age < replaced_world_age - m = first(methods(foo, Tuple{Int})) - @test Mocking.delete_method(m) === nothing + @test Mocking.delete_method(replaced_method) === nothing deleted_world_age = Base.get_world_counter() + # @show deleted_world_age + # show_methodtableentry(original_method) + @test foo(1) === :original @test length(methods(foo)) == 1 @test replaced_world_age < deleted_world_age @@ -47,16 +132,25 @@ show_methodtable(x) = show_methodtable(stdout, x) @test Base.invoke_in_world(replaced_world_age, foo, 1) === :replaced @test Base.invoke_in_world(deleted_world_age, foo, 1) === :original - @static if VERSION < v"1.12" - mt = get_methodtable(m) - def = mt.defs - count = 0 - while def !== nothing - count += 1 + # Validate the world age range associated with the methods + def = get_methodtableentry(original_method) + expected_count = VERSION >= v"1.12" ? 2 : 3 + count = 0 + while def !== nothing + count += 1 + if VERSION >= v"1.12" + if count == 1 + @test def.min_world == replaced_world_age + @test def.max_world == replaced_world_age + elseif count == 2 + @test def.min_world == original_world_age + @test def.max_world == typemax(UInt) + end + else if count == 1 @test def.min_world == deleted_world_age - @test def.max_world == typemax(UInt64) + @test def.max_world == typemax(UInt) elseif count == 2 @test def.min_world == replaced_world_age @test def.max_world == replaced_world_age @@ -64,15 +158,20 @@ show_methodtable(x) = show_methodtable(stdout, x) @test def.min_world == original_world_age @test def.max_world == original_world_age end - - def = def.next end - @test count == 3 - ml = collect(Base.MethodList(mt)) - @test length(ml) == 3 + def = def.next + end + @test count == expected_count + + ml = get_methodlist(original_method) + @test length(ml) == expected_count + if VERSION >= v"1.12" + @test ml[1].primary_world == replaced_world_age + @test ml[2].primary_world == original_world_age + else @test ml[1].primary_world == deleted_world_age - @test ml[1].deleted_world == typemax(UInt64) + @test ml[1].deleted_world == typemax(UInt) @test ml[2].primary_world == replaced_world_age @test ml[2].deleted_world == replaced_world_age @test_broken ml[3].primary_world == original_world_age @@ -83,12 +182,12 @@ show_methodtable(x) = show_methodtable(stdout, x) @testset "delete only" begin foo(::Int) = :original original_world_age = Base.get_world_counter() + original_method = first(methods(foo)) @test foo(1) === :original @test length(methods(foo)) == 1 - m = first(methods(foo, Tuple{Int})) - @test Mocking.delete_method(m) === nothing + @test Mocking.delete_method(original_method) === nothing deleted_world_age = Base.get_world_counter() @test_throws MethodError foo(1) @@ -98,24 +197,26 @@ show_methodtable(x) = show_methodtable(stdout, x) @test Base.invoke_in_world(original_world_age, foo, 1) === :original @test_throws MethodError Base.invoke_in_world(deleted_world_age, foo, 1) - @static if VERSION < v"1.12" - mt = get_methodtable(m) - def = mt.defs - count = 0 - while def !== nothing - count += 1 - - if count == 1 - @test def.min_world == original_world_age - @test def.max_world == original_world_age - end + # Validate the world age range associated with the methods + def = get_methodtableentry(original_method) + count = 0 + while def !== nothing + count += 1 - def = def.next + if count == 1 + @test def.min_world == original_world_age + @test def.max_world == original_world_age end - @test count == 1 - ml = collect(Base.MethodList(mt)) - @test length(ml) == 1 + def = def.next + end + @test count == 1 + + ml = get_methodlist(original_method) + @test length(ml) == 1 + if VERSION >= v"1.12" + @test ml[1].primary_world == original_world_age + else @test ml[1].primary_world == original_world_age @test ml[1].deleted_world == original_world_age end @@ -124,11 +225,11 @@ show_methodtable(x) = show_methodtable(stdout, x) @testset "delete non-latest" begin foo(::Int) = :original original_world_age = Base.get_world_counter() - original_method = first(methods(foo, Tuple{Int})) + original_method = first(methods(foo)) foo(::Int) = :replaced replaced_world_age = Base.get_world_counter() - replaced_method = first(methods(foo, Tuple{Int})) + replaced_method = first(methods(foo)) @test original_method != replaced_method @@ -147,33 +248,146 @@ show_methodtable(x) = show_methodtable(stdout, x) @test Base.invoke_in_world(replaced_world_age, foo, 1) === :replaced @test Base.invoke_in_world(deleted_world_age, foo, 1) === :replaced - @static if VERSION < v"1.12" - @show original_world_age replaced_world_age deleted_world_age - show_methodtable(foo) - mt = get_methodtable(original_method) - def = mt.defs - count = 0 - while def !== nothing - count += 1 + def = get_methodtableentry(original_method) + count = 0 + while def !== nothing + count += 1 + + if count == 1 + @test def.min_world == replaced_world_age + @test def.max_world == typemax(UInt) + elseif count == 2 + @test def.min_world == original_world_age + @test def.max_world == replaced_world_age + end + + def = def.next + end + @test count == 2 + + ml = get_methodlist(original_method) + @test length(ml) == 2 + if VERSION >= v"1.12" + @test ml[1].primary_world == replaced_world_age + @test ml[2].primary_world == original_world_age + else + @test ml[1].primary_world == replaced_world_age + @test ml[1].deleted_world == typemax(UInt) + @test ml[2].primary_world == original_world_age + @test ml[2].deleted_world == replaced_world_age + end + end + + @testset "signature specific" begin + foo(::Int) = :original + original_world_age = Base.get_world_counter() + foo(::Float64) = :original + float_world_age = Base.get_world_counter() + original_method = first(methods(foo, Tuple{Int})) + + # @show original_world_age + # show_methodtableentry(original_method) + + foo(::Int) = :replaced + replaced_world_age = Base.get_world_counter() + replaced_method = first(methods(foo, Tuple{Int})) + + # @show replaced_world_age + # show_methodtableentry(original_method) + + @test original_method != replaced_method + + @test foo(1) === :replaced + @test foo(1.0) === :original + @test length(methods(foo)) == 2 + @test original_world_age < replaced_world_age + + @test Mocking.delete_method(replaced_method) === nothing + deleted_world_age = Base.get_world_counter() + + # @show deleted_world_age + # show_methodtableentry(original_method) + + @test foo(1) === :original + @test foo(1.0) === :original + @test length(methods(foo)) == 2 + @test replaced_world_age < deleted_world_age + + @test Base.invoke_in_world(original_world_age, foo, 1) === :original + @test Base.invoke_in_world(replaced_world_age, foo, 1) === :replaced + @test Base.invoke_in_world(deleted_world_age, foo, 1) === :original + + # Validate the world age range associated with the methods + def = get_methodtableentry(original_method) + count = 0 + while def !== nothing + count += 1 + if VERSION >= v"1.12" if count == 1 + @test def.sig == Tuple{typeof(foo), Int} @test def.min_world == replaced_world_age - @test def.max_world == Mocking.MAX_WORLD_AGE + @test def.max_world == replaced_world_age elseif count == 2 + @test def.sig == Tuple{typeof(foo), Float64} + @test def.min_world == float_world_age + @test def.max_world == typemax(UInt) + elseif count == 3 + @test def.sig == Tuple{typeof(foo), Int} @test def.min_world == original_world_age + @test def.max_world == typemax(UInt) + end + else + if count == 1 + @test def.sig == Tuple{typeof(foo), Int} + @test def.min_world == deleted_world_age + @test def.max_world == typemax(UInt) + elseif count == 2 + @test def.sig == Tuple{typeof(foo), Int} + @test def.min_world == replaced_world_age @test def.max_world == replaced_world_age + elseif count == 3 + @test def.sig == Tuple{typeof(foo), Float64} + @test def.min_world == float_world_age + @test def.max_world == typemax(UInt) + elseif count == 4 + @test def.sig == Tuple{typeof(foo), Int} + @test def.min_world == original_world_age + @test def.max_world == float_world_age end - - def = def.next end - @test count == 2 - ml = collect(Base.MethodList(mt)) - @test length(ml) == 2 + def = def.next + end + @test count == (VERSION >= v"1.12" ? 3 : 4) + + ml = get_methodlist(original_method) + @test length(ml) == (VERSION >= v"1.12" ? 3 : 4) + if VERSION >= v"1.12" + @test ml[1].sig == Tuple{typeof(foo), Int} @test ml[1].primary_world == replaced_world_age - @test ml[1].deleted_world == Mocking.MAX_WORLD_AGE - @test ml[2].primary_world == original_world_age + + @test ml[2].sig == Tuple{typeof(foo), Float64} + @test ml[2].primary_world == float_world_age + + @test ml[3].sig == Tuple{typeof(foo), Int} + @test ml[3].primary_world == original_world_age + else + @test ml[1].sig == Tuple{typeof(foo), Int} + @test ml[1].primary_world == deleted_world_age + @test ml[1].deleted_world == typemax(UInt) + + @test ml[2].sig == Tuple{typeof(foo), Int} + @test ml[2].primary_world == replaced_world_age @test ml[2].deleted_world == replaced_world_age + + @test ml[3].sig == Tuple{typeof(foo), Float64} + @test ml[3].primary_world == float_world_age + @test ml[3].deleted_world == typemax(UInt) + + @test ml[4].sig == Tuple{typeof(foo), Int} + @test_broken ml[4].primary_world == original_world_age + @test_broken ml[4].deleted_world == float_world_age end end end From 5adf6ae50c1e6d1023c43f8e9b9a8a7f228f47a8 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Tue, 25 Nov 2025 14:00:06 -0600 Subject: [PATCH 16/16] Formatting --- test/compat.jl | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/compat.jl b/test/compat.jl index 54e09d0..cb7b174 100644 --- a/test/compat.jl +++ b/test/compat.jl @@ -325,29 +325,29 @@ show_methodtableentry(x) = show_methodtableentry(stdout, x) if VERSION >= v"1.12" if count == 1 - @test def.sig == Tuple{typeof(foo), Int} + @test def.sig == Tuple{typeof(foo),Int} @test def.min_world == replaced_world_age @test def.max_world == replaced_world_age elseif count == 2 - @test def.sig == Tuple{typeof(foo), Float64} + @test def.sig == Tuple{typeof(foo),Float64} @test def.min_world == float_world_age @test def.max_world == typemax(UInt) elseif count == 3 - @test def.sig == Tuple{typeof(foo), Int} + @test def.sig == Tuple{typeof(foo),Int} @test def.min_world == original_world_age @test def.max_world == typemax(UInt) end else if count == 1 - @test def.sig == Tuple{typeof(foo), Int} + @test def.sig == Tuple{typeof(foo),Int} @test def.min_world == deleted_world_age @test def.max_world == typemax(UInt) elseif count == 2 - @test def.sig == Tuple{typeof(foo), Int} + @test def.sig == Tuple{typeof(foo),Int} @test def.min_world == replaced_world_age @test def.max_world == replaced_world_age elseif count == 3 - @test def.sig == Tuple{typeof(foo), Float64} + @test def.sig == Tuple{typeof(foo),Float64} @test def.min_world == float_world_age @test def.max_world == typemax(UInt) elseif count == 4 @@ -364,28 +364,28 @@ show_methodtableentry(x) = show_methodtableentry(stdout, x) ml = get_methodlist(original_method) @test length(ml) == (VERSION >= v"1.12" ? 3 : 4) if VERSION >= v"1.12" - @test ml[1].sig == Tuple{typeof(foo), Int} + @test ml[1].sig == Tuple{typeof(foo),Int} @test ml[1].primary_world == replaced_world_age - @test ml[2].sig == Tuple{typeof(foo), Float64} + @test ml[2].sig == Tuple{typeof(foo),Float64} @test ml[2].primary_world == float_world_age - @test ml[3].sig == Tuple{typeof(foo), Int} + @test ml[3].sig == Tuple{typeof(foo),Int} @test ml[3].primary_world == original_world_age else - @test ml[1].sig == Tuple{typeof(foo), Int} + @test ml[1].sig == Tuple{typeof(foo),Int} @test ml[1].primary_world == deleted_world_age @test ml[1].deleted_world == typemax(UInt) - @test ml[2].sig == Tuple{typeof(foo), Int} + @test ml[2].sig == Tuple{typeof(foo),Int} @test ml[2].primary_world == replaced_world_age @test ml[2].deleted_world == replaced_world_age - @test ml[3].sig == Tuple{typeof(foo), Float64} + @test ml[3].sig == Tuple{typeof(foo),Float64} @test ml[3].primary_world == float_world_age @test ml[3].deleted_world == typemax(UInt) - @test ml[4].sig == Tuple{typeof(foo), Int} + @test ml[4].sig == Tuple{typeof(foo),Int} @test_broken ml[4].primary_world == original_world_age @test_broken ml[4].deleted_world == float_world_age end