1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 00:29:09 -05:00

Add test bufioReadLine

Original: 01f576af87
This commit is contained in:
Ryan Dahl 2018-11-08 02:05:06 -05:00
parent 5f74f7eebe
commit d35e13e6be
2 changed files with 106 additions and 47 deletions

View file

@ -12,11 +12,7 @@ const MAX_CONSECUTIVE_EMPTY_READS = 100;
const CR = charCode("\r"); const CR = charCode("\r");
const LF = charCode("\n"); const LF = charCode("\n");
export enum BufState { export type BufState = null | "EOF" | "BufferFull" | "NoProgress" | Error;
Ok,
EOF,
BufferFull
}
/** BufReader implements buffering for a Reader object. */ /** BufReader implements buffering for a Reader object. */
export class BufReader implements Reader { export class BufReader implements Reader {
@ -26,7 +22,7 @@ export class BufReader implements Reader {
private w = 0; // buf write position. private w = 0; // buf write position.
private lastByte: number; private lastByte: number;
private lastCharSize: number; private lastCharSize: number;
private err: null | Error; private err: BufState;
constructor(rd: Reader, size = DEFAULT_BUF_SIZE) { constructor(rd: Reader, size = DEFAULT_BUF_SIZE) {
if (size < MIN_BUF_SIZE) { if (size < MIN_BUF_SIZE) {
@ -44,7 +40,7 @@ export class BufReader implements Reader {
return this.w - this.r; return this.w - this.r;
} }
private _readErr(): Error { private _readErr(): BufState {
const err = this.err; const err = this.err;
this.err = null; this.err = null;
return err; return err;
@ -52,7 +48,7 @@ export class BufReader implements Reader {
// Reads a new chunk into the buffer. // Reads a new chunk into the buffer.
// Returns true if EOF, false on successful read. // Returns true if EOF, false on successful read.
private async _fill(): Promise<BufState> { private async _fill(): Promise<void> {
// Slide existing data to beginning. // Slide existing data to beginning.
if (this.r > 0) { if (this.r > 0) {
this.buf.copyWithin(0, this.r, this.w); this.buf.copyWithin(0, this.r, this.w);
@ -71,18 +67,19 @@ export class BufReader implements Reader {
rr = await this.rd.read(this.buf.subarray(this.w)); rr = await this.rd.read(this.buf.subarray(this.w));
} catch (e) { } catch (e) {
this.err = e; this.err = e;
return BufState.Ok; return;
} }
assert(rr.nread >= 0, "negative read"); assert(rr.nread >= 0, "negative read");
this.w += rr.nread; this.w += rr.nread;
if (rr.eof) { if (rr.eof) {
return BufState.EOF; this.err = "EOF";
return;
} }
if (rr.nread > 0) { if (rr.nread > 0) {
return BufState.Ok; return;
} }
} }
throw Error("No Progress"); this.err = "NoProgress";
} }
/** Discards any buffered data, resets all state, and switches /** Discards any buffered data, resets all state, and switches
@ -96,7 +93,7 @@ export class BufReader implements Reader {
this.buf = buf; this.buf = buf;
this.rd = rd; this.rd = rd;
this.lastByte = -1; this.lastByte = -1;
this.lastCharSize = -1; // this.lastRuneSize = -1;
} }
/** reads data into p. /** reads data into p.
@ -163,13 +160,13 @@ export class BufReader implements Reader {
/** Returns the next byte [0, 255] or -1 if EOF. */ /** Returns the next byte [0, 255] or -1 if EOF. */
async readByte(): Promise<number> { async readByte(): Promise<number> {
while (this.r === this.w) { while (this.r === this.w) {
const eof = await this._fill(); // buffer is empty. await this._fill(); // buffer is empty.
if (this.err == "EOF") {
return -1;
}
if (this.err != null) { if (this.err != null) {
throw this._readErr(); throw this._readErr();
} }
if (eof) {
return -1;
}
} }
const c = this.buf[this.r]; const c = this.buf[this.r];
this.r++; this.r++;
@ -206,20 +203,10 @@ export class BufReader implements Reader {
* (possibly a character belonging to the line end) even if that byte is not * (possibly a character belonging to the line end) even if that byte is not
* part of the line returned by ReadLine. * part of the line returned by ReadLine.
*/ */
async readLine(): Promise<{ async readLine(): Promise<[Uint8Array, boolean, BufState]> {
line?: Uint8Array; let [line, err] = await this.readSlice(LF);
isPrefix: boolean;
state: BufState;
}> {
let line: Uint8Array;
let state: BufState;
try {
[line, state] = await this.readSlice(LF);
} catch (err) {
this.err = err;
}
if (state === BufState.BufferFull) { if (err === "BufferFull") {
// Handle the case where "\r\n" straddles the buffer. // Handle the case where "\r\n" straddles the buffer.
if (line.byteLength > 0 && line[line.byteLength - 1] === CR) { if (line.byteLength > 0 && line[line.byteLength - 1] === CR) {
// Put the '\r' back on buf and drop it from line. // Put the '\r' back on buf and drop it from line.
@ -228,12 +215,13 @@ export class BufReader implements Reader {
this.r--; this.r--;
line = line.subarray(0, line.byteLength - 1); line = line.subarray(0, line.byteLength - 1);
} }
return { line, isPrefix: true, state }; return [line, true, null];
} }
if (line.byteLength === 0) { if (line.byteLength === 0) {
return { line, isPrefix: false, state }; return [line, false, err];
} }
err = null;
if (line[line.byteLength - 1] == LF) { if (line[line.byteLength - 1] == LF) {
let drop = 1; let drop = 1;
@ -242,7 +230,7 @@ export class BufReader implements Reader {
} }
line = line.subarray(0, line.byteLength - drop); line = line.subarray(0, line.byteLength - drop);
} }
return { line, isPrefix: false, state }; return [line, false, err];
} }
/** readSlice() reads until the first occurrence of delim in the input, /** readSlice() reads until the first occurrence of delim in the input,
@ -258,7 +246,7 @@ export class BufReader implements Reader {
async readSlice(delim: number): Promise<[Uint8Array, BufState]> { async readSlice(delim: number): Promise<[Uint8Array, BufState]> {
let s = 0; // search start index let s = 0; // search start index
let line: Uint8Array; let line: Uint8Array;
let state = BufState.Ok; let err: BufState;
while (true) { while (true) {
// Search buffer. // Search buffer.
let i = this.buf.subarray(this.r + s, this.w).indexOf(delim); let i = this.buf.subarray(this.r + s, this.w).indexOf(delim);
@ -273,7 +261,7 @@ export class BufReader implements Reader {
if (this.err) { if (this.err) {
line = this.buf.subarray(this.r, this.w); line = this.buf.subarray(this.r, this.w);
this.r = this.w; this.r = this.w;
throw this._readErr(); err = this._readErr();
break; break;
} }
@ -281,7 +269,7 @@ export class BufReader implements Reader {
if (this.buffered() >= this.buf.byteLength) { if (this.buffered() >= this.buf.byteLength) {
this.r = this.w; this.r = this.w;
line = this.buf; line = this.buf;
state = BufState.BufferFull; err = "BufferFull";
break; break;
} }
@ -297,6 +285,6 @@ export class BufReader implements Reader {
// this.lastRuneSize = -1 // this.lastRuneSize = -1
} }
return [line, state]; return [line, err];
} }
} }

View file

@ -3,12 +3,18 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
import * as deno from "deno"; import { Reader, ReadResult } from "deno";
import { test, assertEqual } from "https://deno.land/x/testing/testing.ts"; import {
test,
assert,
assertEqual
} from "https://deno.land/x/testing/testing.ts";
import { BufReader, BufState } from "./bufio.ts"; import { BufReader, BufState } from "./bufio.ts";
import { Buffer } from "./buffer.ts"; import { Buffer } from "./buffer.ts";
import * as iotest from "./iotest.ts"; import * as iotest from "./iotest.ts";
import { charCode } from "./util.ts"; import { charCode, copyBytes } from "./util.ts";
const encoder = new TextEncoder();
async function readBytes(buf: BufReader): Promise<string> { async function readBytes(buf: BufReader): Promise<string> {
const b = new Uint8Array(1000); const b = new Uint8Array(1000);
@ -25,8 +31,7 @@ async function readBytes(buf: BufReader): Promise<string> {
return decoder.decode(b.subarray(0, nb)); return decoder.decode(b.subarray(0, nb));
} }
function stringsReader(s: string): deno.Reader { function stringsReader(s: string): Reader {
const encoder = new TextEncoder();
const ui8 = encoder.encode(s); const ui8 = encoder.encode(s);
return new Buffer(ui8.buffer as ArrayBuffer); return new Buffer(ui8.buffer as ArrayBuffer);
} }
@ -38,7 +43,7 @@ test(async function bufioReaderSimple() {
assertEqual(s, data); assertEqual(s, data);
}); });
type ReadMaker = { name: string; fn: (r: deno.Reader) => deno.Reader }; type ReadMaker = { name: string; fn: (r: Reader) => Reader };
const readMakers: ReadMaker[] = [ const readMakers: ReadMaker[] = [
{ name: "full", fn: r => r }, { name: "full", fn: r => r },
@ -134,15 +139,81 @@ test(async function bufioBufferFull() {
const longString = const longString =
"And now, hello, world! It is the time for all good men to come to the aid of their party"; "And now, hello, world! It is the time for all good men to come to the aid of their party";
const buf = new BufReader(stringsReader(longString), MIN_READ_BUFFER_SIZE); const buf = new BufReader(stringsReader(longString), MIN_READ_BUFFER_SIZE);
let [line, state] = await buf.readSlice(charCode("!")); let [line, err] = await buf.readSlice(charCode("!"));
const decoder = new TextDecoder(); const decoder = new TextDecoder();
let actual = decoder.decode(line); let actual = decoder.decode(line);
assertEqual(state, BufState.BufferFull); assertEqual(err, "BufferFull");
assertEqual(actual, "And now, hello, "); assertEqual(actual, "And now, hello, ");
[line, state] = await buf.readSlice(charCode("!")); [line, err] = await buf.readSlice(charCode("!"));
actual = decoder.decode(line); actual = decoder.decode(line);
assertEqual(actual, "world!"); assertEqual(actual, "world!");
assertEqual(state, BufState.Ok); assert(err == null);
});
const testInput = encoder.encode(
"012\n345\n678\n9ab\ncde\nfgh\nijk\nlmn\nopq\nrst\nuvw\nxy"
);
const testInputrn = encoder.encode(
"012\r\n345\r\n678\r\n9ab\r\ncde\r\nfgh\r\nijk\r\nlmn\r\nopq\r\nrst\r\nuvw\r\nxy\r\n\n\r\n"
);
const testOutput = encoder.encode("0123456789abcdefghijklmnopqrstuvwxy");
// TestReader wraps a Uint8Array and returns reads of a specific length.
class TestReader implements Reader {
constructor(private data: Uint8Array, private stride: number) {}
async read(buf: ArrayBufferView): Promise<ReadResult> {
let nread = this.stride;
if (nread > this.data.byteLength) {
nread = this.data.byteLength;
}
if (nread > buf.byteLength) {
nread = buf.byteLength;
}
copyBytes(buf as Uint8Array, this.data);
this.data = this.data.subarray(nread);
let eof = false;
if (this.data.byteLength == 0) {
eof = true;
}
return { nread, eof };
}
}
async function testReadLine(input: Uint8Array): Promise<void> {
for (let stride = 1; stride < 2; stride++) {
let done = 0;
let reader = new TestReader(input, stride);
let l = new BufReader(reader, input.byteLength + 1);
while (true) {
let [line, isPrefix, err] = await l.readLine();
if (line.byteLength > 0 && err != null) {
throw Error("readLine returned both data and error");
}
assertEqual(isPrefix, false);
if (err == "EOF") {
break;
}
let want = testOutput.subarray(done, done + line.byteLength);
assertEqual(
line,
want,
`Bad line at stride ${stride}: want: ${want} got: ${line}`
);
done += line.byteLength;
}
assertEqual(
done,
testOutput.byteLength,
`readLine didn't return everything: got: ${done}, ` +
`want: ${testOutput} (stride: ${stride})`
);
}
}
test(async function bufioReadLine() {
await testReadLine(testInput);
await testReadLine(testInputrn);
}); });