Skip to content

Commit cf53adf

Browse files
authored
laws testing: add insert/delete, generalize modify (#136)
* combine insert & delete tests into one file * add test_insertdelete_laws * generalize test_modify_law * extend docstrings * call test_modify_law
1 parent 48fa640 commit cf53adf

File tree

9 files changed

+138
-87
lines changed

9 files changed

+138
-87
lines changed

ext/AccessorsTestExt.jl

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,25 @@ function Accessors.test_getset_laws(lens, obj, val1, val2; cmp=(==))
1616
obj12 = set(obj1, lens, val2)
1717
obj2 = set(obj12, lens, val2)
1818
@test cmp(obj12, obj2)
19+
20+
Accessors.test_modify_law(identity, lens, obj; cmp)
1921
end
2022

21-
function Accessors.test_modify_law(f, lens, obj)
23+
function Accessors.test_modify_law(f, lens, obj; cmp=(==))
2224
obj_modify = modify(f, obj, lens)
23-
old_val = lens(obj)
24-
val = f(old_val)
25-
obj_setfget = set(obj, lens, val)
26-
@test obj_modify == obj_setfget
25+
old_vals = getall(obj, lens)
26+
vals = map(f, old_vals)
27+
obj_setfget = setall(obj, lens, vals)
28+
@test cmp(obj_modify, obj_setfget)
29+
end
30+
31+
function Accessors.test_insertdelete_laws(lens, obj, val; cmp=(==))
32+
obj1 = insert(obj, lens, val)
33+
@test cmp(lens(obj1), val)
34+
obj2 = set(obj1, lens, val)
35+
@test cmp(obj1, obj2)
36+
obj3 = delete(obj1, lens)
37+
@test cmp(obj, obj3)
2738
end
2839

2940
function Accessors.test_getsetall_laws(optic, obj, vals1, vals2; cmp=(==))
@@ -39,6 +50,8 @@ function Accessors.test_getsetall_laws(optic, obj, vals1, vals2; cmp=(==))
3950
obj12 = setall(obj1, optic, vals2)
4051
obj2 = setall(obj12, optic, vals2)
4152
@test obj12 == obj2
53+
54+
Accessors.test_modify_law(identity, optic, obj; cmp)
4255
end
4356

4457
end

src/optics.jl

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,20 @@ function modify end
3434
3535
Replace a part according to `optic` of `obj` by `val`.
3636
37+
For a callable `optic`, this law defines the `set` operation: `optic(set(obj, optic, val)) == val` (for an appropriate notion of equality).
38+
3739
```jldoctest
3840
julia> using Accessors
3941
4042
julia> obj = (a=1, b=2); lens=@optic _.a; val = 100;
4143
4244
julia> set(obj, lens, val)
4345
(a = 100, b = 2)
46+
47+
julia> lens = Elements();
48+
49+
julia> set(obj, lens, val)
50+
(a = 100, b = 100)
4451
```
4552
See also [`modify`](@ref).
4653
"""
@@ -51,14 +58,29 @@ function set end
5158
5259
Delete a part according to `optic` of `obj`.
5360
61+
Note that `optic(delete(obj, optic))` can still have a valid value: for example, when deleting an element from a `Tuple` or `Vector`.
62+
5463
```jldoctest
5564
julia> using Accessors
5665
5766
julia> obj = (a=1, b=2); lens=@optic _.a;
5867
59-
julia> delete(obj, lens)
68+
julia> obj_d = delete(obj, lens)
6069
(b = 2,)
70+
71+
julia> lens(obj_d)
72+
ERROR: type NamedTuple has no field a
73+
74+
75+
julia> obj = (1, 2); lens=first;
76+
77+
julia> obj_d = delete(obj, lens)
78+
(2,)
79+
80+
julia> lens(obj_d)
81+
2
6182
```
83+
See also [`set`](@ref), [`insert`](@ref).
6284
"""
6385
function delete end
6486

@@ -67,6 +89,8 @@ function delete end
6789
6890
Insert a part according to `optic` into `obj` with the value `val`.
6991
92+
For a callable `optic`, this law defines the `insert` operation: `optic(insert(obj, optic, val)) == val` (for an appropriate notion of equality).
93+
7094
```jldoctest
7195
julia> using Accessors
7296
@@ -75,7 +99,7 @@ julia> obj = (a=1, b=2); lens=@optic _.c; val = 100;
7599
julia> insert(obj, lens, val)
76100
(a = 1, b = 2, c = 100)
77101
```
78-
See also [`set`](@ref).
102+
See also [`set`](@ref), [`delete`](@ref).
79103
"""
80104
function insert end
81105

src/testing.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
function test_getset_laws end
44
function test_modify_law end
55
function test_getsetall_laws end
6+
function test_insertdelete_laws end

test/runtests.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ PerformanceTestTools.@include("perf.jl")
88
include("test_examples.jl")
99
include("test_core.jl")
1010
include("test_optics.jl")
11-
include("test_delete.jl")
12-
include("test_insert.jl")
11+
include("test_insert_delete.jl")
1312
include("test_extensions.jl")
1413
include("test_quicktypes.jl")
1514
include("test_setmacro.jl")

test/test_extensions.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module TestExtensions
22
using Test
33
using Accessors
4-
using Accessors: test_getset_laws
4+
using Accessors: test_getset_laws, test_insertdelete_laws
55
using AxisKeys
66
using IntervalSets
77
using StaticArrays, StaticNumbers
@@ -122,6 +122,9 @@ end
122122
end
123123
test_getset_laws(SVector, (0, 1), SVector('x', 'y'), SVector(1, 2); cmp=cmp)
124124
test_getset_laws(MVector, (0, 1), MVector('x', 'y'), MVector(1, 2); cmp=cmp)
125+
126+
test_insertdelete_laws((@optic _[1]), SVector(1), 2)
127+
test_insertdelete_laws((@optic _[2]), SVector(1), 2)
125128
end
126129

127130

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

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

165170
s = StructArray([S(1, 2), S(3, 4)])
166171
@test @inferred(set(s, PropertyLens(:a), 10:11))::StructArray == StructArray([S(10, 2), S(11, 4)])

test/test_functionlenses.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ using Unitful
55
using LinearAlgebra: norm, diag
66
using AxisKeys
77
using InverseFunctions: inverse
8-
using Accessors: test_getset_laws, test_modify_law
8+
using Accessors: test_getset_laws, test_modify_law, test_insertdelete_laws
99
using Accessors
1010

1111

@@ -159,6 +159,7 @@ end
159159
B = @insert size(A)[2] = 1
160160
@test reshape(A, (2, 1, 3)) == B
161161
@test A == @delete size(B)[2]
162+
test_insertdelete_laws((@optic size(_)[2]), A, 1)
162163
@test_throws Exception @set size(A)[1] = 1
163164
@test_throws Exception @insert size(A)[2] = 2
164165

test/test_getsetall.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module TestGetSetAll
22
using Test
33
using Accessors
4-
using Accessors: test_getsetall_laws
4+
using Accessors: test_getsetall_laws, test_modify_law
55
using StaticNumbers
66
using StaticArrays
77

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

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

test/test_insert.jl

Lines changed: 0 additions & 70 deletions
This file was deleted.

test/test_delete.jl renamed to test/test_insert_delete.jl

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,73 @@
1-
module TestDelete
1+
module TestInsertDelete
22
using Test
3-
using Accessors
43
using StaticArrays
4+
using Accessors
5+
using Accessors: insert
6+
using Accessors: test_insertdelete_laws
7+
8+
9+
@testset "test insert" begin
10+
@testset "function" begin
11+
@test @inferred(insert( (b=2, c=3), @optic(_.a), 1 )) == (b=2, c=3, a=1)
12+
@test insert( (b=2, c=3), @optic(_[:a]), 1 ) == (b=2, c=3, a=1)
13+
let A = [1, 2]
14+
@test insert(A, @optic(_[2]), 3) == [1, 3, 2]
15+
@test_throws BoundsError insert(A, @optic(_[4]), 3)
16+
@test_throws Exception insert(A, @optic(_[1, 3]), 3)
17+
@test insert(A, first, 3) == [3, 1, 2]
18+
@test insert(A, @optic(first(_, 2)), [3, 4]) == [3, 4, 1, 2]
19+
@test insert(A, @optic(last(_, 2)), [3, 4]) == [1, 2, 3, 4]
20+
@test A == [1, 2] # not changed
21+
end
22+
@test @inferred(insert(CartesianIndex(1, 2, 3), @optic(_[2]), 4)) == CartesianIndex(1, 4, 2, 3)
23+
@test insert((1,2), last, 3) == (1, 2, 3)
24+
@inferred(insert((1,2), last, 3))
25+
@test @inferred(insert(SVector(1,2), @optic(_[1]), 3)) == SVector(3, 1, 2)
26+
@test @inferred(insert(SVector(1,2), last, 3)) == SVector(1, 2, 3)
27+
let D = Dict(:a => 1)
28+
@test insert(D, @optic(_[:b]), 2) == Dict(:a => 1, :b => 2)
29+
@test D == Dict(:a => 1) # not changed
30+
end
31+
@test insert((a=1, b=(2, 3)), @optic(_.b[2]), "xxx") === (a=1, b=(2, "xxx", 3))
32+
@test_broken begin
33+
@inferred insert((a=1, b=(2, 3)), @optic(_.b[2]), "xxx")
34+
true
35+
end
36+
@test @inferred(insert((1, 2), @optic(_[1]), 3)) == (3, 1, 2)
37+
end
38+
39+
@testset "macro" begin
40+
x = (b=2, c=3)
41+
@test @insert(x.a = 1) === (b=2, c=3, a=1)
42+
@test @insert(x[(:a, :x)] = (1, :xyz)) === (b=2, c=3, a=1, x=:xyz)
43+
@test @insert(x[(:a, :x)] = (x=:xyz, a=1)) === (b=2, c=3, a=1, x=:xyz)
44+
x = [1, 2]
45+
@test @insert(x[3] = 3) == [1, 2, 3]
46+
x = (a=(b=(1, 2),), c=1)
47+
@test @insert(x.a.b[1] = 0) == (a=(b=(0, 1, 2),), c=1)
48+
49+
# inferred & constant-propagated:
50+
function doit(nt)
51+
nt = @delete nt[1]
52+
nt = @insert nt[:a] =1
53+
nt = @delete nt[(:a, :c)]
54+
nt = @insert nt[(:x, :y)] = ("def", :abc)
55+
return nt
56+
end
57+
@test @inferred(doit((a='3', b=2, c="1"))) === (b=2, x="def", y=:abc)
58+
59+
x = (1, 2)
60+
@test [@insert(x[3] = 3)] == [(1, 2, 3)]
61+
62+
A = [(x=1, y=2), (x=3, y=4)]
63+
@test @insert(Elements()(A).z = 5) == [(x=1, y=2, z=5), (x=3, y=4, z=5)]
64+
end
65+
66+
@testset "friendly error" begin
67+
res = @test_throws ArgumentError Accessors.insertmacro(identity, :(obj.prop == val))
68+
@test occursin("obj.prop == val", res.value.msg)
69+
end
70+
end
571

672
@testset "test delete" begin
773
@testset "function" begin
@@ -73,4 +139,15 @@ using StaticArrays
73139
end
74140
end
75141

142+
@testset "insert-delete laws" begin
143+
test_insertdelete_laws((@o _.c), (a=1, b=2), "3")
144+
@testset for o in ((@o _[2]), (@o _[3]), first, last), obj in ((1, 2), [1, 2])
145+
test_insertdelete_laws(o, obj, 3)
146+
end
147+
@testset for o in ((@o _.a[2]), (@o _.a[3]), (@o first(_.a)), (@o last(_.a)))
148+
test_insertdelete_laws(o, (a=(1, 2),), "3")
149+
end
150+
test_insertdelete_laws((@o first(_, 2)), [1, 2, 3], [4, 5])
151+
end
152+
76153
end

0 commit comments

Comments
 (0)