|
| 1 | +@testitem "order" begin |
| 2 | + source = QueryOperators.query([3, 1, 2]) |
| 3 | + |
| 4 | + res = QueryOperators.order(source) |
| 5 | + |
| 6 | + @test collect(res) == [1, 2, 3] |
| 7 | + @test eltype(res) == Int |
| 8 | + @test Base.IteratorSize(typeof(res)) == Base.HasLength() |
| 9 | + @test length(res) == 3 |
| 10 | +end |
| 11 | + |
| 12 | +@testitem "order_descending" begin |
| 13 | + source = QueryOperators.query([3, 1, 2]) |
| 14 | + |
| 15 | + @test collect(QueryOperators.order_descending(source)) == [3, 2, 1] |
| 16 | +end |
| 17 | + |
| 18 | +@testitem "order on an empty source" begin |
| 19 | + source = QueryOperators.query(Int[]) |
| 20 | + |
| 21 | + @test collect(QueryOperators.order(source)) == Int[] |
| 22 | + @test collect(QueryOperators.order_descending(source)) == Int[] |
| 23 | +end |
| 24 | + |
| 25 | +@testitem "thenby can follow order" begin |
| 26 | + source = QueryOperators.query([(a=1, b=2), (a=1, b=1), (a=0, b=9)]) |
| 27 | + |
| 28 | + # order sorts by the whole element; thenby then refines it, which only |
| 29 | + # works because order reuses EnumerableOrderby. |
| 30 | + res = QueryOperators.@thenby(QueryOperators.order(source), i -> i.b) |
| 31 | + |
| 32 | + @test collect(res) == [(a=0, b=9), (a=1, b=1), (a=1, b=2)] |
| 33 | +end |
| 34 | + |
| 35 | +@testitem "reverse" begin |
| 36 | + source = QueryOperators.query([1, 2, 3]) |
| 37 | + |
| 38 | + res = QueryOperators.reverse(source) |
| 39 | + |
| 40 | + @test collect(res) == [3, 2, 1] |
| 41 | + @test eltype(res) == Int |
| 42 | + @test Base.IteratorSize(typeof(res)) == Base.HasLength() |
| 43 | + @test length(res) == 3 |
| 44 | +end |
| 45 | + |
| 46 | +@testitem "reverse on empty and single-element sources" begin |
| 47 | + @test collect(QueryOperators.reverse(QueryOperators.query(Int[]))) == Int[] |
| 48 | + @test collect(QueryOperators.reverse(QueryOperators.query([7]))) == [7] |
| 49 | +end |
| 50 | + |
| 51 | +@testitem "reverse of reverse is the identity" begin |
| 52 | + source = QueryOperators.query([1, 2, 3, 4]) |
| 53 | + |
| 54 | + @test collect(QueryOperators.reverse(QueryOperators.reverse(source))) == [1, 2, 3, 4] |
| 55 | +end |
| 56 | + |
| 57 | +@testitem "shuffle is a permutation of its source" begin |
| 58 | + using Random |
| 59 | + |
| 60 | + source = QueryOperators.query(collect(1:50)) |
| 61 | + |
| 62 | + res = QueryOperators.shuffle(source) |
| 63 | + |
| 64 | + @test sort(collect(res)) == collect(1:50) |
| 65 | + @test eltype(res) == Int |
| 66 | + @test length(res) == 50 |
| 67 | +end |
| 68 | + |
| 69 | +@testitem "shuffle with an explicit rng is reproducible" begin |
| 70 | + using Random |
| 71 | + |
| 72 | + source = QueryOperators.query(collect(1:50)) |
| 73 | + |
| 74 | + a = collect(QueryOperators.shuffle(source, MersenneTwister(42))) |
| 75 | + b = collect(QueryOperators.shuffle(source, MersenneTwister(42))) |
| 76 | + |
| 77 | + @test a == b |
| 78 | + @test sort(a) == collect(1:50) |
| 79 | +end |
| 80 | + |
| 81 | +@testitem "shuffle on an empty source" begin |
| 82 | + @test collect(QueryOperators.shuffle(QueryOperators.query(Int[]))) == Int[] |
| 83 | +end |
| 84 | + |
| 85 | +@testitem "index" begin |
| 86 | + source = QueryOperators.query(["a", "b", "c"]) |
| 87 | + |
| 88 | + res = QueryOperators.index(source) |
| 89 | + |
| 90 | + # 1-based, matching Julia rather than .NET's 0-based Index(). |
| 91 | + @test collect(res) == [(index=1, item="a"), (index=2, item="b"), (index=3, item="c")] |
| 92 | + @test eltype(res) == NamedTuple{(:index, :item),Tuple{Int,String}} |
| 93 | + @test Base.IteratorSize(typeof(res)) == Base.HasLength() |
| 94 | + @test length(res) == 3 |
| 95 | +end |
| 96 | + |
| 97 | +@testitem "index on an empty source" begin |
| 98 | + source = QueryOperators.query(String[]) |
| 99 | + |
| 100 | + @test collect(QueryOperators.index(source)) == NamedTuple{(:index, :item),Tuple{Int,String}}[] |
| 101 | +end |
| 102 | + |
| 103 | +@testitem "index is lazy" begin |
| 104 | + import IteratorInterfaceExtensions |
| 105 | + |
| 106 | + mutable struct CountedIndexSource |
| 107 | + data::Vector{Int} |
| 108 | + pulled::Int |
| 109 | + end |
| 110 | + Base.eltype(::Type{CountedIndexSource}) = Int |
| 111 | + Base.IteratorSize(::Type{CountedIndexSource}) = Base.HasLength() |
| 112 | + Base.length(c::CountedIndexSource) = length(c.data) |
| 113 | + function Base.iterate(c::CountedIndexSource, i=1) |
| 114 | + i > length(c.data) && return nothing |
| 115 | + c.pulled += 1 |
| 116 | + return c.data[i], i + 1 |
| 117 | + end |
| 118 | + IteratorInterfaceExtensions.isiterable(::CountedIndexSource) = true |
| 119 | + IteratorInterfaceExtensions.getiterator(c::CountedIndexSource) = c |
| 120 | + |
| 121 | + src = CountedIndexSource([1, 2, 3, 4, 5], 0) |
| 122 | + res = QueryOperators.index(QueryOperators.query(src)) |
| 123 | + |
| 124 | + it = iterate(res) |
| 125 | + it = iterate(res, it[2]) |
| 126 | + |
| 127 | + @test it[1] == (index=2, item=2) |
| 128 | + @test src.pulled == 2 |
| 129 | +end |
| 130 | + |
| 131 | +@testitem "ordering operators work downstream of groupby" begin |
| 132 | + source = QueryOperators.query([(k=2, v=1), (k=1, v=2), (k=3, v=3)]) |
| 133 | + grouped = QueryOperators.@groupby_simple(source, i -> i.k) |
| 134 | + |
| 135 | + reversed = collect(QueryOperators.reverse(grouped)) |
| 136 | + @test [QueryOperators.key(g) for g in reversed] == [3, 1, 2] |
| 137 | + |
| 138 | + indexed = collect(QueryOperators.index(grouped)) |
| 139 | + @test [i.index for i in indexed] == [1, 2, 3] |
| 140 | + @test [QueryOperators.key(i.item) for i in indexed] == [2, 1, 3] |
| 141 | +end |
0 commit comments