From bc6ab5c8f2861371daaebbfa292ff496ab87bd84 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Tue, 25 Mar 2025 13:17:24 +0100 Subject: [PATCH] add some "no invalidations" tests for the sysimage Test that defining certain handpicked type-method pairs (as a user) doesn't result in any invalidation of the sysimage. Some of the tests are marked as broken. Merging this will make the contribution process more demanding: when someone makes a PR they might need to fix unrelated type stability issues, or decrease `max_methods` for some function, before being able to merge their initial PR. However that's better than ignoring the constant danger of type instability/invalidation regressions making the sysimage and precompilation less useful. This change should only be merged while up-to-date with the `master` branch. Fixes #47022 --- test/choosetests.jl | 2 +- test/no_invalidations.jl | 118 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 test/no_invalidations.jl diff --git a/test/choosetests.jl b/test/choosetests.jl index 17970313da01f..d79f2439c296a 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -16,7 +16,7 @@ const TESTNAMES = [ "operators", "ordering", "path", "ccall", "parse", "loading", "gmp", "sorting", "spawn", "backtrace", "exceptions", "file", "read", "version", "namedtuple", - "mpfr", "broadcast", "complex", + "mpfr", "broadcast", "complex", "no_invalidations", "floatapprox", "stdlib", "reflection", "regex", "float16", "combinatorics", "sysinfo", "env", "rounding", "ranges", "mod2pi", "euler", "show", "client", "terminfo", diff --git a/test/no_invalidations.jl b/test/no_invalidations.jl new file mode 100644 index 0000000000000..d82b1e443d0fa --- /dev/null +++ b/test/no_invalidations.jl @@ -0,0 +1,118 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using Test + +module ThereAreNoInvalidations + function julia_expr_cmd(expr::Expr) + `$(Base.julia_cmd()) -e $expr` + end + function readline_julia_expr_cmd(expr::Expr) + open(readline, julia_expr_cmd(expr)) + end + function invalidations_expr(expr::Expr) + quote + try + let invs = ccall(:jl_debug_method_invalidation, Any, (Cint,), 1) + @eval $expr + invs + end + finally + ccall(:jl_debug_method_invalidation, Any, (Cint,), 0) + end + end + end + function there_are_no_invalidations_expr(expr::Expr) + e = invalidations_expr(expr) + quote + let invalidations = $e + show(isempty(invalidations)) + end + end + end + function there_are_no_invalidations(expr::Expr) + e = there_are_no_invalidations_expr(expr) + s = readline_julia_expr_cmd(e) + parse(Bool, s) + end +end + +function type_expr(f, supertype::Type) + type_name = :T + rest = f(type_name) + quote + struct $type_name <: $supertype end + $rest + end +end + +function test_expr(f, supertype::Type) + e = type_expr(f, supertype) + ThereAreNoInvalidations.there_are_no_invalidations(e) +end + +@testset "no invalidations test set" begin + concrete_subtypes_integer = (Bool, Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128, BigInt) + abstract_subtypes_number = (Signed, Unsigned, Integer, AbstractFloat, AbstractIrrational, Real, Number) + @testset "2-arg `show` for various new types" begin + stypes = (Any, AbstractString, Function, abstract_subtypes_number...) + broken = (AbstractString, Function) + @testset "S: $S" for S ∈ stypes + @test test_expr((n -> :(function Base.show(::IO, ::$n) end)), S) broken=(S ∈ broken) + end + end + @testset "new subtype of `AbstractString`" begin + @testset "`print`" begin + @test test_expr((n -> :(function Base.print(::IO, ::$n) end)), AbstractString) broken=true + end + @testset "index-related" begin + fs = (thisind, prevind, nextind) + broken = (thisind, prevind, nextind) + @testset "f: $f" for f ∈ fs + @test test_expr((n -> :(function Base.$f(::$n, ::Int) end)), AbstractString) broken=(f ∈ broken) + end + end + end + @testset "new subtype of `Integer`" begin + @testset "construction of old int type from new type" begin + broken = (UInt16, Int, UInt) + (Int === Int64) && (broken = (broken..., Int32)) + @testset "T: $T" for T ∈ concrete_subtypes_integer + @test test_expr((n -> :(function Base.$T(::$n) end)), Integer) broken=(T ∈ broken) + end + end + @testset "construction of new int type from old type" begin + broken = () + @testset "T: $T" for T ∈ concrete_subtypes_integer + @test test_expr((n -> :(function $n(::$T) end)), Integer) broken=(T ∈ broken) + end + end + @testset "arithmetic between old int types and new int type" begin + ops = (:+, :*, :<<, :>>, :>>>) + broken = () + @testset "T: $T" for T ∈ concrete_subtypes_integer + @testset "op: $op" for op ∈ ops + @test ( + test_expr((n -> :(function Base.$op(::$n, ::$T) end)), Integer) && + test_expr((n -> :(function Base.$op(::$T, ::$n) end)), Integer) + ) broken=((T, op) ∈ broken) + end + end + end + @testset "promotion between old int types and new int type" begin + broken = (Bool, UInt8, Int) + @testset "T: $T" for T ∈ concrete_subtypes_integer + @test ( + test_expr((n -> :(function Base.promote_rule(::Type{$T}, ::Type{$n}) end)), Integer) && + test_expr((n -> :(function Base.promote_rule(::Type{$n}, ::Type{$T}) end)), Integer) + ) broken=(T ∈ broken) + end + end + @testset "unary functions" begin + fs = (zero, one) + broken = (zero, one) + @testset "f: $f" for f ∈ fs + @test test_expr((n -> :(function Base.$f(::$n) end)), Integer) broken=(f ∈ broken) + end + end + end +end