Skip to content

Commit

Permalink
laws testing: add insert/delete, generalize modify (#136)
Browse files Browse the repository at this point in the history
* combine insert & delete tests into one file
* add test_insertdelete_laws
* generalize test_modify_law
* extend docstrings
* call test_modify_law
  • Loading branch information
aplavin authored Feb 28, 2024
1 parent 48fa640 commit cf53adf
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 87 deletions.
23 changes: 18 additions & 5 deletions ext/AccessorsTestExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,25 @@ function Accessors.test_getset_laws(lens, obj, val1, val2; cmp=(==))
obj12 = set(obj1, lens, val2)
obj2 = set(obj12, lens, val2)
@test cmp(obj12, obj2)

Accessors.test_modify_law(identity, lens, obj; cmp)
end

function Accessors.test_modify_law(f, lens, obj)
function Accessors.test_modify_law(f, lens, obj; cmp=(==))
obj_modify = modify(f, obj, lens)
old_val = lens(obj)
val = f(old_val)
obj_setfget = set(obj, lens, val)
@test obj_modify == obj_setfget
old_vals = getall(obj, lens)
vals = map(f, old_vals)
obj_setfget = setall(obj, lens, vals)
@test cmp(obj_modify, obj_setfget)
end

function Accessors.test_insertdelete_laws(lens, obj, val; cmp=(==))
obj1 = insert(obj, lens, val)
@test cmp(lens(obj1), val)
obj2 = set(obj1, lens, val)
@test cmp(obj1, obj2)
obj3 = delete(obj1, lens)
@test cmp(obj, obj3)
end

function Accessors.test_getsetall_laws(optic, obj, vals1, vals2; cmp=(==))
Expand All @@ -39,6 +50,8 @@ function Accessors.test_getsetall_laws(optic, obj, vals1, vals2; cmp=(==))
obj12 = setall(obj1, optic, vals2)
obj2 = setall(obj12, optic, vals2)
@test obj12 == obj2

Accessors.test_modify_law(identity, optic, obj; cmp)
end

end
28 changes: 26 additions & 2 deletions src/optics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,20 @@ function modify end
Replace a part according to `optic` of `obj` by `val`.
For a callable `optic`, this law defines the `set` operation: `optic(set(obj, optic, val)) == val` (for an appropriate notion of equality).
```jldoctest
julia> using Accessors
julia> obj = (a=1, b=2); lens=@optic _.a; val = 100;
julia> set(obj, lens, val)
(a = 100, b = 2)
julia> lens = Elements();
julia> set(obj, lens, val)
(a = 100, b = 100)
```
See also [`modify`](@ref).
"""
Expand All @@ -51,14 +58,29 @@ function set end
Delete a part according to `optic` of `obj`.
Note that `optic(delete(obj, optic))` can still have a valid value: for example, when deleting an element from a `Tuple` or `Vector`.
```jldoctest
julia> using Accessors
julia> obj = (a=1, b=2); lens=@optic _.a;
julia> delete(obj, lens)
julia> obj_d = delete(obj, lens)
(b = 2,)
julia> lens(obj_d)
ERROR: type NamedTuple has no field a
julia> obj = (1, 2); lens=first;
julia> obj_d = delete(obj, lens)
(2,)
julia> lens(obj_d)
2
```
See also [`set`](@ref), [`insert`](@ref).
"""
function delete end

Expand All @@ -67,6 +89,8 @@ function delete end
Insert a part according to `optic` into `obj` with the value `val`.
For a callable `optic`, this law defines the `insert` operation: `optic(insert(obj, optic, val)) == val` (for an appropriate notion of equality).
```jldoctest
julia> using Accessors
Expand All @@ -75,7 +99,7 @@ julia> obj = (a=1, b=2); lens=@optic _.c; val = 100;
julia> insert(obj, lens, val)
(a = 1, b = 2, c = 100)
```
See also [`set`](@ref).
See also [`set`](@ref), [`delete`](@ref).
"""
function insert end

Expand Down
1 change: 1 addition & 0 deletions src/testing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
function test_getset_laws end
function test_modify_law end
function test_getsetall_laws end
function test_insertdelete_laws end
3 changes: 1 addition & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ PerformanceTestTools.@include("perf.jl")
include("test_examples.jl")
include("test_core.jl")
include("test_optics.jl")
include("test_delete.jl")
include("test_insert.jl")
include("test_insert_delete.jl")
include("test_extensions.jl")
include("test_quicktypes.jl")
include("test_setmacro.jl")
Expand Down
7 changes: 6 additions & 1 deletion test/test_extensions.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module TestExtensions
using Test
using Accessors
using Accessors: test_getset_laws
using Accessors: test_getset_laws, test_insertdelete_laws
using AxisKeys
using IntervalSets
using StaticArrays, StaticNumbers
Expand Down Expand Up @@ -122,6 +122,9 @@ end
end
test_getset_laws(SVector, (0, 1), SVector('x', 'y'), SVector(1, 2); cmp=cmp)
test_getset_laws(MVector, (0, 1), MVector('x', 'y'), MVector(1, 2); cmp=cmp)

test_insertdelete_laws((@optic _[1]), SVector(1), 2)
test_insertdelete_laws((@optic _[2]), SVector(1), 2)
end


Expand All @@ -139,6 +142,7 @@ VERSION >= v"1.9-" && @testset "StructArrays" begin
@test sb.:2 === 10:12
ss = @delete sb.:2
@test ss.:1 === s.:1
test_insertdelete_laws((@optic _.:2), s, 10:12)

s = StructArray(a=[1, 2, 3])
sb = @insert StructArrays.components(s).b = 10:12
Expand All @@ -161,6 +165,7 @@ VERSION >= v"1.9-" && @testset "StructArrays" begin
@test @insert(s.b = 10:11)::StructArray == [(a=(x=1, y=:abc), b=10), (a=(x=2, y=:def), b=11)]
@test @insert(s.a.z = 10:11)::StructArray == [(a=(x=1, y=:abc, z=10),), (a=(x=2, y=:def, z=11),)]
@test @delete(s.a.y)::StructArray == [(a=(x=1,),), (a=(x=2,),)]
test_insertdelete_laws((@optic _.a.z), s, ["a", "b"])

s = StructArray([S(1, 2), S(3, 4)])
@test @inferred(set(s, PropertyLens(:a), 10:11))::StructArray == StructArray([S(10, 2), S(11, 4)])
Expand Down
3 changes: 2 additions & 1 deletion test/test_functionlenses.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ using Unitful
using LinearAlgebra: norm, diag
using AxisKeys
using InverseFunctions: inverse
using Accessors: test_getset_laws, test_modify_law
using Accessors: test_getset_laws, test_modify_law, test_insertdelete_laws
using Accessors


Expand Down Expand Up @@ -159,6 +159,7 @@ end
B = @insert size(A)[2] = 1
@test reshape(A, (2, 1, 3)) == B
@test A == @delete size(B)[2]
test_insertdelete_laws((@optic size(_)[2]), A, 1)
@test_throws Exception @set size(A)[1] = 1
@test_throws Exception @insert size(A)[2] = 2

Expand Down
9 changes: 5 additions & 4 deletions test/test_getsetall.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module TestGetSetAll
using Test
using Accessors
using Accessors: test_getsetall_laws
using Accessors: test_getsetall_laws, test_modify_law
using StaticNumbers
using StaticArrays

Expand Down Expand Up @@ -155,14 +155,15 @@ end
@test_broken ([1, 2], [3.0, 4.0, 5.0], ("6",)) == @inferred setall(obj, @optic(_ |> Elements() |> Elements()), (1, 2, 3., 4., 5., "6"))
end

@testset "getall/setall consistency" begin
@testset "getall-setall laws" begin
for (optic, obj, vals1, vals2) in [
(Elements(), (1, "2"), (2, 3), (4, 5)),
(Properties(), (a=1, b="2"), (2, 3), (4, 5)),
(Elements(), (1, false), (2, 3), (4, 5)),
(Properties(), (a=1, b=false), (2, 3), (4, 5)),
(If(x -> x isa Number) Properties(), (a=1, b="2"), (2,), (4,)),
(@optic(_.b |> Elements() |> Properties() |> _ * 3), (a=1, b=((c=3, d=4), (c=5, d=6))), 1:4, (-9, -12, -15, -18)),
]
test_getsetall_laws(optic, obj, vals1, vals2)
test_modify_law(x -> x + 1, optic, obj)
end
end

Expand Down
70 changes: 0 additions & 70 deletions test/test_insert.jl

This file was deleted.

81 changes: 79 additions & 2 deletions test/test_delete.jl → test/test_insert_delete.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,73 @@
module TestDelete
module TestInsertDelete
using Test
using Accessors
using StaticArrays
using Accessors
using Accessors: insert
using Accessors: test_insertdelete_laws


@testset "test insert" begin
@testset "function" begin
@test @inferred(insert( (b=2, c=3), @optic(_.a), 1 )) == (b=2, c=3, a=1)
@test insert( (b=2, c=3), @optic(_[:a]), 1 ) == (b=2, c=3, a=1)
let A = [1, 2]
@test insert(A, @optic(_[2]), 3) == [1, 3, 2]
@test_throws BoundsError insert(A, @optic(_[4]), 3)
@test_throws Exception insert(A, @optic(_[1, 3]), 3)
@test insert(A, first, 3) == [3, 1, 2]
@test insert(A, @optic(first(_, 2)), [3, 4]) == [3, 4, 1, 2]
@test insert(A, @optic(last(_, 2)), [3, 4]) == [1, 2, 3, 4]
@test A == [1, 2] # not changed
end
@test @inferred(insert(CartesianIndex(1, 2, 3), @optic(_[2]), 4)) == CartesianIndex(1, 4, 2, 3)
@test insert((1,2), last, 3) == (1, 2, 3)
@inferred(insert((1,2), last, 3))
@test @inferred(insert(SVector(1,2), @optic(_[1]), 3)) == SVector(3, 1, 2)
@test @inferred(insert(SVector(1,2), last, 3)) == SVector(1, 2, 3)
let D = Dict(:a => 1)
@test insert(D, @optic(_[:b]), 2) == Dict(:a => 1, :b => 2)
@test D == Dict(:a => 1) # not changed
end
@test insert((a=1, b=(2, 3)), @optic(_.b[2]), "xxx") === (a=1, b=(2, "xxx", 3))
@test_broken begin
@inferred insert((a=1, b=(2, 3)), @optic(_.b[2]), "xxx")
true
end
@test @inferred(insert((1, 2), @optic(_[1]), 3)) == (3, 1, 2)
end

@testset "macro" begin
x = (b=2, c=3)
@test @insert(x.a = 1) === (b=2, c=3, a=1)
@test @insert(x[(:a, :x)] = (1, :xyz)) === (b=2, c=3, a=1, x=:xyz)
@test @insert(x[(:a, :x)] = (x=:xyz, a=1)) === (b=2, c=3, a=1, x=:xyz)
x = [1, 2]
@test @insert(x[3] = 3) == [1, 2, 3]
x = (a=(b=(1, 2),), c=1)
@test @insert(x.a.b[1] = 0) == (a=(b=(0, 1, 2),), c=1)

# inferred & constant-propagated:
function doit(nt)
nt = @delete nt[1]
nt = @insert nt[:a] =1
nt = @delete nt[(:a, :c)]
nt = @insert nt[(:x, :y)] = ("def", :abc)
return nt
end
@test @inferred(doit((a='3', b=2, c="1"))) === (b=2, x="def", y=:abc)

x = (1, 2)
@test [@insert(x[3] = 3)] == [(1, 2, 3)]

A = [(x=1, y=2), (x=3, y=4)]
@test @insert(Elements()(A).z = 5) == [(x=1, y=2, z=5), (x=3, y=4, z=5)]
end

@testset "friendly error" begin
res = @test_throws ArgumentError Accessors.insertmacro(identity, :(obj.prop == val))
@test occursin("obj.prop == val", res.value.msg)
end
end

@testset "test delete" begin
@testset "function" begin
Expand Down Expand Up @@ -73,4 +139,15 @@ using StaticArrays
end
end

@testset "insert-delete laws" begin
test_insertdelete_laws((@o _.c), (a=1, b=2), "3")
@testset for o in ((@o _[2]), (@o _[3]), first, last), obj in ((1, 2), [1, 2])
test_insertdelete_laws(o, obj, 3)
end
@testset for o in ((@o _.a[2]), (@o _.a[3]), (@o first(_.a)), (@o last(_.a)))
test_insertdelete_laws(o, (a=(1, 2),), "3")
end
test_insertdelete_laws((@o first(_, 2)), [1, 2, 3], [4, 5])
end

end

0 comments on commit cf53adf

Please sign in to comment.