From 33c833ffe26075f75c5aaf5bf9c479dac96c54ed Mon Sep 17 00:00:00 2001 From: rafaqz Date: Sun, 15 Oct 2023 00:16:43 +0200 Subject: [PATCH] more interface tests --- BaseInterfaces/src/BaseInterfaces.jl | 30 +++++---- BaseInterfaces/src/array.jl | 95 +++++++++++++++++++++++++--- BaseInterfaces/src/set.jl | 95 ++++++++++++++-------------- BaseInterfaces/test/runtests.jl | 26 +++++++- 4 files changed, 177 insertions(+), 69 deletions(-) diff --git a/BaseInterfaces/src/BaseInterfaces.jl b/BaseInterfaces/src/BaseInterfaces.jl index 71a5f08..8cdbf77 100644 --- a/BaseInterfaces/src/BaseInterfaces.jl +++ b/BaseInterfaces/src/BaseInterfaces.jl @@ -10,18 +10,24 @@ include("set.jl") include("array.jl") # Some example interface delarations. -@implements ArrayInterface Base.LogicalIndex # No getindex -@implements ArrayInterface{(:getindex,)} UnitRange -@implements ArrayInterface{(:getindex,)} StepRange -@implements ArrayInterface{(:getindex,:setindex,)} Array -@implements ArrayInterface{(:getindex,:setindex,)} SubArray -@implements ArrayInterface{(:getindex,:setindex,)} PermutedDimsArray -@implements ArrayInterface{(:getindex,:setindex,)} Base.ReshapedArray + +# @implements ArrayInterface Base.LogicalIndex # No getindex +@implements ArrayInterface UnitRange +@implements ArrayInterface StepRange +@implements ArrayInterface LinRange +@implements ArrayInterface Base.OneTo +@implements ArrayInterface Base.Slice +@implements ArrayInterface Base.IdentityUnitRange +@implements ArrayInterface Base.CodeUnits +@implements ArrayInterface{(:setindex!,:similar_type,:similar_eltype,:similar_size)} Array +@implements ArrayInterface{(:setindex!,:similar_type,:similar_size)} BitArray +@implements ArrayInterface{(:setindex!,)} SubArray +@implements ArrayInterface{(:setindex!,)} PermutedDimsArray +@implements ArrayInterface{(:setindex!,)} Base.ReshapedArray @implements DictInterface{(:setindex,)} Dict @implements DictInterface{(:setindex,)} IdDict -# @implements DictInterface GenericDict -# @implements DictInterface{(:setindex,)} WeakKeyDict +@implements DictInterface{(:setindex,)} WeakKeyDict @implements DictInterface Base.EnvDict @implements DictInterface Base.ImmutableDict @implements DictInterface Base.Pairs @@ -32,7 +38,9 @@ include("array.jl") @implements IterationInterface{(:reverse,)} Base.Generator @implements IterationInterface{(:reverse,:indexing,)} Tuple -@implements SetInterface Set -@implements SetInterface BitSet +# TODO add grouping to reduce the number of options +@implements SetInterface{(:copy,:empty,:emptymutable,:hasfastin,:setdiff,:intersect,:empty!,:delete!,:push!,:copymutable,:sizehint)} Set +@implements SetInterface{(:copy,:empty,:emptymutable,:hasfastin,:setdiff,:intersect,:empty!,:delete!,:push!,:copymutable,:sizehint)} BitSet +@implements SetInterface{(:empty,:emptymutable,:hasfastin,:intersect,:union,:sizehint)} Base.KeySet end diff --git a/BaseInterfaces/src/array.jl b/BaseInterfaces/src/array.jl index 555d960..389b312 100644 --- a/BaseInterfaces/src/array.jl +++ b/BaseInterfaces/src/array.jl @@ -1,16 +1,93 @@ -@interface ArrayInterface ( - mandatory = ( - iterate = A -> Interfaces.test(IterationInterface, A; show=false), - ndims = A -> ndims(A) isa Int, +#= Abstract Arrays + +Methods to implement Brief description +size(A) Returns a tuple containing the dimensions of A +getindex(A, i::Int) (if IndexLinear) Linear scalar indexing +getindex(A, I::Vararg{Int, N}) (if IndexCartesian, where N = ndims(A)) N-dimensional scalar indexing + +Optional methods Default definition Brief description +IndexStyle(::Type) IndexCartesian() Returns either IndexLinear() or IndexCartesian(). See the description below. +setindex!(A, v, i::Int) (if IndexLinear) Scalar indexed assignment +setindex!(A, v, I::Vararg{Int, N}) (if IndexCartesian, where N = ndims(A)) N-dimensional scalar indexed assignment +getindex(A, I...) defined in terms of scalar getindex Multidimensional and nonscalar indexing +setindex!(A, X, I...) defined in terms of scalar setindex! Multidimensional and nonscalar indexed assignment +iterate defined in terms of scalar getindex Iteration +length(A) prod(size(A)) Number of elements +similar(A) similar(A, eltype(A), size(A)) Return a mutable array with the same shape and element type +similar(A, ::Type{S}) similar(A, S, size(A)) Return a mutable array with the same shape and the specified element type +similar(A, dims::Dims) similar(A, eltype(A), dims) Return a mutable array with the same element type and size dims +similar(A, ::Type{S}, dims::Dims) Array{S}(undef, dims) Return a mutable array with the specified element type and size + +Non-traditional indices Default definition Brief description +axes(A) map(OneTo, size(A)) Return a tuple of AbstractUnitRange{<:Integer} of valid indices +similar(A, ::Type{S}, inds) similar(A, S, Base.to_shape(inds)) Return a mutable array with the specified indices inds (see below) +similar(T::Union{Type,Function}, inds) T(Base.to_shape(inds)) Return an array similar to T with the specified indices inds (see below) +=# + +# And arbitrary new type for array values +struct ArrayTestVal + a::Int +end + +# In case `eltype` and `ndims` have custom methods +# We should always be able to use these to mean the same thing +_eltype(::AbstractArray{T}) where T = T +_ndims(::AbstractArray{<:Any,N}) where N = N + +array_components = (; + mandatory = (; + type = A -> A isa AbstractArray, + eltype = ( + A -> eltype(A) isa Type, + A -> eltype(A) == _eltype(A), + ), + ndims = ( + A -> ndims(A) isa Int, + A -> ndims(A) == _ndims(A), + ), size = ( A -> size(A) isa NTuple{<:Any,Int}, A -> length(size(A)) == ndims(A), ), - eltype = A -> typeof(first(iterate(A))) <: eltype(A) + getindex = ( + A -> A[begin] isa eltype(A), + A -> A[map(first, axes(A))...] isa eltype(A), + ), + indexstyle = A -> IndexStyle(A) in (IndexCartesian(), IndexLinear()), ), # TODO implement all the optional conditions optional = (; - getindex = A -> true, - setindex = A -> true, - ), -) "Base Julia AbstractArray interface" + setindex! = ( + A -> length(A) > 1 || throw(ArgumentError("Test arrays must have more than one element to test setindex!")), + A -> begin + # Tests setindex! by simply swapping the first and last elements + x1 = A[begin]; x2 = A[end] + A[begin] = x2 + A[end] = x1 + A[begin] == x2 && A[end] == x1 + end, + A -> begin + fs = map(first, axes(A)) + ls = map(last, axes(A)) + x1 = A[fs...]; + x2 = A[ls...] + A[fs...] = x2 + A[ls...] = x1 + A[fs...] == x2 && A[ls...] == x1 + end, + ), + similar_type = A -> similar(A) isa typeof(A), + similar_eltype = A -> begin + A1 = similar(A, ArrayTestVal) + eltype(A1) == ArrayTestVal && _wrappertype(A) == _wrappertype(A1) + end, + similar_size = A -> begin + A1 = similar(A, (2, 3)) + size(A1) == (2, 3) && _wrappertype(A) == _wrappertype(A1) + end, + ) +) + +_wrappertype(A) = Base.typename(typeof(A)).wrapper + +@interface ArrayInterface array_components "Base Julia AbstractArray interface" diff --git a/BaseInterfaces/src/set.jl b/BaseInterfaces/src/set.jl index 9dcafa2..1a0031c 100644 --- a/BaseInterfaces/src/set.jl +++ b/BaseInterfaces/src/set.jl @@ -2,59 +2,60 @@ # Requirements for AbstractSet subtypes: -mandatory = ( - type = s -> s isa AbstractSet, - eltype = "elements eltype of set `s` are subtypes of `eltype(s)`" => s -> typeof(first(iterate(s))) <: eltype(s), - length = "set defines length and test object has length larger than zero" => s -> length(s) isa Int && length(s) > 0, - iteration = "follows the IterationInterface" => x -> Interfaces.test(IterationInterface, x; show=false), - copy = s -> begin - s1 = copy(s) - s1 !== s && s1 isa typeof(s) && collect(s) == collect(s1) - end, - in = "`in` is true for elements in set" => s -> begin - all(x -> in(x, s), s) - end, -) - -optional = ( - empty = "returns a empty set able to hold elements of type U" => s -> begin - s1 = Base.empty(s) - eltype(s1) == eltype(s) || return false - s1 = Base.empty(s, Int) - eltype(s1) == Int || return false - end, - emptymutable = "returns a empty set able to hold elements of type U" => s -> begin - s1 = Base.empty(s) - s1 isa typeof(s) - eltype(s1) == eltype(s) || return false - s1 = Base.empty(s, Int) - eltype(s1) == Int || return false - end, - hasfastin = s -> Base.hasfastin(s) isa Bool, - setdiff = s -> begin - sd = setdiff(s, collect(s)) - typeof(sd) == typeof(s) && length(sd) == 0 - end, - intersect = s -> intesect(s, itr), - union = s -> union(s, itr), - mutable = ( - "empty! removes all elements from the set" => s -> empty!(s), - "delete! removes element valued x of the set" => s -> begin +set_components = (; + mandatory = (; + type = s -> s isa AbstractSet, + eltype = "elements eltype of set `s` are subtypes of `eltype(s)`" => s -> typeof(first(iterate(s))) <: eltype(s), + length = "set defines length and test object has length larger than zero" => s -> length(s) isa Int && length(s) > 0, + iteration = "follows the IterationInterface" => x -> Interfaces.test(IterationInterface, x; show=false), + in = "`in` is true for elements in set" => s -> begin + all(x -> in(x, s), s) + end, + ), + optional = (; + copy = s -> begin + s1 = copy(s) + s1 !== s && s1 isa typeof(s) && collect(s) == collect(s1) + end, + empty = "returns a empty set able to hold elements of type U" => s -> begin + s1 = Base.empty(s) + eltype(s1) == eltype(s) || return false + s1 = Base.empty(s, Int) + eltype(s1) == Int || return false + end, + emptymutable = "returns a empty set able to hold elements of type U" => s -> begin + s1 = Base.empty(s) + s1 isa typeof(s) + eltype(s1) == eltype(s) || return false + s1 = Base.empty(s, Int) + eltype(s1) == Int || return false + end, + hasfastin = s -> Base.hasfastin(s) isa Bool, + setdiff = s -> begin + sd = setdiff(s, collect(s)) + typeof(sd) == typeof(s) && length(sd) == 0 + end, + intersect = s -> intersect(s, s) == s, # TODO + union = s -> union(s, s) == s, # TODO + empty! = "empty! removes all elements from the set" => s -> length(empty!(s)) == 0, + delete! = "delete! removes element valued x of the set" => s -> begin x = first(iterate(s)) !in(delete!(s, x), x) end, - "push! adds element x to the set" => s -> begin + push! = "push! adds element x to the set" => s -> begin + # TODO do this without delete! x = first(iterate(s)) delete!(s, x) push!(s, x) - in(s, x) + in(x, s) end, - ), - coppymutable = s -> begin - s1 = Base.copymutable(s) - s1 isa typeof(s) && s1 !== s - end, - sizehint = s -> (sizehint!(s, n); true), + copymutable = s -> begin + s1 = Base.copymutable(s) + s1 isa typeof(s) && s1 !== s + end, + # TODO is there anything we can actually test here? + sizehint = s -> (sizehint!(s, 10); true), + ) ) -@interface SetInterface (; mandatory, optional) "The `AbstractSet` interface" +@interface SetInterface set_components "The `AbstractSet` interface" diff --git a/BaseInterfaces/test/runtests.jl b/BaseInterfaces/test/runtests.jl index 86d4218..d0acd3b 100644 --- a/BaseInterfaces/test/runtests.jl +++ b/BaseInterfaces/test/runtests.jl @@ -2,9 +2,25 @@ using BaseInterfaces using Interfaces using Test +@implements SetInterface{(:empty,:emptymutable,:hasfastin,:intersect,:union,:sizehint)} Test.GenericSet +@implements DictInterface Test.GenericDict + @testset "ArrayInterface" begin - @test Interfaces.test(ArrayInterface, Array, [[1, 2]]) - @test Interfaces.test(ArrayInterface, SubArray, [view([1, 2], 1:2)]) + @test Interfaces.test(ArrayInterface, Array, [[3, 2], ['a' 'b'; 'n' 'm']]) + @test Interfaces.test(ArrayInterface, BitArray, [BitArray([false true; true false])]) + @test Interfaces.test(ArrayInterface, SubArray, [view([7, 2], 1:2)]) + @test Interfaces.test(ArrayInterface, PermutedDimsArray, [PermutedDimsArray([7 2], (2, 1))]) + @test Interfaces.test(ArrayInterface, Base.ReshapedArray, [reshape(view([7, 2], 1:2), 2, 1)]) + @test Interfaces.test(ArrayInterface, UnitRange, [2:10]) + @test Interfaces.test(ArrayInterface, StepRange, [2:1:10]) + @test Interfaces.test(ArrayInterface, Base.OneTo, [Base.OneTo(10)]) + @test Interfaces.test(ArrayInterface, Base.Slice, [Base.Slice(100:150)]) + @test Interfaces.test(ArrayInterface, Base.IdentityUnitRange, [Base.IdentityUnitRange(100:150)]) + @test Interfaces.test(ArrayInterface, Base.CodeUnits, [codeunits("abcde")]) + # No `getindex` defined for LogicalIndex + @test_broken Interfaces.test(ArrayInterface, Base.LogicalIndex, [to_indices([1, 2, 3], ([false, true, true],))[1]]) + + # TODO test LinearAlgebra arrays and SparseArrays end @testset "DictInterface" begin @@ -13,6 +29,9 @@ end @test Interfaces.test(DictInterface, Base.EnvDict, [Arguments(d=Base.EnvDict())]) @test Interfaces.test(DictInterface, Base.ImmutableDict, [Arguments(d=Base.ImmutableDict(:a => 1, :b => 2))]) @test Interfaces.test(DictInterface, Base.Pairs, [Arguments(d=Base.pairs((a = 1, b = 2)))]) + @test Interfaces.test(DictInterface, Test.GenericDict, [Arguments(d=Test.GenericDict(Dict(:a => 1, :b => 2)), k=:c, v=3)]) + a = Ref(1); b = Ref(2) + @test Interfaces.test(DictInterface, WeakKeyDict, [Arguments(d= d = WeakKeyDict(a => 1, b => 2), k=Ref(3), v=3)]) end @testset "IterationInterface" begin @@ -26,4 +45,7 @@ end @testset "SetInterface" begin @test Interfaces.test(SetInterface, Set, [Set((1, 2))]) @test Interfaces.test(SetInterface, BitSet, [BitSet((1, 2))]) + @test Interfaces.test(SetInterface, Base.KeySet, [Base.KeySet(Dict(:a=>1, :b=>2))]) + @test Interfaces.test(SetInterface, Test.GenericSet, [Test.GenericSet(Set((1, 2)))]) + # @test Interfaces.test(SetInterface, Base.IdSet, ?) end