Skip to content

Commit

Permalink
more interface tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaqz committed Oct 14, 2023
1 parent 6ac42c2 commit 33c833f
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 69 deletions.
30 changes: 19 additions & 11 deletions BaseInterfaces/src/BaseInterfaces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
95 changes: 86 additions & 9 deletions BaseInterfaces/src/array.jl
Original file line number Diff line number Diff line change
@@ -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"
95 changes: 48 additions & 47 deletions BaseInterfaces/src/set.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"
26 changes: 24 additions & 2 deletions BaseInterfaces/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

0 comments on commit 33c833f

Please sign in to comment.