From 9b014313dbec50da61213b5797d54ba1a5b4a530 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 7 Nov 2018 20:28:01 -0500 Subject: [PATCH] Add bufio tests. Original: https://github.com/denoland/deno_std/commit/aa9e3df0d21e927ec86209c8cc81d5f1a27a0736 --- bufio.ts | 55 +++++++++++++++++++++++++++++++++++++--------- bufio_test.ts | 34 +++++++++++++++++++++------- iotest.ts | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 iotest.ts diff --git a/bufio.ts b/bufio.ts index 805a8136ed..c1fcf9c473 100644 --- a/bufio.ts +++ b/bufio.ts @@ -24,6 +24,7 @@ export class Reader implements deno.Reader { private w = 0; // buf write position. private lastByte: number; private lastCharSize: number; + private err: null | Error; constructor(rd: deno.Reader, size = DEFAULT_BUF_SIZE) { if (size < MIN_BUF_SIZE) { @@ -37,9 +38,15 @@ export class Reader implements deno.Reader { return this.buf.byteLength; } + private _readErr(): Error { + const err = this.err; + this.err = null; + return err; + } + // Reads a new chunk into the buffer. // Returns true if EOF, false on successful read. - async _fill(): Promise { + private async _fill(): Promise { // Slide existing data to beginning. if (this.r > 0) { this.buf.copyWithin(0, this.r, this.w); @@ -53,15 +60,21 @@ export class Reader implements deno.Reader { // Read new data: try a limited number of times. for (let i = MAX_CONSECUTIVE_EMPTY_READS; i > 0; i--) { - const { nread, eof } = await this.rd.read(this.buf.subarray(this.w)); - if (nread < 0) { + let rr: deno.ReadResult; + try { + rr = await this.rd.read(this.buf.subarray(this.w)); + } catch (e) { + this.err = e; + return false; + } + if (rr.nread < 0) { throw new ErrNegativeRead(); } - this.w += nread; - if (eof) { + this.w += rr.nread; + if (rr.eof) { return true; } - if (nread > 0) { + if (rr.nread > 0) { return false; } } @@ -92,15 +105,16 @@ export class Reader implements deno.Reader { async read(p: ArrayBufferView): Promise { let rr: deno.ReadResult = { nread: p.byteLength, eof: false }; if (rr.nread === 0) { + if (this.err) { + throw this._readErr(); + } return rr; } if (this.r === this.w) { - /* - if (this.err != null) { - throw this.readErr(); + if (this.err) { + throw this._readErr(); } - */ if (p.byteLength >= this.buf.byteLength) { // Large read, empty buffer. // Read directly into p to avoid copy. @@ -112,6 +126,9 @@ export class Reader implements deno.Reader { this.lastByte = p[rr.nread - 1]; // this.lastRuneSize = -1; } + if (this.err) { + throw this._readErr(); + } return rr; } // One read. @@ -123,6 +140,9 @@ export class Reader implements deno.Reader { throw new ErrNegativeRead(); } if (rr.nread === 0) { + if (this.err) { + throw this._readErr(); + } return rr; } this.w += rr.nread; @@ -140,6 +160,9 @@ export class Reader implements deno.Reader { async readByte(): Promise { while (this.r === this.w) { const eof = await this._fill(); // buffer is empty. + if (this.err != null) { + throw this._readErr(); + } if (eof) { return -1; } @@ -149,4 +172,16 @@ export class Reader implements deno.Reader { this.lastByte = c; return c; } + + /** readString() reads until the first occurrence of delim in the input, + * returning a string containing the data up to and including the delimiter. + * If ReadString encounters an error before finding a delimiter, + * it returns the data read before the error and the error itself (often io.EOF). + * ReadString returns err != nil if and only if the returned data does not end in + * delim. + * For simple uses, a Scanner may be more convenient. + */ + async readString(delim: string): Promise { + throw new Error("Not implemented"); + } } diff --git a/bufio_test.ts b/bufio_test.ts index 1600852afd..dc16f5e69f 100644 --- a/bufio_test.ts +++ b/bufio_test.ts @@ -7,6 +7,7 @@ import * as deno from "deno"; import { test, assertEqual } from "http://deno.land/x/testing/testing.ts"; import * as bufio from "./bufio.ts"; import { Buffer } from "./buffer.ts"; +import * as iotest from "./iotest.ts"; async function readBytes(buf: bufio.Reader): Promise { const b = new Uint8Array(1000); @@ -39,15 +40,25 @@ test(async function bufioReaderSimple() { type ReadMaker = { name: string; fn: (r: deno.Reader) => deno.Reader }; const readMakers: ReadMaker[] = [ - { name: "full", fn: r => r } - /* - { name: "byte", fn(r) => new iotest.OneByteReader(r) }, - { name: "half", fn(r) => new iotest.HalfReader(r) }, - { name: "data+err", r => new iotest.DataErrReader(r) }, - { name: "timeout", r => new iotest.TimeoutReader(r) }, - */ + { name: "full", fn: r => r }, + { name: "byte", fn: r => new iotest.OneByteReader(r) }, + { name: "half", fn: r => new iotest.HalfReader(r) } + // TODO { name: "data+err", r => new iotest.DataErrReader(r) }, + // { name: "timeout", fn: r => new iotest.TimeoutReader(r) }, ]; +function readLines(b: bufio.Reader): string { + let s = ""; + while (true) { + let s1 = b.readString("\n"); + if (s1 == null) { + break; // EOF + } + s += s1; + } + return s; +} + // Call read to accumulate the text of a file async function reads(buf: bufio.Reader, m: number): Promise { const b = new Uint8Array(1000); @@ -66,7 +77,14 @@ async function reads(buf: bufio.Reader, m: number): Promise { type BufReader = { name: string; fn: (r: bufio.Reader) => Promise }; const bufreaders: BufReader[] = [ - { name: "1", fn: (b: bufio.Reader) => reads(b, 1) } + { name: "1", fn: (b: bufio.Reader) => reads(b, 1) }, + { name: "2", fn: (b: bufio.Reader) => reads(b, 2) }, + { name: "3", fn: (b: bufio.Reader) => reads(b, 3) }, + { name: "4", fn: (b: bufio.Reader) => reads(b, 4) }, + { name: "5", fn: (b: bufio.Reader) => reads(b, 5) }, + { name: "7", fn: (b: bufio.Reader) => reads(b, 7) }, + { name: "bytes", fn: readBytes } + // { name: "lines", fn: readLines }, ]; const MIN_READ_BUFFER_SIZE = 16; diff --git a/iotest.ts b/iotest.ts new file mode 100644 index 0000000000..e2498e1557 --- /dev/null +++ b/iotest.ts @@ -0,0 +1,61 @@ +// Ported to Deno from +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import { Reader, ReadResult } from "deno"; + +/** OneByteReader returns a Reader that implements + * each non-empty Read by reading one byte from r. + */ +export class OneByteReader implements Reader { + constructor(readonly r: Reader) {} + + async read(p: ArrayBufferView): Promise { + if (p.byteLength === 0) { + return { nread: 0, eof: false }; + } + if (!(p instanceof Uint8Array)) { + throw Error("expected Uint8Array"); + } + return this.r.read(p.subarray(0, 1)); + } +} + +/** HalfReader returns a Reader that implements Read + * by reading half as many requested bytes from r. + */ +export class HalfReader implements Reader { + constructor(readonly r: Reader) {} + + async read(p: ArrayBufferView): Promise { + if (!(p instanceof Uint8Array)) { + throw Error("expected Uint8Array"); + } + const half = Math.floor((p.byteLength + 1) / 2); + return this.r.read(p.subarray(0, half)); + } +} + +export class ErrTimeout extends Error { + constructor() { + super("timeout"); + this.name = "ErrTimeout"; + } +} + +/** TimeoutReader returns ErrTimeout on the second read + * with no data. Subsequent calls to read succeed. + */ +export class TimeoutReader implements Reader { + count = 0; + constructor(readonly r: Reader) {} + + async read(p: ArrayBufferView): Promise { + this.count++; + if (this.count === 2) { + throw new ErrTimeout(); + } + return this.r.read(p); + } +}