|
| 1 | +using TranscodingStreams |
| 2 | +using Random |
| 3 | +using Test |
| 4 | +using TranscodingStreams: |
| 5 | + TranscodingStreams, |
| 6 | + TranscodingStream, |
| 7 | + test_roundtrip_read, |
| 8 | + test_roundtrip_write, |
| 9 | + test_roundtrip_lines, |
| 10 | + test_roundtrip_transcode, |
| 11 | + test_roundtrip_fileio, |
| 12 | + test_chunked_read, |
| 13 | + test_chunked_write, |
| 14 | + Error |
| 15 | + |
| 16 | +# An insane codec for testing the codec APIs. |
| 17 | +struct DoubleFrameEncoder <: TranscodingStreams.Codec |
| 18 | + opened::Base.RefValue{Bool} |
| 19 | + stopped::Base.RefValue{Bool} |
| 20 | + got_stop_msg::Base.RefValue{Bool} |
| 21 | +end |
| 22 | + |
| 23 | +DoubleFrameEncoder() = DoubleFrameEncoder(Ref(false), Ref(false), Ref(false)) |
| 24 | + |
| 25 | +function TranscodingStreams.process( |
| 26 | + codec :: DoubleFrameEncoder, |
| 27 | + input :: TranscodingStreams.Memory, |
| 28 | + output :: TranscodingStreams.Memory, |
| 29 | + error :: TranscodingStreams.Error, |
| 30 | + ) |
| 31 | + if input.size == 0 |
| 32 | + codec.got_stop_msg[] = true |
| 33 | + end |
| 34 | + |
| 35 | + if output.size < 2 |
| 36 | + error[] = ErrorException("requires a minimum of 2 bytes of output space") |
| 37 | + return 0, 0, :error |
| 38 | + elseif codec.stopped[] |
| 39 | + error[] = ErrorException("cannot process after stopped") |
| 40 | + return 0, 0, :error |
| 41 | + elseif codec.got_stop_msg[] && input.size != 0 |
| 42 | + error[] = ErrorException("cannot accept more input after getting stop message") |
| 43 | + return 0, 0, :error |
| 44 | + elseif !codec.opened[] |
| 45 | + output[1] = UInt8('[') |
| 46 | + output[2] = UInt8(' ') |
| 47 | + codec.opened[] = true |
| 48 | + return 0, 2, :ok |
| 49 | + elseif codec.got_stop_msg[] |
| 50 | + output[1] = UInt8(' ') |
| 51 | + output[2] = UInt8(']') |
| 52 | + codec.stopped[] = true |
| 53 | + return 0, 2, :end |
| 54 | + else |
| 55 | + i = j = 0 |
| 56 | + while i + 1 ≤ lastindex(input) && j + 2 ≤ lastindex(output) |
| 57 | + b = input[i+1] |
| 58 | + i += 1 |
| 59 | + output[j+1] = output[j+2] = b |
| 60 | + j += 2 |
| 61 | + end |
| 62 | + return i, j, :ok |
| 63 | + end |
| 64 | +end |
| 65 | + |
| 66 | +function TranscodingStreams.expectedsize( |
| 67 | + :: DoubleFrameEncoder, |
| 68 | + input :: TranscodingStreams.Memory) |
| 69 | + return input.size * 2 + 2 + 2 |
| 70 | +end |
| 71 | + |
| 72 | +function TranscodingStreams.minoutsize( |
| 73 | + :: DoubleFrameEncoder, |
| 74 | + :: TranscodingStreams.Memory) |
| 75 | + return 2 |
| 76 | +end |
| 77 | + |
| 78 | +function TranscodingStreams.startproc(codec::DoubleFrameEncoder, ::Symbol, error::Error) |
| 79 | + codec.opened[] = false |
| 80 | + codec.got_stop_msg[] = false |
| 81 | + codec.stopped[] = false |
| 82 | + return :ok |
| 83 | +end |
| 84 | + |
| 85 | +# An insane codec for testing the codec APIs. |
| 86 | +struct DoubleFrameDecoder <: TranscodingStreams.Codec |
| 87 | + state::Base.RefValue{Int} |
| 88 | + a::Base.RefValue{UInt8} |
| 89 | + b::Base.RefValue{UInt8} |
| 90 | +end |
| 91 | + |
| 92 | +DoubleFrameDecoder() = DoubleFrameDecoder(Ref(1), Ref(0x00), Ref(0x00)) |
| 93 | + |
| 94 | +function TranscodingStreams.process( |
| 95 | + codec :: DoubleFrameDecoder, |
| 96 | + input :: TranscodingStreams.Memory, |
| 97 | + output :: TranscodingStreams.Memory, |
| 98 | + error :: TranscodingStreams.Error, |
| 99 | + ) |
| 100 | + Δin::Int = 0 |
| 101 | + Δout::Int = 0 |
| 102 | + |
| 103 | + function do_read(ref) |
| 104 | + iszero(input.size) && error("expected byte") |
| 105 | + if Δin + 1 ≤ lastindex(input) |
| 106 | + Δin += 1 |
| 107 | + ref[] = input[Δin] |
| 108 | + true |
| 109 | + else |
| 110 | + false |
| 111 | + end |
| 112 | + end |
| 113 | + |
| 114 | + function do_write(x::UInt8) |
| 115 | + if Δout + 1 ≤ lastindex(output) |
| 116 | + Δout += 1 |
| 117 | + output[Δout] = x |
| 118 | + true |
| 119 | + else |
| 120 | + false |
| 121 | + end |
| 122 | + end |
| 123 | + |
| 124 | + try |
| 125 | + # hacky resumable function using goto, just for fun. |
| 126 | + oldstate = codec.state[] |
| 127 | + if oldstate == 1 |
| 128 | + @goto state1 |
| 129 | + elseif oldstate == 2 |
| 130 | + @goto state2 |
| 131 | + elseif oldstate == 3 |
| 132 | + @goto state3 |
| 133 | + elseif oldstate == 4 |
| 134 | + @goto state4 |
| 135 | + elseif oldstate == 5 |
| 136 | + @goto state5 |
| 137 | + elseif oldstate == 6 |
| 138 | + error("cannot process after ending") |
| 139 | + elseif oldstate == 7 |
| 140 | + error("cannot process after erroring") |
| 141 | + else |
| 142 | + error("unexpected state $(oldstate)") |
| 143 | + end |
| 144 | + |
| 145 | + @label state1 |
| 146 | + do_read(codec.a) || return (codec.state[]=1; (Δin, Δout, :ok)) |
| 147 | + codec.a[] != UInt8('[') && error("expected [") |
| 148 | + @label state2 |
| 149 | + do_read(codec.a) || return (codec.state[]=2; (Δin, Δout, :ok)) |
| 150 | + codec.a[] != UInt8(' ') && error("expected space") |
| 151 | + while true |
| 152 | + @label state3 |
| 153 | + do_read(codec.a) || return (codec.state[]=3; (Δin, Δout, :ok)) |
| 154 | + @label state4 |
| 155 | + do_read(codec.b) || return (codec.state[]=4; (Δin, Δout, :ok)) |
| 156 | + if codec.a[] == codec.b[] |
| 157 | + @label state5 |
| 158 | + do_write(codec.a[]) || return (codec.state[]=5; (Δin, Δout, :ok)) |
| 159 | + elseif codec.a[] == UInt8(' ') && codec.b[] == UInt8(']') |
| 160 | + break |
| 161 | + else |
| 162 | + error("expected matching bytes or space and ]") |
| 163 | + end |
| 164 | + end |
| 165 | + codec.state[]=6 |
| 166 | + return Δin, Δout, :end |
| 167 | + catch e |
| 168 | + codec.state[]=7 |
| 169 | + e isa ErrorException || rethrow() |
| 170 | + error[] = e |
| 171 | + return Δin, Δout, :error |
| 172 | + end |
| 173 | +end |
| 174 | + |
| 175 | +function TranscodingStreams.startproc(codec::DoubleFrameDecoder, ::Symbol, error::Error) |
| 176 | + codec.state[] = 1 |
| 177 | + codec.a[] = 0x00 |
| 178 | + codec.b[] = 0x00 |
| 179 | + return :ok |
| 180 | +end |
| 181 | + |
| 182 | +const DoubleFrameEncoderStream{S} = TranscodingStream{DoubleFrameEncoder,S} where S<:IO |
| 183 | +DoubleFrameEncoderStream(stream::IO; kwargs...) = TranscodingStream(DoubleFrameEncoder(), stream; kwargs...) |
| 184 | + |
| 185 | +const DoubleFrameDecoderStream{S} = TranscodingStream{DoubleFrameDecoder,S} where S<:IO |
| 186 | +DoubleFrameDecoderStream(stream::IO; kwargs...) = TranscodingStream(DoubleFrameDecoder(), stream; kwargs...) |
| 187 | + |
| 188 | + |
| 189 | +@testset "DoubleFrame Codecs" begin |
| 190 | + @test transcode(DoubleFrameEncoder, b"") == b"[ ]" |
| 191 | + @test transcode(DoubleFrameEncoder, b"a") == b"[ aa ]" |
| 192 | + @test transcode(DoubleFrameEncoder, b"ab") == b"[ aabb ]" |
| 193 | + @test transcode(DoubleFrameEncoder(), b"") == b"[ ]" |
| 194 | + @test transcode(DoubleFrameEncoder(), b"a") == b"[ aa ]" |
| 195 | + @test transcode(DoubleFrameEncoder(), b"ab") == b"[ aabb ]" |
| 196 | + |
| 197 | + @test_throws Exception transcode(DoubleFrameDecoder, b"") |
| 198 | + @test_throws Exception transcode(DoubleFrameDecoder, b" [") |
| 199 | + @test_throws Exception transcode(DoubleFrameDecoder, b" ]") |
| 200 | + @test_throws Exception transcode(DoubleFrameDecoder, b"[]") |
| 201 | + @test_throws Exception transcode(DoubleFrameDecoder, b" ") |
| 202 | + @test_throws Exception transcode(DoubleFrameDecoder, b" ") |
| 203 | + @test_throws Exception transcode(DoubleFrameDecoder, b"aabb") |
| 204 | + @test_throws Exception transcode(DoubleFrameDecoder, b"[ ab ]") |
| 205 | + @test transcode(DoubleFrameDecoder, b"[ ]") == b"" |
| 206 | + @test transcode(DoubleFrameDecoder, b"[ aa ]") == b"a" |
| 207 | + @test transcode(DoubleFrameDecoder, b"[ aabb ]") == b"ab" |
| 208 | + @test transcode(DoubleFrameDecoder, b"[ aaaa ]") == b"aa" |
| 209 | + @test transcode(DoubleFrameDecoder, b"[ ]") == b" " |
| 210 | + @test transcode(DoubleFrameDecoder, b"[ ]] ]") == b" ]" |
| 211 | + @test transcode(DoubleFrameDecoder, b"[ aa ][ bb ]") == b"ab" |
| 212 | + |
| 213 | + @testset "eof is true after write stops" begin |
| 214 | + sink = IOBuffer() |
| 215 | + stream = TranscodingStream(DoubleFrameDecoder(), sink, stop_on_end=true) |
| 216 | + write(stream, "[ yy ]sdfsadfasdfdf") |
| 217 | + flush(stream) |
| 218 | + @test_broken eof(stream) |
| 219 | + # TODO This is broken. |
| 220 | + # @test_throws ArgumentError read(stream, UInt8) |
| 221 | + @test take!(sink) == b"y" |
| 222 | + close(stream) |
| 223 | + end |
| 224 | + |
| 225 | + test_roundtrip_read(DoubleFrameEncoderStream, DoubleFrameDecoderStream) |
| 226 | + test_roundtrip_write(DoubleFrameEncoderStream, DoubleFrameDecoderStream) |
| 227 | + test_roundtrip_lines(DoubleFrameEncoderStream, DoubleFrameDecoderStream) |
| 228 | + test_roundtrip_transcode(DoubleFrameEncoder, DoubleFrameDecoder) |
| 229 | + test_roundtrip_fileio(DoubleFrameEncoder, DoubleFrameDecoder) |
| 230 | + test_chunked_read(DoubleFrameEncoder, DoubleFrameDecoder) |
| 231 | + test_chunked_write(DoubleFrameEncoder, DoubleFrameDecoder) |
| 232 | +end |
0 commit comments