|
| 1 | +@testitem "append" begin |
| 2 | + source = QueryOperators.query([1, 2, 3]) |
| 3 | + |
| 4 | + res = QueryOperators.append(source, 4) |
| 5 | + |
| 6 | + @test collect(res) == [1, 2, 3, 4] |
| 7 | + @test eltype(res) == Int |
| 8 | + @test Base.IteratorSize(typeof(res)) == Base.HasLength() |
| 9 | + @test length(res) == 4 |
| 10 | +end |
| 11 | + |
| 12 | +@testitem "append to an empty source" begin |
| 13 | + source = QueryOperators.query(Int[]) |
| 14 | + |
| 15 | + @test collect(QueryOperators.append(source, 1)) == [1] |
| 16 | +end |
| 17 | + |
| 18 | +@testitem "append converts the element to the source element type" begin |
| 19 | + source = QueryOperators.query([1.0, 2.0]) |
| 20 | + |
| 21 | + res = QueryOperators.append(source, 3) |
| 22 | + |
| 23 | + @test collect(res) == [1.0, 2.0, 3.0] |
| 24 | + @test eltype(res) == Float64 |
| 25 | +end |
| 26 | + |
| 27 | +@testitem "append rejects an element that cannot be converted" begin |
| 28 | + source = QueryOperators.query([1, 2]) |
| 29 | + |
| 30 | + @test_throws MethodError QueryOperators.append(source, "three") |
| 31 | +end |
| 32 | + |
| 33 | +@testitem "append of NamedTuple rows" begin |
| 34 | + source = QueryOperators.query([(a=1, b="x")]) |
| 35 | + |
| 36 | + res = QueryOperators.append(source, (a=2, b="y")) |
| 37 | + |
| 38 | + @test collect(res) == [(a=1, b="x"), (a=2, b="y")] |
| 39 | +end |
| 40 | + |
| 41 | +@testitem "prepend" begin |
| 42 | + source = QueryOperators.query([2, 3]) |
| 43 | + |
| 44 | + res = QueryOperators.prepend(source, 1) |
| 45 | + |
| 46 | + @test collect(res) == [1, 2, 3] |
| 47 | + @test eltype(res) == Int |
| 48 | + @test Base.IteratorSize(typeof(res)) == Base.HasLength() |
| 49 | + @test length(res) == 3 |
| 50 | +end |
| 51 | + |
| 52 | +@testitem "prepend to an empty source" begin |
| 53 | + source = QueryOperators.query(Int[]) |
| 54 | + |
| 55 | + @test collect(QueryOperators.prepend(source, 1)) == [1] |
| 56 | +end |
| 57 | + |
| 58 | +@testitem "append and prepend compose" begin |
| 59 | + source = QueryOperators.query([2, 3]) |
| 60 | + |
| 61 | + res = QueryOperators.append(QueryOperators.prepend(source, 1), 4) |
| 62 | + |
| 63 | + @test collect(res) == [1, 2, 3, 4] |
| 64 | + @test length(res) == 4 |
| 65 | +end |
| 66 | + |
| 67 | +@testitem "zip" begin |
| 68 | + a = QueryOperators.query([1, 2, 3]) |
| 69 | + b = QueryOperators.query(["a", "b", "c"]) |
| 70 | + |
| 71 | + res = QueryOperators.zip(a, b) |
| 72 | + |
| 73 | + @test collect(res) == [(1, "a"), (2, "b"), (3, "c")] |
| 74 | + @test eltype(res) == Tuple{Int,String} |
| 75 | + @test Base.IteratorSize(typeof(res)) == Base.HasLength() |
| 76 | + @test length(res) == 3 |
| 77 | +end |
| 78 | + |
| 79 | +@testitem "zip truncates to the shorter source" begin |
| 80 | + a = QueryOperators.query([1, 2, 3, 4]) |
| 81 | + b = QueryOperators.query(["a", "b"]) |
| 82 | + |
| 83 | + @test collect(QueryOperators.zip(a, b)) == [(1, "a"), (2, "b")] |
| 84 | + @test length(QueryOperators.zip(a, b)) == 2 |
| 85 | + @test collect(QueryOperators.zip(b, a)) == [("a", 1), ("b", 2)] |
| 86 | +end |
| 87 | + |
| 88 | +@testitem "zip with an empty source" begin |
| 89 | + a = QueryOperators.query([1, 2]) |
| 90 | + empty = QueryOperators.query(String[]) |
| 91 | + |
| 92 | + @test collect(QueryOperators.zip(a, empty)) == Tuple{Int,String}[] |
| 93 | + @test collect(QueryOperators.zip(empty, a)) == Tuple{String,Int}[] |
| 94 | +end |
| 95 | + |
| 96 | +@testitem "zip with a result selector" begin |
| 97 | + a = QueryOperators.query([1, 2, 3]) |
| 98 | + b = QueryOperators.query([10, 20, 30]) |
| 99 | + |
| 100 | + res = QueryOperators.zip(a, b, (x, y) -> (sum=x + y,), :((x, y) -> (sum=x + y,))) |
| 101 | + |
| 102 | + @test collect(res) == [(sum=11,), (sum=22,), (sum=33,)] |
| 103 | + @test eltype(res) == NamedTuple{(:sum,),Tuple{Int}} |
| 104 | +end |
| 105 | + |
| 106 | +@testitem "zip pads nothing, so it manufactures no null values" begin |
| 107 | + using DataValues |
| 108 | + |
| 109 | + a = QueryOperators.query([1, 2, 3]) |
| 110 | + b = QueryOperators.query([10]) |
| 111 | + |
| 112 | + res = collect(QueryOperators.zip(a, b)) |
| 113 | + |
| 114 | + @test length(res) == 1 |
| 115 | + @test !any(r -> any(ismissing, r), res) |
| 116 | + @test !any(r -> any(x -> x isa DataValue, r), res) |
| 117 | +end |
| 118 | + |
| 119 | +@testitem "zip does not over-read the longer source" begin |
| 120 | + import IteratorInterfaceExtensions |
| 121 | + |
| 122 | + mutable struct CountedZipSource |
| 123 | + data::Vector{Int} |
| 124 | + pulled::Int |
| 125 | + end |
| 126 | + Base.eltype(::Type{CountedZipSource}) = Int |
| 127 | + Base.IteratorSize(::Type{CountedZipSource}) = Base.HasLength() |
| 128 | + Base.length(c::CountedZipSource) = length(c.data) |
| 129 | + function Base.iterate(c::CountedZipSource, i=1) |
| 130 | + i > length(c.data) && return nothing |
| 131 | + c.pulled += 1 |
| 132 | + return c.data[i], i + 1 |
| 133 | + end |
| 134 | + IteratorInterfaceExtensions.isiterable(::CountedZipSource) = true |
| 135 | + IteratorInterfaceExtensions.getiterator(c::CountedZipSource) = c |
| 136 | + |
| 137 | + long = CountedZipSource(collect(1:100), 0) |
| 138 | + res = QueryOperators.zip(QueryOperators.query(long), QueryOperators.query([1, 2])) |
| 139 | + |
| 140 | + @test length(collect(res)) == 2 |
| 141 | + # Three pulls: two matched, plus the one that had no partner. |
| 142 | + @test long.pulled == 3 |
| 143 | +end |
| 144 | + |
| 145 | +@testitem "combining operators work downstream of groupby" begin |
| 146 | + source = QueryOperators.query([(k=1, v=1), (k=2, v=2)]) |
| 147 | + grouped = QueryOperators.@groupby_simple(source, i -> i.k) |
| 148 | + |
| 149 | + zipped = collect(QueryOperators.zip(grouped, QueryOperators.query(["first", "second"]))) |
| 150 | + |
| 151 | + @test length(zipped) == 2 |
| 152 | + @test [QueryOperators.key(g) for (g, _) in zipped] == [1, 2] |
| 153 | + @test [s for (_, s) in zipped] == ["first", "second"] |
| 154 | +end |
0 commit comments