Skip to content

Commit

Permalink
Moved test objects to test method
Browse files Browse the repository at this point in the history
  • Loading branch information
stemann committed May 29, 2023
1 parent 15cd55f commit 8dc5408
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 58 deletions.
10 changes: 5 additions & 5 deletions BaseInterfaces/src/BaseInterfaces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ export IterationInterface
include("iteration.jl")

# Some example interface delarations.
@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,)} Base.Generator [(i for i in 1:5), (i for i in 1:5)]
@implements IterationInterface{(:reverse,:indexing,)} Tuple [(1, 2, 3, 4)]
@implements IterationInterface{(:reverse,:indexing,)} UnitRange
@implements IterationInterface{(:reverse,:indexing,)} StepRange
@implements IterationInterface{(:reverse,:indexing,)} Array
@implements IterationInterface{(:reverse,)} Base.Generator
@implements IterationInterface{(:reverse,:indexing,)} Tuple

end
10 changes: 5 additions & 5 deletions BaseInterfaces/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ using Interfaces
using Test

@testset "BaseInterfaces.jl" begin
@test Interfaces.test(IterationInterface, UnitRange)
@test Interfaces.test(IterationInterface, StepRange)
@test Interfaces.test(IterationInterface, Array)
@test Interfaces.test(IterationInterface, Base.Generator)
@test Interfaces.test(IterationInterface, Tuple)
@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
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,10 @@ end

Now we can implement the AnimalInterface, for a Duck.

The `@implements` macro takes three arguments.
The `@implements` macro takes two arguments.
1. The interface type, with a tuple of optional components in
its first type parameter.
2. The the type of the object implementing the interface
3. Some code that defines an instance of that type that can be used in tests.
2. The type for which the interface is implemented.

```julia
using Interfaces
Expand All @@ -86,7 +85,7 @@ Animals.walk(::Duck) = "waddle"
Animals.talk(::Duck) = :quack

# And define the interface
@implements Animals.AnimalInterface{(:walk, :talk)} Duck [Duck(1), Duck(2)]
@implements Animals.AnimalInterface{(:walk, :talk)} Duck
```

Now we have some methods we can use as traits, and test the interface with:
Expand All @@ -99,17 +98,17 @@ julia> Interfaces.implements(Animals.AnimalInterface{:dig}, Duck)
false

# We can test the interface
julia> Interfaces.test(Animals.AnimalInterface, Duck)
julia> Interfaces.test(Animals.AnimalInterface, Duck, [Duck(1), Duck(2)])
true

# Or components of it:
julia> Interfaces.test(Animals.AnimalInterface{(:walk,:talk)}, Duck)
julia> Interfaces.test(Animals.AnimalInterface{(:walk,:talk)}, Duck, [Duck(1), Duck(2)])
true

# Test another object
# Test another type
struct Chicken end

julia> Interfaces.implements(Animals.AnimalInterface, Chicken())
julia> Interfaces.implements(Animals.AnimalInterface, Chicken)
false
```

Expand Down
41 changes: 9 additions & 32 deletions src/implements.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,11 @@ function implements end
implements(::Type{<:Interface}, obj) = false

"""
test_objects(::Type{<:Interface}, ::Type)
Return the test object for an `Interface` and type.
"""
function test_objects end

# Wrap objects so we don't get confused iterating
# inside the objects themselves during tests.
struct TestObjectWrapper{O}
objects::O
end

Base.iterate(tow::TestObjectWrapper, args...) = iterate(tow.objects, args...)
Base.length(tow::TestObjectWrapper, args...) = length(tow.objects)
Base.getindex(tow::TestObjectWrapper, i::Int) = getindex(tow.objects, i)

"""
@implements(interface, objtype, obj)
@implements(dev, interface, objtype, obj)
@implements(interface, objtype)
@implements(dev, interface, objtype)
Declare that an interface implements an interface, or multipleinterfaces.
Also pass an object or tuple of objects to test it with.
The macro can only be used once per module for any one type. To define
multiple interfaces a type implements, combine them in square brackets.
Expand All @@ -55,35 +36,31 @@ Here we implement the IterationInterface for Base julia, indicating with
```julia
using BaseInterfaces
@implements BaseInterfaces.IterationInterface{(:indexing,:reverse)} MyObject MyObject([1, 2, 3])
@implements BaseInterfaces.IterationInterface{(:indexing,:reverse)} MyObject
```
"""
macro implements(interface, objtype, test_objects)
_implements_inner(interface, objtype, test_objects)
macro implements(interface, objtype)
_implements_inner(interface, objtype)
end
macro implements(dev::Symbol, interface, objtype, test_objects)
dev == :dev || error("4 arg version of `@implements must start with `dev`, and should only be used in testing")
_implements_inner(interface, objtype, test_objects; show=true)
macro implements(dev::Symbol, interface, objtype)
dev == :dev || error("3 arg version of `@implements must start with `dev`, and should only be used in testing")
_implements_inner(interface, objtype; show=true)
end
function _implements_inner(interface, objtype, test_objects; show=false)
function _implements_inner(interface, objtype; show=false)
if interface isa Expr && interface.head == :curly
interfacetype = interface.args[1]
optional_keys = interface.args[2]
else
interfacetype = interface
optional_keys = ()
end
test_objects.head == :vect || error("test object must be wrapped in square brackets")
test_objects = Expr(:tuple, test_objects.args...)
quote
# Define a `implements` trait stating that `objtype` implements `interface`
Interfaces.implements(::Type{<:$interfacetype}, ::Type{<:$objtype}) = true
Interfaces.implements(T::Type{<:$interfacetype{Options}}, O::Type{<:$objtype}) where Options =
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
# Define the object to be used in interface tests
Interfaces.test_objects(::Type{<:$interfacetype}, ::Type{<:$objtype}) = Interfaces.TestObjectWrapper($test_objects)
nothing
end |> esc
end
Expand Down
17 changes: 13 additions & 4 deletions src/test.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# Wrap objects so we don't get confused iterating
# inside the objects themselves during tests.
struct TestObjectWrapper{O}
objects::O
end

Base.iterate(tow::TestObjectWrapper, args...) = iterate(tow.objects, args...)
Base.length(tow::TestObjectWrapper, args...) = length(tow.objects)
Base.getindex(tow::TestObjectWrapper, i::Int) = getindex(tow.objects, i)

"""
test(::Type{<:Interface}, obj)
Expand All @@ -8,13 +17,13 @@ returning `true` or `false`.
If no interface type is passed, Interfaces.jl will find all the
interfaces available and test them.
"""
function test(T::Type{<:Interface{Keys}}, O::Type; kw...) where Keys
function test(T::Type{<:Interface{Keys}}, O::Type, test_objects; kw...) where Keys
T1 = _get_type(T).name.wrapper
objs = test_objects(T1, O)
objs = TestObjectWrapper(test_objects)
return test(T1, O, objs; keys=Keys, kw...)
end
function test(T::Type{<:Interface}, O::Type; kw...)
objs = test_objects(T, O)
function test(T::Type{<:Interface}, O::Type, test_objects; kw...)
objs = TestObjectWrapper(test_objects)
return test(T, O, objs; kw...)
end
function test(T::Type{<:Interface}, O::Type, objs::TestObjectWrapper;
Expand Down
9 changes: 5 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,16 @@ Animals.age(duck::Duck) = duck.age
Animals.walk(::Duck) = "waddle"
Animals.talk(::Duck) = :quack

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

@testset "duck" begin
ducks = [Duck(1), Duck(2)]
@test Interfaces.implements(Animals.AnimalInterface, Duck) == true
@test Interfaces.implements(Animals.AnimalInterface{:dig}, Duck) == false
@test Interfaces.test(Animals.AnimalInterface, Duck) == true
@test Interfaces.test(Animals.AnimalInterface{(:walk,:talk)}, Duck) == true
@test Interfaces.test(Animals.AnimalInterface, Duck, ducks) == true
@test Interfaces.test(Animals.AnimalInterface{(:walk,:talk)}, Duck, ducks) == true
# TODO wrap errors somehow, or just let Invariants.jl handle that.
@test_throws MethodError Interfaces.test(Animals.AnimalInterface{:dig}, Duck)
@test_throws MethodError Interfaces.test(Animals.AnimalInterface{:dig}, Duck, ducks)
end

struct Chicken end
Expand Down

0 comments on commit 8dc5408

Please sign in to comment.