Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

put test objects in implements and test modules #37

Merged
merged 11 commits into from
Nov 3, 2023
77 changes: 43 additions & 34 deletions BaseInterfaces/src/implementations.jl
Original file line number Diff line number Diff line change
@@ -1,43 +1,52 @@
# Some example interface delarations.

# @implements ArrayInterface Base.LogicalIndex # No getindex
@implements ArrayInterface UnitRange
@implements ArrayInterface StepRange
@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{:setindex!} WeakKeyDict
@implements DictInterface Base.EnvDict
@implements DictInterface Base.ImmutableDict

@implements ArrayInterface UnitRange [2:10]
@implements ArrayInterface StepRange [2:1:10]
@implements ArrayInterface Base.OneTo [Base.OneTo(10)]
@implements ArrayInterface Base.Slice [Base.Slice(100:150)]
@implements ArrayInterface Base.CodeUnits [codeunits("abcde")]
@implements ArrayInterface Base.IdentityUnitRange [Base.IdentityUnitRange(100:150)]
@implements ArrayInterface{(:setindex!,:similar_type,:similar_eltype,:similar_size)} Array [[3, 2], ['a' 'b'; 'n' 'm']]
@implements ArrayInterface{(:setindex!,:similar_type,:similar_size)} BitArray [BitArray([false true; true false])]
@implements ArrayInterface{:setindex!} SubArray [view([7, 2], 1:2)]
@implements ArrayInterface{:setindex!} PermutedDimsArray [PermutedDimsArray([7 2], (2, 1))]
@implements ArrayInterface{:setindex!} Base.ReshapedArray [reshape(view([7, 2], 1:2), 2, 1)]

@implements DictInterface{:setindex!} Dict [Arguments(d=Dict(:a => 1, :b => 2), k=:c, v=3)]
@implements DictInterface{:setindex!} IdDict [Arguments(d=IdDict(:a => 1, :b => 2), k=:c, v=3)]
# This errors because the ref is garbage collected
# @implements DictInterface{:setindex!} WeakKeyDict [Arguments(; d=WeakKeyDict(Ref(1) => 1, Ref(2) => 2), k=Ref(3), v=3)]
@implements DictInterface Base.EnvDict [Arguments(d=Base.EnvDict())]
@implements DictInterface Base.ImmutableDict [Arguments(d=Base.ImmutableDict(:a => 1, :b => 2))]
@static if VERSION >= v"1.9.0"
@implements DictInterface Base.Pairs
@implements DictInterface Base.Pairs [Arguments(d=Base.pairs((a = 1, b = 2)))]
end

@implements IterationInterface{(:reverse,:indexing)} UnitRange
@implements IterationInterface{(:reverse,:indexing)} StepRange
@implements IterationInterface{(:reverse,:indexing)} Array
@implements IterationInterface{(:reverse,:indexing)} Tuple
@implements IterationInterface{(:reverse,:indexing)} NamedTuple
@implements IterationInterface{(:reverse,:indexing)} String
@implements IterationInterface{(:reverse,:indexing)} Pair
@implements IterationInterface{(:reverse,:indexing)} Number
@implements IterationInterface{(:reverse,:indexing)} Base.EachLine
@implements IterationInterface{(:reverse,)} Base.Generator
@implements IterationInterface Set
@implements IterationInterface BitSet
@implements IterationInterface IdDict
@implements IterationInterface Dict
@implements IterationInterface WeakKeyDict
@implements IterationInterface{(:reverse,:indexing)} UnitRange [1:5, -2:2]
@implements IterationInterface{(:reverse,:indexing)} StepRange [1:2:10, 20:-10:-20]
@implements IterationInterface{(:reverse,:indexing)} Array [[1, 2, 3, 4], [:a :b; :c :d]]
@implements IterationInterface{(:reverse,:indexing)} Tuple [(1, 2, 3, 4)]
@static if VERSION >= v"1.9.0"
@implements IterationInterface{(:reverse,:indexing)} NamedTuple [(a=1, b=2, c=3, d=4)]
else
@implements IterationInterface{:indexing} NamedTuple [(a=1, b=2, c=3, d=4)] # No reverse on 1.6
end
# @implements IterationInterface{(:reverse,:indexing)} String
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's up with the commented ones?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They weren't actually tested before anyway 😅

Good reason for the PR in itself. Guess I should make some test data for them...

# @implements IterationInterface{(:reverse,:indexing)} Pair
# @implements IterationInterface{(:reverse,:indexing)} Number
# @implements IterationInterface{(:reverse,:indexing)} Base.EachLine
@implements IterationInterface{:reverse} Base.Generator [(i for i in 1:5), (i for i in 1:5)]
# @implements IterationInterface Set
# @implements IterationInterface BitSet
# @implements IterationInterface IdDict
# @implements IterationInterface Dict
# @implements IterationInterface WeakKeyDict

# TODO add grouping to reduce the number of options
@implements SetInterface{(:copy,:empty,:emptymutable,:hasfastin,:setdiff,:intersect,:union,:empty!,:delete!,:push!,:copymutable,:sizehint!)} Set
@implements SetInterface{(:copy,:empty,:emptymutable,:hasfastin,:setdiff,:intersect,:union,:empty!,:delete!,:push!,:copymutable,:sizehint!)} BitSet
@implements SetInterface{(:empty,:emptymutable,:hasfastin,:intersect,:union,:sizehint!)} Base.KeySet
@implements SetInterface{(:copy,:empty,:emptymutable,:hasfastin,:setdiff,:intersect,:union,:empty!,:delete!,:push!,:copymutable,:sizehint!)} Set [Set((1, 2))]
@implements SetInterface{(:copy,:empty,:emptymutable,:hasfastin,:setdiff,:intersect,:union,:empty!,:delete!,:push!,:copymutable,:sizehint!)} BitSet [BitSet((1, 2))]
@implements SetInterface{(:empty,:emptymutable,:hasfastin,:intersect,:union,:sizehint!)} Base.KeySet [Base.KeySet(Dict(:a=>1, :b=>2))]
@implements SetInterface{(:empty,:hasfastin,:intersect,:union,:sizehint!)} Base.IdSet (s = Base.IdSet(); push!(s, "a"); push!(s, "b"); [s])
59 changes: 10 additions & 49 deletions BaseInterfaces/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,16 @@ using BaseInterfaces
using Interfaces
using Test

@implements SetInterface{(:empty,:emptymutable,:hasfastin,:intersect,:union,:sizehint!)} Test.GenericSet
@implements DictInterface Test.GenericDict
@implements SetInterface{(:empty,:emptymutable,:hasfastin,:intersect,:union,:sizehint!)} Test.GenericSet [Test.GenericSet(Set((1, 2)))]
rafaqz marked this conversation as resolved.
Show resolved Hide resolved
@implements DictInterface Test.GenericDict [Arguments(d=Test.GenericDict(Dict(:a => 1, :b => 2)), k=:c, v=3)]

@testset "ArrayInterface" begin
@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]])
# Test all interfaaces in BaseInterfaces
@test Interfaces.test(BaseInterfaces)
rafaqz marked this conversation as resolved.
Show resolved Hide resolved

# TODO test LinearAlgebra arrays and SparseArrays
end
# Or test each interface in the module individually
@test Interfaces.test(ArrayInterface, BaseInterfaces)
@test Interfaces.test(DictInterface, BaseInterfaces)
@test Interfaces.test(IterationInterface, BaseInterfaces)
@test Interfaces.test(SetInterface, BaseInterfaces)

@testset "DictInterface" begin
@test Interfaces.test(DictInterface, Dict, [Arguments(d=Dict(:a => 1, :b => 2), k=:c, v=3)])
@test Interfaces.test(DictInterface, IdDict, [Arguments(d=IdDict(:a => 1, :b => 2), k=:c, v=3)])
@test Interfaces.test(DictInterface, Base.EnvDict, [Arguments(d=Base.EnvDict())])
@test Interfaces.test(DictInterface, Base.ImmutableDict, [Arguments(d=Base.ImmutableDict(:a => 1, :b => 2))])
@static if VERSION >= v"1.9.0"
@test Interfaces.test(DictInterface, Base.Pairs, [Arguments(d=Base.pairs((a = 1, b = 2)))])
end
@test Interfaces.test(DictInterface, Test.GenericDict, [Arguments(d=Test.GenericDict(Dict(:a => 1, :b => 2)), k=:c, v=3)])
GC.enable(false) # Avoid segfaults from garbage collection of WeakKeyDict keys
a = Ref(1); b = Ref(2); k=Ref(3)
@test Interfaces.test(DictInterface, WeakKeyDict, [Arguments(; d=WeakKeyDict(a => 1, b => 2), k, v=3)])
GC.enable(true)
end

@testset "IterationInterface" begin
@test Interfaces.test(IterationInterface, UnitRange, [1:5, -2:2])
@test Interfaces.test(IterationInterface, StepRange, [1:2:10, 20:-10:-20])
@test Interfaces.test(IterationInterface, Array, [[1, 2, 3, 4], [:a :b; :c :d]])
@test Interfaces.test(IterationInterface, Base.Generator, [(i for i in 1:5), (i for i in 1:5)])
@test Interfaces.test(IterationInterface, Tuple, [(1, 2, 3, 4)])
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)))])
s = Base.IdSet(); push!(s, "a"); push!(s, "b")
@test Interfaces.test(SetInterface, Base.IdSet, [s])
end
@test_broken Interfaces.test(ArrayInterface, Base.LogicalIndex, [to_indices([1, 2, 3], ([false, true, true],))[1]])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this fixable in Base Julia?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes by putting collect(LogicalIndex(I)) here

But of course that has a serious performance hit.

See: JuliaLang/julia#51071

11 changes: 6 additions & 5 deletions src/implements.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ using BaseInterfaces
@implements BaseInterfaces.IterationInterface{(:indexing,:reverse)} MyObject
```
"""
macro implements(interface, objtype)
_implements_inner(interface, objtype)
macro implements(interface, objtype, test_objects)
_implements_inner(interface, objtype, test_objects)
end
macro implements(dev::Symbol, interface, objtype)
macro implements(dev::Symbol, interface, objtype, test_objects)
dev == :dev || error("3 arg version of `@implements must start with `dev`, and should only be used in testing")
rafaqz marked this conversation as resolved.
Show resolved Hide resolved
_implements_inner(interface, objtype; show=true)
_implements_inner(interface, objtype, test_objects; show=true)
end
function _implements_inner(interface, objtype; show=false)
function _implements_inner(interface, objtype, test_objects; show=false)
rafaqz marked this conversation as resolved.
Show resolved Hide resolved
if interface isa Expr && interface.head == :curly
interfacetype = interface.args[1]
optional_keys = interface.args[2]
Expand All @@ -70,6 +70,7 @@ function _implements_inner(interface, objtype; show=false)
$Interfaces._all_in(Options, $Interfaces.optional_keys(T, O))
# Define which optional components the object implements
$Interfaces.optional_keys(::Type{<:$interfacetype}, ::Type{<:$objtype}) = $optional_keys
$Interfaces.test_objects(::Type{<:$interfacetype}, ::Type{<:$objtype}) = $test_objects
nothing
end |> esc
end
Expand Down
9 changes: 8 additions & 1 deletion src/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Components is an `Tuple` of `Symbol`.
abstract type Interface{Components} end

"""
optional_keys(T::Type{<:Interface}, obj::Type)
optional_keys(T::Type{<:Interface}, O::Type)

Get the keys for the optional components of an [`Interface`](@ref),
as a tuple os `Symbol`.
Expand All @@ -21,6 +21,13 @@ optional_keys(T::Type{<:Interface}) = keys(components(T).optional)

mandatory_keys(T::Type{<:Interface}, args...) = keys(components(T).mandatory)

"""
test_objects(T::Type{<:Interface}, O::Type)

Get the test object(s) for type `O` and interface `T`.
"""
function test_objects end

"""
description(::Type{<:Interface})

Expand Down
39 changes: 36 additions & 3 deletions src/test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,54 @@ function check_coherent_types(O::Type, tow::TestObjectWrapper)
end

"""
test(::Type{<:Interface}, obj)
test(m::Module)
test(::Type{<:Interface}, m::Module)
test(::Type{<:Interface}, obj::Type)

Test if an interface is implemented correctly for an object,
returning `true` or `false`.

If no interface type is passed, Interfaces.jl will find all the
interfaces available and test them.
rafaqz marked this conversation as resolved.
Show resolved Hide resolved
"""
function test(T::Type{<:Interface{Keys}}, O::Type, test_objects; kw...) where Keys
function test(T::Type{<:Interface}, mod::Module; kw...)
methodlist = methods(implements, Tuple{T,<:Any})
_test_module(mod, methodlist; kw...)
end
function test(mod::Module; kw...)
methodlist = methods(implements, Tuple{<:Any,<:Any})
_test_module(mod, methodlist; kw...)
end


function _test_module(mod, methodlist; kw...)
rafaqz marked this conversation as resolved.
Show resolved Hide resolved
all(methodlist) do m
m.module == mod || return true
# We make this signature in the @interface macro
# so we know it is this consistent
b = m.sig isa UnionAll ? m.sig.body : m.sig
t = b.parameters[2].var.ub
if t isa UnionAll
T = t.body.name.wrapper
else
T = t.name.wrapper
end
O = b.parameters[3].var.ub
@show T O typeof(T) typeof(O)

return test(T, O; kw...)
end
end

function test(T::Type{<:Interface{Keys}}, O::Type, test_objects=test_objects(T, O); kw...) where Keys
# Allow passing the keys in the abstract type
# But get them out and put them in the `keys` keyword
T1 = _get_type(T).name.wrapper
objs = TestObjectWrapper(test_objects)
# And run the tests on the parameterless type
return _test(T1, O, objs; keys=Keys, kw...)
end
function test(T::Type{<:Interface}, O::Type, test_objects; kw...)
function test(T::Type{<:Interface}, O::Type, test_objects=test_objects(T, O); kw...)
objs = TestObjectWrapper(test_objects)
return _test(T, O, objs; kw...)
end
Expand Down Expand Up @@ -118,7 +149,9 @@ function _test(T, name::Symbol, condition, obj, i=nothing)
obj_copy = deepcopy(obj)
res = try
f = condition isa Pair ? condition[2] : condition
# GC.enable(false)
rafaqz marked this conversation as resolved.
Show resolved Hide resolved
f(obj_copy)
# GC.enable(true)
# Allow returning a function or tuple of functions that are tested again
catch e
desc = condition isa Pair ? string(" \"", condition[1], "\"") : ""
Expand Down
4 changes: 2 additions & 2 deletions test/advanced.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Interfaces.test(Group.GroupInterface, Float64, float_pairs)

# We can thus declare proudly

@implements Group.GroupInterface Float64
@implements Group.GroupInterface Float64 [Arguments(x = 2.0, y = 1.0)]

#=
Now we check it for integer numbers.
Expand Down Expand Up @@ -99,6 +99,6 @@ In summary, there are two things to remember:

using Test #src

@test Interfaces.test(Group.GroupInterface, Float64, float_pairs) #src
@test Interfaces.test(Group.GroupInterface, Float64) #src
@test !Interfaces.test(Group.GroupInterface, Int, int_pairs) #src
@test_throws ArgumentError Interfaces.test(Group.GroupInterface, Float64, int_pairs) #src
2 changes: 1 addition & 1 deletion test/basic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The `@implements` macro takes two arguments.
2. The type for which the interface is implemented.
=#

@implements Animals.AnimalInterface{(:walk,:talk)} Duck
@implements Animals.AnimalInterface{(:walk,:talk)} Duck [Duck(1), Duck(2)]

# Now let's see what happens when the interface is not correctly implemented.
struct Chicken <: Animals.Animal end
Expand Down
Loading