diff --git a/test/Manifest.toml b/test/Manifest.toml deleted file mode 100644 index 728922e..0000000 --- a/test/Manifest.toml +++ /dev/null @@ -1,80 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - -[[Artifacts]] -uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" -version = "1.11.0" - -[[Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" -version = "1.11.0" - -[[CompilerSupportLibraries_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.3.0+1" - -[[InteractiveUtils]] -deps = ["Markdown"] -uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" -version = "1.11.0" - -[[JuliaSyntaxHighlighting]] -deps = ["StyledStrings"] -uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" -version = "1.12.0" - -[[Libdl]] -uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" -version = "1.11.0" - -[[LinearAlgebra]] -deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] -uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -version = "1.12.0" - -[[Logging]] -uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" -version = "1.11.0" - -[[Markdown]] -deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] -uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" -version = "1.11.0" - -[[OpenBLAS_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] -uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.29+0" - -[[Random]] -deps = ["SHA"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -version = "1.11.0" - -[[SHA]] -uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" -version = "0.7.0" - -[[Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" -version = "1.11.0" - -[[StyledStrings]] -uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" -version = "1.11.0" - -[[Suppressor]] -deps = ["Logging"] -git-tree-sha1 = "6dbb5b635c5437c68c28c2ac9e39b87138f37c0a" -uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb" -version = "0.2.8" - -[[Test]] -deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] -uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -version = "1.11.0" - -[[libblastrampoline_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.15.0+0" diff --git a/test/Project.toml b/test/Project.toml index 1d6acbc..e874bcf 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,8 +1,12 @@ [deps] -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" +SciMLTesting = "09d9d899-5365-40a9-917a-5f67fddea283" Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] +SafeTestsets = "0.0.1, 0.1" +SciMLTesting = "1" Suppressor = "0.2.8" +Test = "1" diff --git a/test/commutation_tests.jl b/test/commutation_tests.jl new file mode 100644 index 0000000..4400d9a --- /dev/null +++ b/test/commutation_tests.jl @@ -0,0 +1,33 @@ +using ConcreteStructs +using Test + +# this has been working for a while already +Base.@kwdef @concrete struct Foo2{T <: Real} + x::T = 1.0 + y = true + z = 42 +end + +foo = Foo2() +@test typeof(foo) === Foo2{Float64, Bool, Int64} + +# this is new +@concrete Base.@kwdef struct Foo3{T <: Real} + x::T = 1.0 + y = true + z = 42 +end + +foo = Foo3() +@test typeof(foo) === Foo3{Float64, Bool, Int64} + +foo = Foo3(z = [1, 2, 3]) +@test typeof(foo) === Foo3{Float64, Bool, Vector{Int64}} + +# terse test case, unfortunately doesn't work the other way around. But also never did. +@concrete terse Base.@kwdef struct TerseKWDef + a = 1.0 + b = true +end +terse_kw_def = TerseKWDef(b = false) +@test typeof(terse_kw_def) === TerseKWDef{Float64, Bool} diff --git a/test/core.jl b/test/core.jl deleted file mode 100644 index 4b29fa7..0000000 --- a/test/core.jl +++ /dev/null @@ -1,237 +0,0 @@ -using ConcreteStructs -using ConcreteStructs: _parse_head, _parse_struct_def, _parse_line -using ConcreteStructs: _strip_super, _get_subparams, _get_constructor_params -using LinearAlgebra: Adjoint -using Suppressor -using Test - - -# # Unit Tests - -# Test setup -line_number_node = LineNumberNode(1) -annotated_abstract = :(b::AbstractFloat) -annotated_concrete = :(b::Float64) -struct_name = :(MyStruct) -sub_typed_params = :(MyStruct{T1, T2 <: AbstractVector{T1}}) -sub_typed = :(MyStruct{D} <: Number) -kitchen_sink = :(MyStruct{T1, T2 <: AbstractVector{T1}} <: AbstractVector{T1}) -plain_assignment = :(a = 1) -annotated_assignment = :(a::Float64 = 2.0) -subtype_annotated_assignment = :(a <: Real = 2.0) - - -# Run tests -@testset "Unit tests" begin - @testset "_parse_line" begin - @test _parse_line(line_number_node) == (line_number_node, nothing) - # @test _parse_line(annotated_abstract)[1].args[2] != :AbstractFloat - # @test _parse_line(annotated_abstract)[2].args[2] == :AbstractFloat - @test _parse_line(annotated_concrete)[1].args[2] == :Float64 - @test _parse_line(annotated_concrete)[2] === nothing - - parsed_sym = _parse_line(:a) - @test parsed_sym[1].args[2] == parsed_sym[2] - - @test _parse_line(plain_assignment)[1].head == :(=) - @test _parse_line(plain_assignment)[1].args[1].args[2] == _parse_line(plain_assignment)[2] - @test _parse_line(annotated_assignment)[1].head == :(=) - @test _parse_line(annotated_assignment)[2] === nothing - @test _parse_line(subtype_annotated_assignment)[1].head == :(=) - @test _parse_line(subtype_annotated_assignment)[2].args[2] == :Real - end - - @testset "_parse_struct_def" begin - @test _parse_struct_def(struct_name) == (struct_name, []) - @test _parse_struct_def(sub_typed_params) == (struct_name, [:T1, :(T2 <: AbstractVector{T1})]) - end - - @testset "_parse_head" begin - @test _parse_head(struct_name) == (struct_name, [], :Any) - @test _parse_head(sub_typed) == (struct_name, Any[:D], :Number) - @test _parse_head(kitchen_sink) == (struct_name, [:T1, :(T2 <: AbstractVector{T1})], :(AbstractVector{T1})) - end - - @testset "_strip_super" begin - @test _strip_super(struct_name) == struct_name - @test _strip_super(kitchen_sink) == :(MyStruct{T1, T2 <: AbstractVector{T1}}) - @test _strip_super([struct_name, sub_typed]) == [struct_name, :(MyStruct{D})] - end - - @testset "_get_subparams" begin - @test _get_subparams(struct_name) == [] - @test _get_subparams([:T1, :(T2 <: AbstractArray{T1, N}), :(T3 <: Complex{T1})]) == Any[:T1, :N, :T1] - end - - @testset "_get_constructor_params" begin - @test _get_constructor_params([:T, :(A <: AbstractVector{T})], [:A, :B]) == [] - @test _get_constructor_params([:iip, :T, :(A <: AbstractVector{T})], [:A, :B]) == [:iip] - @test _get_constructor_params([:iip, :T, :(A <: AbstractVector{T}), :B], [:B]) == [:iip, :A] - end -end - - -# # End-to-end tests - -# Test setup -@concrete struct Plain end -plain = Plain() - -@concrete terse struct PlainTerse end -plain_terse = PlainTerse() - -"test" -@concrete struct PlainDoc end -plain_doc = PlainDoc() - -"test" -@concrete terse struct PlainTerseDoc end -plain_terse_doc = PlainTerseDoc() - -@concrete struct Args - a - b -end -args = Args(1 + im, "hi") - -@concrete terse struct ArgsTerse <: Number - a - b -end -args_terse = ArgsTerse(1 + im, "hi") - -@concrete mutable struct SubtypedMutable <: Number - a - b -end -subtyped_mutable = SubtypedMutable(3.0, 4.0f0) - -@concrete struct Partial{A} - # Hey there, I'm a comment - a::A - - "And I'm a docsctring" - b -end -partial = Partial(:yo, 1 // 2) - -@concrete mutable struct ConstructorMutable{iip, C} - a - b - c::C -end -ConstructorMutable(a, b, c) = ConstructorMutable{true}(a, b, eltype(a)(c)) -constructor_mutable = ConstructorMutable([1.0 + im, 2], 'r', 1.5) -constructor_mutable.b = 'h' - -@concrete terse struct TerseSameType{A} - a::A - b::A -end -function TerseSameType(a::A, b::B) where {A, B} - T = promote_type(A, B) - return TerseSameType{T}(T(a), T(b)) -end -terse_same_type = TerseSameType(1 + im, 5.0f0) - -@concrete terse struct FullyParameterized{B} - a::Symbol - b::B -end -fully_parameterized = FullyParameterized(:sine, sin) - -@concrete mutable struct ParameterizedSubtyped{T, N, B <: AbstractArray{T, N}} <: AbstractArray{T, N} - a - b::B - c::T -end -parameterized_subtyped = ParameterizedSubtyped(:🏦, [1, 2, 3], 4) -Base.size(x::ParameterizedSubtyped) = size(x.b) -Base.getindex(x::ParameterizedSubtyped, i...) = x.b[i...] - -@concrete terse struct HangingTypeParam{iip} - a - b -end -hanging_type_param = HangingTypeParam{true}(1, 2.0) - -@concrete terse struct HangingTypeParam2{iip, B} - a - b::B -end -hanging_type_param2 = HangingTypeParam2{true}(1, 2.0) - - -Base.@kwdef @concrete struct KWDef - a = "ayyy" - b::Float64 = 4.0 - c <: Real = 3 - d - e::Float64 - f <: Real -end -kwdef = KWDef(d = "hi", e = 2.0, f = 1 // 2) - - -# Run tests -@testset "End-to-end tests" begin - @test_throws ErrorException args.a = 2 + im - @test_throws MethodError subtyped_mutable.a = "hi" - - @test typeof(partial) |> isconcretetype - @test typeof(terse_same_type.a) === typeof(terse_same_type.b) - @test typeof(fully_parameterized.a) |> isconcretetype - @test eltype(parameterized_subtyped.b) === typeof(parameterized_subtyped.c) - @test typeof(kwdef) |> isconcretetype - - @test @capture_out(show(stdout, MIME("text/plain"), typeof(fully_parameterized))) == "FullyParameterized{typeof(sin)}" - @test @capture_out(show(stdout, FullyParameterized)) == "FullyParameterized" - @test @capture_out(show(stdout, hanging_type_param)) == "HangingTypeParam{true}(1, 2.0)" - @test @capture_out(show(stdout, MIME("text/plain"), ArgsTerse)) == "ArgsTerse" -end - -@testset "Issue #3" begin - @concrete struct Foo{T <: Real} - x::T - y <: Real - z <: AbstractMatrix{T} - end - - foo = Foo(1, 1.0, [1, 2]') - - @test typeof(foo) === Foo{Int, Float64, Adjoint{Int, Vector{Int}}} -end - - -@testset "Commutation with other Macros" begin - # this has been working for a while already - Base.@kwdef @concrete struct Foo2{T <: Real} - x::T = 1.0 - y = true - z = 42 - end - - foo = Foo2() - @test typeof(foo) === Foo2{Float64, Bool, Int64} - - # this is new - @concrete Base.@kwdef struct Foo3{T <: Real} - x::T = 1.0 - y = true - z = 42 - end - - foo = Foo3() - @test typeof(foo) === Foo3{Float64, Bool, Int64} - - foo = Foo3(z = [1, 2, 3]) - @test typeof(foo) === Foo3{Float64, Bool, Vector{Int64}} - - # terse test case, unfortunately doesn't work the other way around. But also never did. - @concrete terse Base.@kwdef struct TerseKWDef - a = 1.0 - b = true - end - terse_kw_def = TerseKWDef(b = false) - @test typeof(terse_kw_def) === TerseKWDef{Float64, Bool} -end diff --git a/test/end_to_end_tests.jl b/test/end_to_end_tests.jl new file mode 100644 index 0000000..29b5b47 --- /dev/null +++ b/test/end_to_end_tests.jl @@ -0,0 +1,119 @@ +using ConcreteStructs +using LinearAlgebra: Adjoint +using Suppressor +using Test + +# Test setup +@concrete struct Plain end +plain = Plain() + +@concrete terse struct PlainTerse end +plain_terse = PlainTerse() + +"test" +@concrete struct PlainDoc end +plain_doc = PlainDoc() + +"test" +@concrete terse struct PlainTerseDoc end +plain_terse_doc = PlainTerseDoc() + +@concrete struct Args + a + b +end +args = Args(1 + im, "hi") + +@concrete terse struct ArgsTerse <: Number + a + b +end +args_terse = ArgsTerse(1 + im, "hi") + +@concrete mutable struct SubtypedMutable <: Number + a + b +end +subtyped_mutable = SubtypedMutable(3.0, 4.0f0) + +@concrete struct Partial{A} + # Hey there, I'm a comment + a::A + + "And I'm a docsctring" + b +end +partial = Partial(:yo, 1 // 2) + +@concrete mutable struct ConstructorMutable{iip, C} + a + b + c::C +end +ConstructorMutable(a, b, c) = ConstructorMutable{true}(a, b, eltype(a)(c)) +constructor_mutable = ConstructorMutable([1.0 + im, 2], 'r', 1.5) +constructor_mutable.b = 'h' + +@concrete terse struct TerseSameType{A} + a::A + b::A +end +function TerseSameType(a::A, b::B) where {A, B} + T = promote_type(A, B) + return TerseSameType{T}(T(a), T(b)) +end +terse_same_type = TerseSameType(1 + im, 5.0f0) + +@concrete terse struct FullyParameterized{B} + a::Symbol + b::B +end +fully_parameterized = FullyParameterized(:sine, sin) + +@concrete mutable struct ParameterizedSubtyped{T, N, B <: AbstractArray{T, N}} <: AbstractArray{T, N} + a + b::B + c::T +end +parameterized_subtyped = ParameterizedSubtyped(:🏦, [1, 2, 3], 4) +Base.size(x::ParameterizedSubtyped) = size(x.b) +Base.getindex(x::ParameterizedSubtyped, i...) = x.b[i...] + +@concrete terse struct HangingTypeParam{iip} + a + b +end +hanging_type_param = HangingTypeParam{true}(1, 2.0) + +@concrete terse struct HangingTypeParam2{iip, B} + a + b::B +end +hanging_type_param2 = HangingTypeParam2{true}(1, 2.0) + + +Base.@kwdef @concrete struct KWDef + a = "ayyy" + b::Float64 = 4.0 + c <: Real = 3 + d + e::Float64 + f <: Real +end +kwdef = KWDef(d = "hi", e = 2.0, f = 1 // 2) + + +# Run tests +@test_throws ErrorException args.a = 2 + im +@test_throws MethodError subtyped_mutable.a = "hi" + +@test typeof(partial) |> isconcretetype +@test typeof(terse_same_type.a) === typeof(terse_same_type.b) +@test typeof(fully_parameterized.a) |> isconcretetype +@test eltype(parameterized_subtyped.b) === typeof(parameterized_subtyped.c) +@test typeof(kwdef) |> isconcretetype + +@test @capture_out(show(stdout, MIME("text/plain"), typeof(fully_parameterized))) == "FullyParameterized{typeof(sin)}" +@test @capture_out(show(stdout, FullyParameterized)) == "FullyParameterized" +@test @capture_out(show(stdout, hanging_type_param)) == "HangingTypeParam{true}(1, 2.0)" +@test @capture_out(show(stdout, MIME("text/plain"), ArgsTerse)) == "ArgsTerse" diff --git a/test/invalid_usage_tests.jl b/test/invalid_usage_tests.jl new file mode 100644 index 0000000..fb74ae1 --- /dev/null +++ b/test/invalid_usage_tests.jl @@ -0,0 +1,8 @@ +using ConcreteStructs +using Test + +# `@concrete` applied to something that is neither a struct definition nor a +# macrocall wrapping one reaches `_find_struct_def`'s fallthrough, which must +# raise a clear `ErrorException` (previously a `nerror` typo made this throw +# an `UndefVarError` instead). +@test_throws "Invalid usage of @concrete." @macroexpand(@concrete x = 1) diff --git a/test/issue_3.jl b/test/issue_3.jl new file mode 100644 index 0000000..ef609b3 --- /dev/null +++ b/test/issue_3.jl @@ -0,0 +1,13 @@ +using ConcreteStructs +using LinearAlgebra: Adjoint +using Test + +@concrete struct Foo{T <: Real} + x::T + y <: Real + z <: AbstractMatrix{T} +end + +foo = Foo(1, 1.0, [1, 2]') + +@test typeof(foo) === Foo{Int, Float64, Adjoint{Int, Vector{Int}}} diff --git a/test/qa/Project.toml b/test/qa/Project.toml index f38d12c..8ead566 100644 --- a/test/qa/Project.toml +++ b/test/qa/Project.toml @@ -2,6 +2,8 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" ConcreteStructs = "2569d6c7-a4a2-43d3-a901-331e8e4be471" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" +SciMLTesting = "09d9d899-5365-40a9-917a-5f67fddea283" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [sources] @@ -10,5 +12,7 @@ ConcreteStructs = {path = "../.."} [compat] Aqua = "0.8" JET = "0.9, 0.10, 0.11" +SafeTestsets = "0.0.1, 0.1" +SciMLTesting = "1" Test = "1" julia = "1.10" diff --git a/test/runtests.jl b/test/runtests.jl index 1482063..a18a7cc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,23 +1,2 @@ -using Pkg - -const GROUP = get(ENV, "GROUP", "All") - -if GROUP == "All" || GROUP == "Core" - include("core.jl") -end - -if GROUP == "All" || GROUP == "QA" - # The QA group carries its own dependencies (Aqua, JET) in test/qa/Project.toml. - Pkg.activate(joinpath(@__DIR__, "qa")) - Pkg.develop(PackageSpec(path = joinpath(@__DIR__, ".."))) - Pkg.instantiate() - include(joinpath(@__DIR__, "qa", "qa.jl")) -end - -@testset "Invalid usage error path" begin - # `@concrete` applied to something that is neither a struct definition nor a - # macrocall wrapping one reaches `_find_struct_def`'s fallthrough, which must - # raise a clear `ErrorException` (previously a `nerror` typo made this throw - # an `UndefVarError` instead). - @test_throws "Invalid usage of @concrete." @macroexpand(@concrete x = 1) -end +using SciMLTesting +run_tests() diff --git a/test/unit_tests.jl b/test/unit_tests.jl new file mode 100644 index 0000000..139ea57 --- /dev/null +++ b/test/unit_tests.jl @@ -0,0 +1,61 @@ +using ConcreteStructs: _parse_head, _parse_struct_def, _parse_line +using ConcreteStructs: _strip_super, _get_subparams, _get_constructor_params +using Test + +# Test setup +line_number_node = LineNumberNode(1) +annotated_abstract = :(b::AbstractFloat) +annotated_concrete = :(b::Float64) +struct_name = :(MyStruct) +sub_typed_params = :(MyStruct{T1, T2 <: AbstractVector{T1}}) +sub_typed = :(MyStruct{D} <: Number) +kitchen_sink = :(MyStruct{T1, T2 <: AbstractVector{T1}} <: AbstractVector{T1}) +plain_assignment = :(a = 1) +annotated_assignment = :(a::Float64 = 2.0) +subtype_annotated_assignment = :(a <: Real = 2.0) + +@testset "_parse_line" begin + @test _parse_line(line_number_node) == (line_number_node, nothing) + # @test _parse_line(annotated_abstract)[1].args[2] != :AbstractFloat + # @test _parse_line(annotated_abstract)[2].args[2] == :AbstractFloat + @test _parse_line(annotated_concrete)[1].args[2] == :Float64 + @test _parse_line(annotated_concrete)[2] === nothing + + parsed_sym = _parse_line(:a) + @test parsed_sym[1].args[2] == parsed_sym[2] + + @test _parse_line(plain_assignment)[1].head == :(=) + @test _parse_line(plain_assignment)[1].args[1].args[2] == _parse_line(plain_assignment)[2] + @test _parse_line(annotated_assignment)[1].head == :(=) + @test _parse_line(annotated_assignment)[2] === nothing + @test _parse_line(subtype_annotated_assignment)[1].head == :(=) + @test _parse_line(subtype_annotated_assignment)[2].args[2] == :Real +end + +@testset "_parse_struct_def" begin + @test _parse_struct_def(struct_name) == (struct_name, []) + @test _parse_struct_def(sub_typed_params) == (struct_name, [:T1, :(T2 <: AbstractVector{T1})]) +end + +@testset "_parse_head" begin + @test _parse_head(struct_name) == (struct_name, [], :Any) + @test _parse_head(sub_typed) == (struct_name, Any[:D], :Number) + @test _parse_head(kitchen_sink) == (struct_name, [:T1, :(T2 <: AbstractVector{T1})], :(AbstractVector{T1})) +end + +@testset "_strip_super" begin + @test _strip_super(struct_name) == struct_name + @test _strip_super(kitchen_sink) == :(MyStruct{T1, T2 <: AbstractVector{T1}}) + @test _strip_super([struct_name, sub_typed]) == [struct_name, :(MyStruct{D})] +end + +@testset "_get_subparams" begin + @test _get_subparams(struct_name) == [] + @test _get_subparams([:T1, :(T2 <: AbstractArray{T1, N}), :(T3 <: Complex{T1})]) == Any[:T1, :N, :T1] +end + +@testset "_get_constructor_params" begin + @test _get_constructor_params([:T, :(A <: AbstractVector{T})], [:A, :B]) == [] + @test _get_constructor_params([:iip, :T, :(A <: AbstractVector{T})], [:A, :B]) == [:iip] + @test _get_constructor_params([:iip, :T, :(A <: AbstractVector{T}), :B], [:B]) == [:iip, :A] +end