diff --git a/Project.toml b/Project.toml index 0840b53..de75e10 100644 --- a/Project.toml +++ b/Project.toml @@ -8,10 +8,4 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [compat] -julia = "1.3" - -[extras] -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[targets] -test = ["Test"] +julia = "1.6" diff --git a/src/chainedvector.jl b/src/chainedvector.jl index 7df46cd..bbe47b6 100644 --- a/src/chainedvector.jl +++ b/src/chainedvector.jl @@ -204,8 +204,10 @@ end import Base: +, -, *, <, >, <=, >=, == for f in (:+, :-, :*, :<, :>, :<=, :>=, :(==)) - @eval $f(a::ChainedVectorIndex, b::Integer) = $f(a.i, b) - @eval $f(a::Integer, b::ChainedVectorIndex) = $f(a, b.i) + for inttype in (:Integer, :BigInt) + @eval $f(a::ChainedVectorIndex, b::$inttype) = $f(a.i, b) + @eval $f(a::$inttype, b::ChainedVectorIndex) = $f(a, b.i) + end @eval $f(a::ChainedVectorIndex, b::ChainedVectorIndex) = $f(a.i, b.i) end Base.convert(::Type{T}, x::ChainedVectorIndex) where {T <: Union{Signed, Unsigned}} = convert(T, x.i) @@ -380,7 +382,9 @@ end Base.copyto!(dest::AbstractVector, src::ChainedVector) = copyto!(dest, 1, src, 1, length(src)) -Base.copyto!(dest::AbstractVector, doffs::Union{Signed, Unsigned}, src::ChainedVector) = +Base.copyto!(dest::PermutedDimsArray{T, 1}, src::ChainedVector{T, A} where {A<:AbstractVector{T}}) where {T} = + copyto!(dest, 1, src, 1, length(src)) +Base.copyto!(dest::AbstractVector, doffs::Union{Signed,Unsigned}, src::ChainedVector) = copyto!(dest, doffs, src, 1, length(src)) Base.copyto!(dest::AbstractVector, doffs::Union{Signed, Unsigned}, src::ChainedVector, soffs::Union{Signed, Unsigned}) = copyto!(dest, doffs, src, soffs, length(src) - soffs + 1) @@ -775,7 +779,9 @@ Base.any(x::ChainedVector) = any(y -> any(y), x.arrays) Base.all(f::Function, x::ChainedVector) = all(y -> all(f, y), x.arrays) Base.all(x::ChainedVector) = all(y -> all(y), x.arrays) -Base.reduce(op::OP, x::ChainedVector) where {OP} = reduce(op, (reduce(op, y) for y in x.arrays)) +for optype in (:Any, :hcat, :vcat) + @eval Base.reduce(op::typeof($optype), x::ChainedVector) = reduce(op, (reduce(op, y) for y in x.arrays)) +end Base.foldl(op::OP, x::ChainedVector) where {OP} = foldl(op, (foldl(op, y) for y in x.arrays)) Base.foldr(op::OP, x::ChainedVector) where {OP} = foldr(op, (foldr(op, y) for y in x.arrays)) Base.mapreduce(f::F, op::OP, x::ChainedVector) where {F, OP} = reduce(op, (mapreduce(f, op, y) for y in x.arrays)) @@ -900,6 +906,7 @@ function Base.findall(A::ChainedVector{Bool}) end Base.findall(f::Function, x::ChainedVector) = findall(map(f, x)) +Base.findall(f::Base.Fix2{typeof(in)}, x::ChainedVector) = findall(map(f, x)) function Base.filter(f, a::ChainedVector{T}) where {T} j = 1 @@ -927,3 +934,6 @@ Base.replace(a::ChainedVector, old_new::Pair...; count::Union{Integer,Nothing}=n Base.replace!(a::ChainedVector, old_new::Pair...; count::Integer=typemax(Int)) = (foreach(A -> replace!(A, old_new...; count=count), a.arrays); return a) Base.Broadcast.broadcasted(f::F, A::ChainedVector) where {F} = map(f, A) +function Base.Broadcast.broadcasted(s::S, c::ChainedVector) where {S<:Base.Broadcast.BroadcastStyle} + error("Broadcasting with BroadcastStyle $s and ChainedVector $c is reserved.") +end \ No newline at end of file diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 0000000..afc51cc --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,4 @@ +[deps] +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +ReTest = "e0db7c4e-2690-44b9-bad6-7687da720f89" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" diff --git a/test/SentinelArrayTests.jl b/test/SentinelArrayTests.jl new file mode 100644 index 0000000..949c9af --- /dev/null +++ b/test/SentinelArrayTests.jl @@ -0,0 +1,12 @@ +module SentinelArrayTests +using ReTest +using Random, SparseArrays + +using SentinelArrays + +include("sentinelarrays.jl") +include("missingvector.jl") +include("chainedvector.jl") +include("BufferedVectors.jl") + +end \ No newline at end of file diff --git a/test/chainedvector.jl b/test/chainedvector.jl index 93363c1..1123b54 100644 --- a/test/chainedvector.jl +++ b/test/chainedvector.jl @@ -560,3 +560,37 @@ end @test deleteat!(v2, m2) == deleteat!(s2, m2) end end + +@testset "Method ambiguities" begin + # some objects to use for testing current ambiguities + cv = ChainedVector([[1, 2], [3, 4]], [5, 6]) + cv_of_abstractvectors = ChainedVector([[1:4, 2:5], [3:6, 4:7]], [5, 6]) + pda_1dim = PermutedDimsArray(1:6 |> collect, [1,]) + sv = SparseArrays.SparseVector(6, [2, 3], [2.0, 3.0]) + fix2in = Base.Fix2(in, [1, 2]) + + @test ==(SentinelArrays.ChainedVectorIndex(1, 2, 3, 4), BigInt(21)) isa Any + @test reduce(hcat, cv_of_abstractvectors) isa Any + @test reduce(vcat, cv_of_abstractvectors) isa Any + @test_throws "reserved" Base.broadcasted(Base.Broadcast.ArrayStyle{Matrix}(), cv) + @test copyto!(pda_1dim, cv) isa Any + @test copyto!(pda_1dim, 1, cv) isa Any + @test findall(fix2in, cv) isa Any + # I think this should not be fixed by us as long as we don't import SparseArrays: + @test_throws MethodError copyto!(sv, cv) isa Any +end + +@testset "ChainedVectorIndex arithmetic" begin + # Metaprogramming defines these operations for (:+, :-, :*, :<, :>, :<=, :>=, :(==)), test only + + cvi = SentinelArrays.ChainedVectorIndex(1, 2, 3, 4) + @test cvi + 1 == 5 + @test cvi + BigInt(21) == 25 + @test 1 + cvi == 5 + @test BigInt(21) + cvi == 25 + @test cvi + cvi == 8 +end + +@testset "Broadcasting a ChainedVector" begin + cv = ChainedVector([[1, 2], [3, 4]], [5, 6]) + @test Base.Fix2(^, 2).(cv) |> collect == (1:4) .^ 2 |> collect +end \ No newline at end of file diff --git a/test/missingvector.jl b/test/missingvector.jl new file mode 100644 index 0000000..9cdc792 --- /dev/null +++ b/test/missingvector.jl @@ -0,0 +1,126 @@ + +@testset "MissingVector" begin + + x = MissingVector(10) + @test all(x .=== missing) + @test length(x) == 10 + @test length(x) isa Int + @test Base.IndexStyle(x) == Base.IndexLinear() + + y = similar(x, Missing, 5) + @test length(y) == 5 + + y = empty(x) + @test length(y) == 0 + + x[1] = missing + x[end] = missing + @test x[1] === missing + @test x[end] === missing + + y = similar(x) + @test typeof(y) == MissingVector + @test length(y) == 10 + y = similar(x, 20) + @test typeof(y) == MissingVector + @test length(y) == 20 + + @test isequal(copy(x), x) + empty!(x) + @test length(x) == 0 + @test isequal(copy(x), x) + + @test_throws ArgumentError resize!(x, -1) + resize!(x, 10) + @test length(x) == 10 + + push!(x, missing) + @test x[end] === missing + empty!(x) + push!(x, missing) + @test x[1] === x[end] === missing + + pushfirst!(x, missing) + @test x[1] === missing + empty!(x) + pushfirst!(x, missing) + @test x[1] === x[end] === missing + pushfirst!(x, missing) + + @test pop!(x) === missing + @test popfirst!(x) === missing + @test isempty(x) + + @test_throws BoundsError insert!(x, 0, missing) + @test_throws BoundsError insert!(x, 2, missing) + insert!(x, 1, missing) + @test x[1] === missing + insert!(x, 1, missing) + @test x[1] === missing + + x = MissingVector(10) + y = MissingVector(10) + + z = vcat(x, y) + @test length(z) == 20 + + empty!(x) + z = vcat(x, y) + @test isequal(z, y) + + x = MissingVector(10) + append!(x, y) + @test length(x) == 20 + + x = MissingVector(10) + append!(x, (missing for _ = 1:10)) + @test length(x) == 20 + + empty!(x) + append!(x, y) + @test isequal(x, y) + + x = MissingVector(10) + y = MissingVector(10) + + prepend!(y, x) + @test length(y) == 20 + + y = MissingVector(10) + prepend!(y, (missing for _ = 1:10)) + @test length(y) == 20 + + empty!(y) + prepend!(y, x) + @test isequal(y, x) + + x = MissingVector(10) + deleteat!(x, 1) + @test x[1] === missing + deleteat!(x, 1:4) + @test length(x) == 5 + deleteat!(x, [2, 4]) + @test length(x) == 3 + + m = similar(x) + @test length(m) == length(x) + m = similar(x, Missing) + @test length(m) == length(x) + @test typeof(m[1:3]) == typeof(m) + + deleteat!(x, [true, true, false]) + @test length(x) == 1 + empty!(x) + @test_throws ArgumentError pop!(x) + @test_throws ArgumentError popfirst!(x) + + m = MissingVector(5) + c = ChainedVector([m, m, m]) + c2 = copy(c) + @test length(c) == length(c2) + @test c2 isa MissingVector + + deleteat!(c2, Int[]) + @test length(c2) == 15 + +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 897035a..20514b6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,379 +1,7 @@ -using SentinelArrays, Test, Random +susing ReTest -@testset "SentinelArrays" begin +using SentinelArrays -# this is a very rare corner case -# we put this test first because it relies on -# the seeded random number generator for thread 1 -# the issue is if we had an underlying array of non-sentinel values -# then tried to `setindex!` to the _NEXT_ chosen sentinel value -# then the element getting set would end up `missing` (because it == the sentinel value) -# instead of the sentinel value getting cycled to something else -Random.seed!(0) -x = SentinelVector{Int64}(undef, 1) -x[1] = 1 -x.sentinel = 1369352191816061504 -x[1] = 1369352191816061504 -@test x[1] == 1369352191816061504 +include("SentinelArrayTests.jl") -x = SentinelVector{Int}(undef, 10) -fill!(x, missing) -@test length(x) == 10 -@test all(isequal.(x.data, x.sentinel)) - -@test x[1] === missing -# force recoding -sent = x.sentinel -x[1] = x.sentinel -@test x[1] === sent -r = reverse(x) -@test x.sentinel == r.sentinel - -@test size(x) == (10,) -x[1] = 3 -@test x[1] === 3 - -resize!(x, length(x) + 1) -@test x[end] === missing - -x = SentinelVector{Int64}(undef, 1) -x[1] = missing -@test x[1] === missing - -x = SentinelArray{Float64, 2}(undef, 10, 10) -@test size(x) == (10, 10) -x = SentinelArray{Float64}(undef, 10, 10) -@test size(x) == (10, 10) - -x = SentinelArray(fill(3.0, 10, 10)) -y = convert(SentinelArray{Int64}, x) -@test size(y) == (10, 10) -@test y isa SentinelArray{Int64} -@test all(y .=== Int64(3)) - -y = convert(SentinelVector{Int64}, x) -@test size(y) == (10, 10) -@test y isa SentinelArray{Int64} -@test all(y .=== Int64(3)) - -y = convert(SentinelArray, fill(Int64(3), 10, 10)) -@test size(y) == (10, 10) -@test size(y, 1) == 10 -@test axes(y, 1) == Base.OneTo(10) -@test stride(y, 1) == 1 -@test strides(y) == (1, 10) -@test y isa SentinelArray{Int64} -@test all(y .=== Int64(3)) - -x = SentinelVector{Union{Bool, Missing}}(undef, 1, missing, missing) -@test x[1] === missing -x[1] = true -@test x[1] === true -x[1] = missing -@test x[1] === missing - -x = SentinelArray(["$i" for i = 1:10]) -deleteat!(x, [9, 10]) -@test length(x) == 8 -@test x == ["$i" for i = 1:8] -deleteat!(x, [true, false, true, false, true, false, true, false]) -@test length(x) == 4 -@test x == ["2", "4", "6", "8"] - -x = SentinelVector{String}(undef, 10) -@test x[1] === missing -x[1] = "hey" -@test x[1] == "hey" -x[1] = missing -@test x[1] === missing - -@test SentinelArrays.newsentinel!(x) === nothing - -@test all(x .=== copy(x)) -@test length(empty!(x)) == 0 -@test length(resize!(x, 10)) == 10 -@test x[1] === missing - -push!(x, missing) -@test x[end] === missing -push!(x, "ho") -@test x[end] == "ho" - -deleteat!(x, length(x)) -@test x[end] === missing -@test length(x) == 11 - -empty!(x) -append!(x, ["hey", "ho", missing]) -@test length(x) == 3 -@test isequal(x, ["hey", "ho", missing]) - -pushfirst!(x, missing) -@test x[1] === missing -prepend!(x, [missing, "first"]) -@test x[1] === missing -@test x[2] == "first" - -@test popfirst!(x) === missing -@test x[1] == "first" - -insert!(x, 1, missing) -@test x[1] === missing -insert!(x, length(x) + 1, "pirate") -@test x[end] == "pirate" - -@test splice!(x, length(x), ["pirate2"]) == "pirate" -@test splice!(x, length(x)) == "pirate2" -@test splice!(x, length(x), ["pirate3", "pirate4"]) === missing - -@test splice!(x, (length(x)-1):length(x), ["pirate5", "pirate6"]) == ["pirate3", "pirate4"] -@test splice!(x, (length(x)-1):length(x), ["pirate7"]) == ["pirate5", "pirate6"] -@test splice!(x, length(x), ["pirate8", "pirate9"]) == "pirate7" -@test splice!(x, (length(x)-1):length(x), ["pirate10", "pirate11", "pirate12"]) == ["pirate8", "pirate9"] -@test splice!(x, (length(x)-2):length(x)) == ["pirate10", "pirate11", "pirate12"] - -t = [SentinelVector{Int64}(undef, 10), SentinelVector{Int64}(undef, 10), SentinelVector{Int64}(undef, 5)] -sent = t[1].sentinel -@test all(x->x.sentinel == sent, t) -SentinelArrays.newsentinel!(t...; force=false) -# make sure nothing got recoded -@test all(x->x.sentinel == sent, t) -SentinelArrays.newsentinel!(t...; force=true) -# make sure all got recoded -@test all(x->x.sentinel != sent, t) - -# force recode of just one vector -sent = t[1].sentinel -t[2][1] = t[2].sentinel -@test !all(x->x.sentinel == sent, t) -SentinelArrays.newsentinel!(t...) -# make sure all got recoded to same -@test all(x->x.sentinel == t[1].sentinel, t) - -A = SentinelArray(Int64[i for i = 1:10]) -B = SentinelVector{Int64}(undef, 10) - -C = vcat(A, B) -@test C[1:10] == collect(1:10) -@test all(C[11:20] .=== missing) - -append!(A, B) -@test A[1:10] == collect(1:10) -@test all(A[11:20] .=== missing) - -A = SentinelArray(Int64[i for i = 1:10]) -B = SentinelVector{Int64}(undef, 10) -# force B to recode -B[1] = B.sentinel -B[1] = missing - -C = vcat(A, B) -@test C[1:10] == collect(1:10) -@test all(C[11:20] .=== missing) - -append!(A, B) -@test A[1:10] == collect(1:10) -@test all(A[11:20] .=== missing) - -# make sure we bail when can't find a new automatic sentinel -A = SentinelArray([i for i = 0x00:0xff]) -@test_throws SentinelCollisionError setindex!(A, A.sentinel, 1) - -@test_throws ErrorException SentinelVector{Bool}(undef, 1) - -t = SentinelVector{Tuple{Int32, Int32}}(undef, 1) -@test t.data[1] === t.sentinel - -t = SentinelMatrix{Float64}(undef, (10, 10)) -@test size(t) == (10, 10) - -t = SentinelArray(collect(1:10)) -pushfirst!(t, 3, 2, 1) -@test t[1:3] == [3, 2, 1] - -prepend!(t, (i for i = 1:3 if i > 0)) -@test t[1:3] == [1, 2, 3] - -@test pop!(t) == 10 -empty!(t) -@test_throws ArgumentError pop!(t) -@test_throws ArgumentError popfirst!(t) - -x = SentinelArray(ones(3)) - -# broadcasting -@test x .+ x == 2 * ones(3) -@test x .+ x .+ x == 3 * ones(3) -@test (x .= 0 .* x .+ 7) == 7 * ones(3) - -v = SentinelArray(zeros(3,)) -m = SentinelArray(ones(3, 3)) -s = 0 - -@test v .+ m == ones(3, 3) == m .+ v -@test s .+ m == ones(3, 3) == m .+ s -@test s .+ v .+ m == ones(3, 3) == m .+ s .+ v - -casts = ( - SentinelArray, # Named Matrix - x->SentinelArray(x[:, 1]), # Named Vector - x->SentinelArray(x[:, 1:1]), # Named Single Column Matrix - identity, # Matrix - x->x[:, 1], # Vector - x->x[:, 1:1], # Single Column Matrix - first, # Scalar - ) -for (T1, T2, T3) in Iterators.product(casts, casts, casts) - all(isequal(identity), (T1, T2, T3)) && continue - !any(isequal(SentinelArray), (T1, T2, T3)) && continue - - total = T1(ones(3, 6)) .+ T2(2ones(3, 6)) .+ T3(3ones(3, 6)) - @test total == 6ones(3, 6) -end - -s = SentinelVector{Int}(undef, 2) -@test isequal(max.(s, 0), [missing, missing]) -@test (s .=== missing) isa BitArray - -s = SentinelArray(collect(1:5)) -c = ChainedVector([s, s, s]) -c2 = copy(c) -@test length(c) == length(c2) -@test c2 isa Vector - -# deleteat! of SentinelArray w/ underlying ChainedVector w/ UndefInitializer -x = SentinelArray(ChainedVector([Vector{String}(undef, 5)])) -deleteat!(x, 1) -@test length(x) == 4 - -end # @testset - - -@testset "MissingVector" begin - -x = MissingVector(10) -@test all(x .=== missing) -@test length(x) == 10 -@test length(x) isa Int -@test Base.IndexStyle(x) == Base.IndexLinear() - -y = similar(x, Missing, 5) -@test length(y) == 5 - -y = empty(x) -@test length(y) == 0 - -x[1] = missing -x[end] = missing -@test x[1] === missing -@test x[end] === missing - -y = similar(x) -@test typeof(y) == MissingVector -@test length(y) == 10 -y = similar(x, 20) -@test typeof(y) == MissingVector -@test length(y) == 20 - -@test isequal(copy(x), x) -empty!(x) -@test length(x) == 0 -@test isequal(copy(x), x) - -@test_throws ArgumentError resize!(x, -1) -resize!(x, 10) -@test length(x) == 10 - -push!(x, missing) -@test x[end] === missing -empty!(x) -push!(x, missing) -@test x[1] === x[end] === missing - -pushfirst!(x, missing) -@test x[1] === missing -empty!(x) -pushfirst!(x, missing) -@test x[1] === x[end] === missing -pushfirst!(x, missing) - -@test pop!(x) === missing -@test popfirst!(x) === missing -@test isempty(x) - -@test_throws BoundsError insert!(x, 0, missing) -@test_throws BoundsError insert!(x, 2, missing) -insert!(x, 1, missing) -@test x[1] === missing -insert!(x, 1, missing) -@test x[1] === missing - -x = MissingVector(10) -y = MissingVector(10) - -z = vcat(x, y) -@test length(z) == 20 - -empty!(x) -z = vcat(x, y) -@test isequal(z, y) - -x = MissingVector(10) -append!(x, y) -@test length(x) == 20 - -x = MissingVector(10) -append!(x, (missing for _ = 1:10)) -@test length(x) == 20 - -empty!(x) -append!(x, y) -@test isequal(x, y) - -x = MissingVector(10) -y = MissingVector(10) - -prepend!(y, x) -@test length(y) == 20 - -y = MissingVector(10) -prepend!(y, (missing for _ = 1:10)) -@test length(y) == 20 - -empty!(y) -prepend!(y, x) -@test isequal(y, x) - -x = MissingVector(10) -deleteat!(x, 1) -@test x[1] === missing -deleteat!(x, 1:4) -@test length(x) == 5 -deleteat!(x, [2, 4]) -@test length(x) == 3 - -m = similar(x) -@test length(m) == length(x) -m = similar(x, Missing) -@test length(m) == length(x) -@test typeof(m[1:3]) == typeof(m) - -deleteat!(x, [true, true, false]) -@test length(x) == 1 -empty!(x) -@test_throws ArgumentError pop!(x) -@test_throws ArgumentError popfirst!(x) - -m = MissingVector(5) -c = ChainedVector([m, m, m]) -c2 = copy(c) -@test length(c) == length(c2) -@test c2 isa MissingVector - -deleteat!(c2, Int[]) -@test length(c2) == 15 - -end - -include("chainedvector.jl") -include("BufferedVectors.jl") +retest(SentinelArrays, SentinelArrayTests) diff --git a/test/sentinelarrays.jl b/test/sentinelarrays.jl new file mode 100644 index 0000000..382b777 --- /dev/null +++ b/test/sentinelarrays.jl @@ -0,0 +1,248 @@ +@testset "SentinelArrays" begin + + # this is a very rare corner case + # we put this test first because it relies on + # the seeded random number generator for thread 1 + # the issue is if we had an underlying array of non-sentinel values + # then tried to `setindex!` to the _NEXT_ chosen sentinel value + # then the element getting set would end up `missing` (because it == the sentinel value) + # instead of the sentinel value getting cycled to something else + Random.seed!(0) + x = SentinelVector{Int64}(undef, 1) + x[1] = 1 + x.sentinel = 1369352191816061504 + x[1] = 1369352191816061504 + @test x[1] == 1369352191816061504 + + x = SentinelVector{Int}(undef, 10) + fill!(x, missing) + @test length(x) == 10 + @test all(isequal.(x.data, x.sentinel)) + + @test x[1] === missing + # force recoding + sent = x.sentinel + x[1] = x.sentinel + @test x[1] === sent + r = reverse(x) + @test x.sentinel == r.sentinel + + @test size(x) == (10,) + x[1] = 3 + @test x[1] === 3 + + resize!(x, length(x) + 1) + @test x[end] === missing + + x = SentinelVector{Int64}(undef, 1) + x[1] = missing + @test x[1] === missing + + x = SentinelArray{Float64,2}(undef, 10, 10) + @test size(x) == (10, 10) + x = SentinelArray{Float64}(undef, 10, 10) + @test size(x) == (10, 10) + + x = SentinelArray(fill(3.0, 10, 10)) + y = convert(SentinelArray{Int64}, x) + @test size(y) == (10, 10) + @test y isa SentinelArray{Int64} + @test all(y .=== Int64(3)) + + y = convert(SentinelVector{Int64}, x) + @test size(y) == (10, 10) + @test y isa SentinelArray{Int64} + @test all(y .=== Int64(3)) + + y = convert(SentinelArray, fill(Int64(3), 10, 10)) + @test size(y) == (10, 10) + @test size(y, 1) == 10 + @test axes(y, 1) == Base.OneTo(10) + @test stride(y, 1) == 1 + @test strides(y) == (1, 10) + @test y isa SentinelArray{Int64} + @test all(y .=== Int64(3)) + + x = SentinelVector{Union{Bool,Missing}}(undef, 1, missing, missing) + @test x[1] === missing + x[1] = true + @test x[1] === true + x[1] = missing + @test x[1] === missing + + x = SentinelArray(["$i" for i = 1:10]) + deleteat!(x, [9, 10]) + @test length(x) == 8 + @test x == ["$i" for i = 1:8] + deleteat!(x, [true, false, true, false, true, false, true, false]) + @test length(x) == 4 + @test x == ["2", "4", "6", "8"] + + x = SentinelVector{String}(undef, 10) + @test x[1] === missing + x[1] = "hey" + @test x[1] == "hey" + x[1] = missing + @test x[1] === missing + + @test SentinelArrays.newsentinel!(x) === nothing + + @test all(x .=== copy(x)) + @test length(empty!(x)) == 0 + @test length(resize!(x, 10)) == 10 + @test x[1] === missing + + push!(x, missing) + @test x[end] === missing + push!(x, "ho") + @test x[end] == "ho" + + deleteat!(x, length(x)) + @test x[end] === missing + @test length(x) == 11 + + empty!(x) + append!(x, ["hey", "ho", missing]) + @test length(x) == 3 + @test isequal(x, ["hey", "ho", missing]) + + pushfirst!(x, missing) + @test x[1] === missing + prepend!(x, [missing, "first"]) + @test x[1] === missing + @test x[2] == "first" + + @test popfirst!(x) === missing + @test x[1] == "first" + + insert!(x, 1, missing) + @test x[1] === missing + insert!(x, length(x) + 1, "pirate") + @test x[end] == "pirate" + + @test splice!(x, length(x), ["pirate2"]) == "pirate" + @test splice!(x, length(x)) == "pirate2" + @test splice!(x, length(x), ["pirate3", "pirate4"]) === missing + + @test splice!(x, (length(x)-1):length(x), ["pirate5", "pirate6"]) == ["pirate3", "pirate4"] + @test splice!(x, (length(x)-1):length(x), ["pirate7"]) == ["pirate5", "pirate6"] + @test splice!(x, length(x), ["pirate8", "pirate9"]) == "pirate7" + @test splice!(x, (length(x)-1):length(x), ["pirate10", "pirate11", "pirate12"]) == ["pirate8", "pirate9"] + @test splice!(x, (length(x)-2):length(x)) == ["pirate10", "pirate11", "pirate12"] + + t = [SentinelVector{Int64}(undef, 10), SentinelVector{Int64}(undef, 10), SentinelVector{Int64}(undef, 5)] + sent = t[1].sentinel + @test all(x -> x.sentinel == sent, t) + SentinelArrays.newsentinel!(t...; force=false) + # make sure nothing got recoded + @test all(x -> x.sentinel == sent, t) + SentinelArrays.newsentinel!(t...; force=true) + # make sure all got recoded + @test all(x -> x.sentinel != sent, t) + + # force recode of just one vector + sent = t[1].sentinel + t[2][1] = t[2].sentinel + @test !all(x -> x.sentinel == sent, t) + SentinelArrays.newsentinel!(t...) + # make sure all got recoded to same + @test all(x -> x.sentinel == t[1].sentinel, t) + + A = SentinelArray(Int64[i for i = 1:10]) + B = SentinelVector{Int64}(undef, 10) + + C = vcat(A, B) + @test C[1:10] == collect(1:10) + @test all(C[11:20] .=== missing) + + append!(A, B) + @test A[1:10] == collect(1:10) + @test all(A[11:20] .=== missing) + + A = SentinelArray(Int64[i for i = 1:10]) + B = SentinelVector{Int64}(undef, 10) + # force B to recode + B[1] = B.sentinel + B[1] = missing + + C = vcat(A, B) + @test C[1:10] == collect(1:10) + @test all(C[11:20] .=== missing) + + append!(A, B) + @test A[1:10] == collect(1:10) + @test all(A[11:20] .=== missing) + + # make sure we bail when can't find a new automatic sentinel + A = SentinelArray([i for i = 0x00:0xff]) + @test_throws SentinelCollisionError setindex!(A, A.sentinel, 1) + + @test_throws ErrorException SentinelVector{Bool}(undef, 1) + + t = SentinelVector{Tuple{Int32,Int32}}(undef, 1) + @test t.data[1] === t.sentinel + + t = SentinelMatrix{Float64}(undef, (10, 10)) + @test size(t) == (10, 10) + + t = SentinelArray(collect(1:10)) + pushfirst!(t, 3, 2, 1) + @test t[1:3] == [3, 2, 1] + + prepend!(t, (i for i = 1:3 if i > 0)) + @test t[1:3] == [1, 2, 3] + + @test pop!(t) == 10 + empty!(t) + @test_throws ArgumentError pop!(t) + @test_throws ArgumentError popfirst!(t) + + x = SentinelArray(ones(3)) + + # broadcasting + @test x .+ x == 2 * ones(3) + @test x .+ x .+ x == 3 * ones(3) + @test (x .= 0 .* x .+ 7) == 7 * ones(3) + + v = SentinelArray(zeros(3,)) + m = SentinelArray(ones(3, 3)) + s = 0 + + @test v .+ m == ones(3, 3) == m .+ v + @test s .+ m == ones(3, 3) == m .+ s + @test s .+ v .+ m == ones(3, 3) == m .+ s .+ v + + casts = ( + SentinelArray, # Named Matrix + x -> SentinelArray(x[:, 1]), # Named Vector + x -> SentinelArray(x[:, 1:1]), # Named Single Column Matrix + identity, # Matrix + x -> x[:, 1], # Vector + x -> x[:, 1:1], # Single Column Matrix + first, # Scalar + ) + for (T1, T2, T3) in Iterators.product(casts, casts, casts) + all(isequal(identity), (T1, T2, T3)) && continue + !any(isequal(SentinelArray), (T1, T2, T3)) && continue + + total = T1(ones(3, 6)) .+ T2(2ones(3, 6)) .+ T3(3ones(3, 6)) + @test total == 6ones(3, 6) + end + + s = SentinelVector{Int}(undef, 2) + @test isequal(max.(s, 0), [missing, missing]) + @test (s .=== missing) isa BitArray + + s = SentinelArray(collect(1:5)) + c = ChainedVector([s, s, s]) + c2 = copy(c) + @test length(c) == length(c2) + @test c2 isa Vector + + # deleteat! of SentinelArray w/ underlying ChainedVector w/ UndefInitializer + x = SentinelArray(ChainedVector([Vector{String}(undef, 5)])) + deleteat!(x, 1) + @test length(x) == 4 + +end # @testset +