js: Update tests, Immutable.js; implement more tests; all tests pass

This commit is contained in:
Tony Garnock-Jones 2019-08-29 21:07:17 +01:00
parent 102cb93f26
commit 04ecbe03e3
8 changed files with 346 additions and 207 deletions

View File

@ -15,12 +15,10 @@
"main": "src/index.js",
"author": "Tony Garnock-Jones <tonyg@leastfixedpoint.com>",
"devDependencies": {
"chai": "^4.2.0",
"chai-immutable": "^2.0.0-rc.2",
"mocha": "^5.2.0",
"nyc": "^13.1.0"
},
"dependencies": {
"immutable": "^3.8.2"
"immutable": "^4.0.0-rc.12"
}
}

View File

@ -6,7 +6,7 @@ if (require('./singletonmodule.js')('leastfixedpoint.com/preserves',
'annotations.js',
module)) return;
const { Record, List, Map, Set } = require('./values.js');
const { Record, List, Map, Set, is, hash } = require('./values.js');
const { PreserveOn, AsPreserve } = require('./symbols.js');
function Annotated(item) {
@ -26,23 +26,39 @@ Annotated.prototype[PreserveOn] = function (encoder) {
encoder.push(this.item);
};
Annotated.prototype.strip = function (depth) {
return stripAnnotations(this, depth);
};
Annotated.prototype.equals = function (other) {
return isAnnotated(other) && is(this.item, other.item);
};
Annotated.prototype.hashCode = function () {
return hash(this.item);
};
function isAnnotated(v) {
return (v instanceof Annotated);
}
function stripAnnotations(v, depth) {
function step(v, depth) {
if (depth === 0) return v;
if (!(v instanceof Annotated)) return v;
if (!isAnnotated(v)) return v;
const nextDepth = depth - 1;
function walk(v) { return step(v, nextDepth); }
if (v.item instanceof Record) {
return Record(walk(v.item.label), v.item.fields.map(walk));
return new Record(walk(v.item.label), v.item.fields.map(walk));
} else if (List.isList(v.item)) {
return v.item.map(walk);
} else if (Set.isSet(v.item)) {
return v.item.map(walk);
} else if (Map.isMap(v.item)) {
return v.item.mapEntries((e) => [walk(e[0]), walk(e[1])]);
} else if (v.item instanceof Annotated) {
} else if (isAnnotated(v.item)) {
const e = new Error("Improper annotation structure");
e.irritant = v;
throw e;
@ -50,5 +66,20 @@ function stripAnnotations(v, depth) {
return v.item;
}
}
step(v, (depth === void 0) ? Infinity : depth);
return step(v, (depth === void 0) ? Infinity : depth);
}
function annotate(v, ...anns) {
if (!isAnnotated(v)) {
v = new Annotated(v);
}
anns.forEach((a) => v.annotations.push(a));
return v;
}
Object.assign(module.exports, {
Annotated,
isAnnotated,
stripAnnotations,
annotate,
});

View File

@ -8,7 +8,7 @@ if (require('./singletonmodule.js')('leastfixedpoint.com/preserves',
const Values = require('./values.js');
const Annotations = require('./annotations.js');
const { List, Map, Set, Bytes, Record, Single, Double } = Values;
const { fromJS, List, Map, Set, Bytes, Record, Single, Double } = Values;
const { PreserveOn } = require('./symbols.js');
@ -28,6 +28,7 @@ class Decoder {
? (packet._view || packet) // strip off Bytes wrapper, if any
: new Uint8Array(0);
this.index = 0;
this.placeholders = fromJS(options.placeholders || {});
this.includeAnnotations = options.includeAnnotations || false;
}
@ -77,7 +78,7 @@ class Decoder {
}
peekend() {
const result = this.nextop() === 4;
const result = this.nextbyte() === 4;
if (!result) this.index--;
return result;
}
@ -143,6 +144,17 @@ class Decoder {
});
}
wrap(v) {
return this.includeAnnotations ? new Annotations.Annotated(v) : v;
}
unshiftAnnotation(a, v) {
if (this.includeAnnotations) {
v.annotations.unshift(a);
}
return v;
}
next() {
const [major, minor, arg] = this.nextop();
switch (major) {
@ -150,33 +162,45 @@ class Decoder {
switch (minor) {
case 0:
switch (arg) {
case 0: return false;
case 1: return true;
case 2: return Single(this.nextbytes(4).getFloat32(0, false));
case 3: return Double(this.nextbytes(8).getFloat64(0, false));
case 0: return this.wrap(false);
case 1: return this.wrap(true);
case 2: return this.wrap(Single(this.nextbytes(4).getFloat32(0, false)));
case 3: return this.wrap(Double(this.nextbytes(8).getFloat64(0, false)));
case 4: throw new DecodeError("Unexpected end-of-stream marker");
case 5: ...annotation...;
case 5: {
const a = this.next();
const v = this.next();
return this.unshiftAnnotation(a, v);
}
default: throw new DecodeError("Illegal format A lead byte");
}
case 1:
...placeholder...;
case 1: {
const n = this.wirelength(arg);
const v = this.placeholders.get(n, void 0);
if (typeof v === 'undefined') {
const e = new DecodeError("Invalid Preserves placeholder");
e.irritant = n;
throw e;
}
return this.wrap(v);
}
case 2: {
const t = arg >> 2;
const n = arg & 3;
switch (t) {
case 0: throw new DecodeError("Invalid format C start byte (0)");
case 1: return this.binarystream(n);
case 2: return this.valuestream(n);
case 1: return this.wrap(this.binarystream(n));
case 2: return this.wrap(this.valuestream(n));
case 3: throw new DecodeError("Invalid format C start byte (3)");
}
}
case 3:
return (arg > 12) ? arg - 16 : arg;
return this.wrap((arg > 12) ? arg - 16 : arg);
}
case 1:
return this.decodebinary(minor, Bytes.from(this.nextbytes(this.wirelength(arg))));
return this.wrap(this.decodebinary(minor, Bytes.from(this.nextbytes(this.wirelength(arg)))));
case 2:
return this.decodecompound(minor, this.nextvalues(this.wirelength(arg)));
return this.wrap(this.decodecompound(minor, this.nextvalues(this.wirelength(arg))));
case 3:
throw new DecodeError("Invalid lead byte (major 3)");
}
@ -196,12 +220,23 @@ class Decoder {
}
}
function decode(bs, options) {
return new Decoder(bs, options).next();
}
function decodeWithAnnotations(bs, options) {
options = options || {};
options.includeAnnotations = true;
return decode(bs, options);
}
class Encoder {
constructor(options) {
options = options || {}
this.chunks = [];
this.view = new DataView(new ArrayBuffer(256));
this.index = 0;
this.placeholders = fromJS(options.placeholders || {});
}
contents() {
@ -281,8 +316,11 @@ class Encoder {
}
push(v) {
...placeholder...;
if (typeof v === 'object' && v !== null && typeof v[PreserveOn] === 'function') {
const placeholder = this.placeholders.get(v, void 0);
if (typeof placeholder !== 'undefined') {
this.header(0, 1, placeholder);
}
else if (typeof v === 'object' && v !== null && typeof v[PreserveOn] === 'function') {
v[PreserveOn](this);
}
else if (typeof v === 'boolean') {
@ -329,7 +367,7 @@ class Encoder {
}));
}
else if (typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function') {
this.encodestream(2, 0, v);
this.encodestream(2, 1, v);
}
else {
throw new EncodeError("Cannot encode", v);
@ -338,10 +376,17 @@ class Encoder {
}
}
function encode(v, options) {
return new Encoder(options).push(v).contents();
}
Object.assign(module.exports, {
DecodeError,
EncodeError,
ShortPacket,
Decoder,
decode,
decodeWithAnnotations,
Encoder,
encode,
});

View File

@ -10,7 +10,7 @@ if (require('./singletonmodule.js')('leastfixedpoint.com/preserves',
const util = require('util');
const Immutable = require('immutable');
const { List, Map, Set, is } = Immutable;
const { List, Map, Set, is, hash } = Immutable;
const { PreserveOn, AsPreserve } = require('./symbols.js');
@ -413,7 +413,7 @@ RecordConstructorInfo.prototype.isClassOf = function (v) {
Object.assign(module.exports, {
fromJS,
List, Map, Set, is,
List, Map, Set, is, hash,
Float, Single, Double,
Bytes,
Record, RecordConstructorInfo,

View File

@ -1,9 +1,6 @@
"use strict";
const chai = require('chai');
const expect = chai.expect;
chai.use(require('chai-immutable'));
const assert = require('assert');
const Immutable = require('immutable');
const { is, Bytes, fromJS } = require('../src/index.js');
@ -12,75 +9,76 @@ describe('immutable byte arrays', () => {
describe('Uint8Array methods', () => {
const bs = Bytes.of(10, 20, 30, 40);
it('should yield entries', () => {
expect(fromJS(Array.from(bs.entries()))).to.equal(fromJS([[0,10],[1,20],[2,30],[3,40]]));
assert(is(fromJS(Array.from(bs.entries())),
fromJS([[0,10],[1,20],[2,30],[3,40]])));
});
it('should implement every', () => {
expect(bs.every((b) => !(b & 1))).to.be.true;
expect(bs.every((b) => b !== 50)).to.be.true;
expect(bs.every((b) => b !== 20)).to.be.false;
assert(bs.every((b) => !(b & 1)));
assert(bs.every((b) => b !== 50));
assert(!(bs.every((b) => b !== 20)));
});
it('should implement find', () => {
expect(bs.find((b) => b > 20)).to.equal(30);
expect(bs.find((b) => b > 50)).to.be.undefined;
assert.strictEqual(bs.find((b) => b > 20), 30);
assert.strictEqual(bs.find((b) => b > 50), void 0);
});
it('should implement findIndex', () => {
expect(bs.findIndex((b) => b > 20)).to.equal(2);
expect(bs.findIndex((b) => b > 50)).to.equal(-1);
assert.strictEqual(bs.findIndex((b) => b > 20), 2);
assert.strictEqual(bs.findIndex((b) => b > 50), -1);
});
it('should implement forEach', () => {
const vs = [];
bs.forEach((b) => vs.push(b));
expect(fromJS(vs)).to.equal(fromJS([10, 20, 30, 40]));
assert(is(fromJS(vs), fromJS([10, 20, 30, 40])));
});
it('should implement includes', () => {
expect(bs.includes(20)).to.be.true;
expect(bs.includes(50)).to.be.false;
assert(bs.includes(20));
assert(!bs.includes(50));
});
it('should implement indexOf', () => {
expect(bs.indexOf(20)).to.equal(1);
expect(bs.indexOf(50)).to.equal(-1);
assert.strictEqual(bs.indexOf(20), 1);
assert.strictEqual(bs.indexOf(50), -1);
});
it('should implement join', () => expect(bs.join('-')).to.equal('10-20-30-40'));
it('should implement join', () => assert.strictEqual(bs.join('-'), '10-20-30-40'));
it('should implement keys', () => {
expect(fromJS(Array.from(bs.keys()))).to.equal(fromJS([0,1,2,3]));
assert(is(fromJS(Array.from(bs.keys())), fromJS([0,1,2,3])));
});
it('should implement values', () => {
expect(fromJS(Array.from(bs.values()))).to.equal(fromJS([10,20,30,40]));
assert(is(fromJS(Array.from(bs.values())), fromJS([10,20,30,40])));
});
it('should implement filter', () => {
expect(is(bs.filter((b) => b !== 30), Bytes.of(10,20,40))).to.be.true;
assert(is(bs.filter((b) => b !== 30), Bytes.of(10,20,40)));
});
it('should implement slice', () => {
const vs = bs.slice(2);
expect(Object.is(vs._view.buffer, bs._view.buffer)).to.be.false;
expect(vs._view.buffer.byteLength).to.equal(2);
expect(vs.get(0)).to.equal(30);
expect(vs.get(1)).to.equal(40);
expect(vs.size).to.equal(2);
assert(!Object.is(vs._view.buffer, bs._view.buffer));
assert.strictEqual(vs._view.buffer.byteLength, 2);
assert.strictEqual(vs.get(0), 30);
assert.strictEqual(vs.get(1), 40);
assert.strictEqual(vs.size, 2);
});
it('should implement subarray', () => {
const vs = bs.subarray(2);
expect(Object.is(vs._view.buffer, bs._view.buffer)).to.be.true;
expect(vs._view.buffer.byteLength).to.equal(4);
expect(vs.get(0)).to.equal(30);
expect(vs.get(1)).to.equal(40);
expect(vs.size).to.equal(2);
assert(Object.is(vs._view.buffer, bs._view.buffer));
assert.strictEqual(vs._view.buffer.byteLength, 4);
assert.strictEqual(vs.get(0), 30);
assert.strictEqual(vs.get(1), 40);
assert.strictEqual(vs.size, 2);
});
it('should implement reverse', () => {
const vs = bs.reverse();
expect(Object.is(vs._view.buffer, bs._view.buffer)).to.be.false;
expect(bs.get(0)).to.equal(10);
expect(bs.get(3)).to.equal(40);
expect(vs.get(0)).to.equal(40);
expect(vs.get(3)).to.equal(10);
assert(!Object.is(vs._view.buffer, bs._view.buffer));
assert.strictEqual(bs.get(0), 10);
assert.strictEqual(bs.get(3), 40);
assert.strictEqual(vs.get(0), 40);
assert.strictEqual(vs.get(3), 10);
});
it('should implement sort', () => {
const vs = bs.reverse().sort();
expect(Object.is(vs._view.buffer, bs._view.buffer)).to.be.false;
expect(bs.get(0)).to.equal(10);
expect(bs.get(3)).to.equal(40);
expect(vs.get(0)).to.equal(10);
expect(vs.get(3)).to.equal(40);
assert(!Object.is(vs._view.buffer, bs._view.buffer));
assert.strictEqual(bs.get(0), 10);
assert.strictEqual(bs.get(3), 40);
assert.strictEqual(vs.get(0), 10);
assert.strictEqual(vs.get(3), 40);
});
});
});

View File

@ -1,35 +1,34 @@
"use strict";
const chai = require('chai');
const expect = chai.expect;
chai.use(require('chai-immutable'));
const assert = require('assert');
const Immutable = require('immutable');
const Preserves = require('../src/index.js');
const { is, List, Set, Map, Decoder, Encoder, Bytes, Record, Single, Double } = Preserves;
const {
is, List, Set, Map,
Decoder, Encoder, decode, decodeWithAnnotations, encode,
DecodeError, EncodeError, ShortPacket,
Bytes, Record, Single, Double,
annotate,
stripAnnotations,
PreserveOn,
} = Preserves;
const fs = require('fs');
const util = require('util');
const shortForms = {
0: Symbol.for('discard'),
1: Symbol.for('capture'),
2: Symbol.for('observe'),
};
const Discard = Record.makeConstructor('discard', []);
const Capture = Record.makeConstructor('capture', ['pattern']);
const Observe = Record.makeConstructor('observe', ['pattern']);
describe('record constructors', () => {
it('should have constructorInfo', () => {
expect(Discard.constructorInfo.label).to.equal(Symbol.for('discard'));
expect(Capture.constructorInfo.label).to.equal(Symbol.for('capture'));
expect(Observe.constructorInfo.label).to.equal(Symbol.for('observe'));
expect(Discard.constructorInfo.arity).to.equal(0);
expect(Capture.constructorInfo.arity).to.equal(1);
expect(Observe.constructorInfo.arity).to.equal(1);
assert.strictEqual(Discard.constructorInfo.label, Symbol.for('discard'));
assert.strictEqual(Capture.constructorInfo.label, Symbol.for('capture'));
assert.strictEqual(Observe.constructorInfo.label, Symbol.for('observe'));
assert.strictEqual(Discard.constructorInfo.arity, 0);
assert.strictEqual(Capture.constructorInfo.arity, 1);
assert.strictEqual(Observe.constructorInfo.arity, 1);
});
})
@ -37,124 +36,198 @@ describe('RecordConstructorInfo', () => {
const C1 = Record.makeBasicConstructor(Immutable.List([1]), ['x', 'y']);
const C2 = Record.makeBasicConstructor(Immutable.List([1]), ['z', 'w']);
it('instance comparison should ignore pointer and fieldname differences', () => {
expect(is(C1(9,9), C2(9,9))).to.be.true;
expect(is(C1(9,9), C2(9,8))).to.be.false;
assert(is(C1(9,9), C2(9,9)));
assert(!is(C1(9,9), C2(9,8)));
});
it('comparison based on pointer equality should not work', () => {
expect(C1.constructorInfo === C2.constructorInfo).to.be.false;
assert.notStrictEqual(C1.constructorInfo, C2.constructorInfo);
});
it('comparison based on .equals should work', () => {
expect(is(C1.constructorInfo, C2.constructorInfo)).to.be.true;
assert(is(C1.constructorInfo, C2.constructorInfo));
});
});
describe('records', () => {
it('should have correct getConstructorInfo', () => {
expect(Discard().getConstructorInfo().equals(Discard.constructorInfo)).to.be.true;
expect(Capture(Discard()).getConstructorInfo().equals(Capture.constructorInfo)).to.be.true;
expect(Observe(Capture(Discard())).getConstructorInfo().equals(Observe.constructorInfo))
.to.be.true;
expect(is(Observe(Capture(Discard())).getConstructorInfo(), Observe.constructorInfo))
.to.be.true;
assert(Discard().getConstructorInfo().equals(Discard.constructorInfo));
assert(Capture(Discard()).getConstructorInfo().equals(Capture.constructorInfo));
assert(Observe(Capture(Discard())).getConstructorInfo().equals(Observe.constructorInfo));
assert(is(Observe(Capture(Discard())).getConstructorInfo(), Observe.constructorInfo));
});
});
describe('hex samples', () => {
const samples = fs.readFileSync(__dirname + '/samples.txt').toString().split(/\n/)
.filter((h) => h) // filters out empty lines
.map(Bytes.fromHex);
class SimpleStream {
constructor(t, n, items) {
this.t = t;
this.n = n;
this.items = items;
}
function manyFalses(n) {
return List().withMutations((l) => {
for (let i = 0; i < n; i++) { l.push(false); }
[PreserveOn](e) {
e.encodestream(this.t, this.n, this.items);
}
}
class StringStream extends SimpleStream { constructor(items) { super(1, 1, items); }}
class BytesStream extends SimpleStream { constructor(items) { super(1, 2, items); }}
class SymbolStream extends SimpleStream { constructor(items) { super(1, 3, items); }}
class RecordStream extends SimpleStream { constructor(items) { super(2, 0, items); }}
class SequenceStream extends SimpleStream { constructor(items) { super(2, 1, items); }}
class SetStream extends SimpleStream { constructor(items) { super(2, 2, items); }}
class DictionaryStream extends SimpleStream { constructor(items) { super(2, 3, items); }}
describe('common test suite', () => {
const samples_bin = fs.readFileSync(__dirname + '/../../../tests/samples.bin');
const samples = decodeWithAnnotations(samples_bin);
const expectedPlaceholderMapping = samples.item.get(0).strip();
const placeholders_decode = expectedPlaceholderMapping.get(0);
const placeholders_encode = placeholders_decode.mapEntries((e) => [e[1],e[0]]);
function DS(bs) {
return decode(bs, {placeholders: placeholders_decode});
}
function D(bs) {
return decodeWithAnnotations(bs, {placeholders: placeholders_decode});
}
function E(v) {
return encode(v, {placeholders: placeholders_encode});
}
const expectedValues = {
annotation1: { forward: annotate(9, "abc"),
back: 9 },
annotation2: { forward: annotate(List([List(), annotate(List(), "x")]), "abc", "def"),
back: List([List(), List()]) },
annotation3: { forward: annotate(5,
annotate(2, 1),
annotate(4, 3)),
back: 5 },
annotation5: { forward: annotate(new Record(Symbol.for('R'),
[annotate(Symbol.for('f'),
Symbol.for('af'))]),
Symbol.for('ar')),
back: new Record(Symbol.for('R'), [Symbol.for('f')]) },
annotation6: { forward: new Record(annotate(Symbol.for('R'),
Symbol.for('ar')),
[annotate(Symbol.for('f'),
Symbol.for('af'))]),
back: new Record(Symbol.for('R'), [Symbol.for('f')]) },
annotation7: { forward: annotate(List(), Symbol.for('a'), Symbol.for('b'), Symbol.for('c')),
back: List() },
bytes1: { forward: new BytesStream([Bytes('he'), Bytes('ll'), Bytes('o')]),
back: Bytes('hello') },
list1: { forward: new SequenceStream([1, 2, 3, 4]),
back: List([1, 2, 3, 4]) },
list2: {
forward: new SequenceStream([
new StringStream([Bytes('abc')]),
new StringStream([Bytes('def')]),
]),
back: List(["abc", "def"])
},
list3: {
forward: new SequenceStream([List(["a", 1]), List(["b", 2]), List(["c", 3])]),
back: List([List(["a", 1]), List(["b", 2]), List(["c", 3])])
},
record2: { value: Observe(new Record(Symbol.for("speak"), [
Discard(),
Capture(Discard())
])) },
string0a: { forward: new StringStream([]), back: '' },
string1: { forward: new StringStream([Bytes('he'), Bytes('ll'), Bytes('o')]),
back: 'hello' },
string2: { forward: new StringStream([Bytes('he'), Bytes('llo')]),
back: 'hello' },
symbol1: { forward: new SymbolStream([Bytes('he'), Bytes('ll'), Bytes('o')]),
back: Symbol.for('hello') },
};
function runTestCase(variety, tName, binaryForm, annotatedTextForm) {
describe(tName, () => {
const textForm = annotatedTextForm.strip();
const {forward, back} = (function () {
const entry = expectedValues[tName] || {value: textForm};
if ('value' in entry) {
return {forward: entry.value, back: entry.value};
} else if ('forward' in entry && 'back' in entry) {
return entry;
} else {
throw new Error('Invalid expectedValues entry for ' + tName);
}
})();
if (!is(textForm, back)) {
console.log(textForm, back);
}
it('should match the expected value', () => assert(is(textForm, back)));
it('should round-trip', () => assert(is(DS(E(textForm)), back)));
it('should go forward', () => assert(is(DS(E(forward)), back)));
it('should go back', () => assert(is(DS(binaryForm), back)));
it('should go back with annotations',
() => assert(is(D(E(annotatedTextForm)), annotatedTextForm)));
if (variety !== 'nondeterministic') {
it('should encode correctly',
() => assert(is(E(forward), binaryForm),
E(forward) + ' ' + binaryForm));
}
if (variety !== 'nondeterministic' && variety !== 'streaming') {
it('should encode correctly with annotations',
() => assert(is(E(annotatedTextForm), binaryForm),
E(annotatedTextForm) + ' ' + binaryForm));
}
});
}
// As new samples are added to samples.txt, we will need to update this list:
const samplesExpected = [
{ expected: new Single(1), },
{ expected: new Double(1), },
{ expected: new Double(-1.202e+300), },
{ expected: 0, },
{ expected: 1, },
{ expected: 12, },
{ expected: -3, },
{ expected: -2, },
{ expected: -1, },
{ expected: "hello", encodesTo: '5568656c6c6f', },
{ expected: "hello", encodesTo: '5568656c6c6f', },
{ expected: Bytes.from("hello"), encodesTo: '6568656c6c6f', },
{ expected: Symbol.for("hello"), encodesTo: '7568656c6c6f', },
{ expected: Immutable.Seq([1, 2, 3, 4]), },
{ expected: Preserves.fromJS(["abc", "def"]), encodesTo: 'c25361626353646566' },
{ expected: Preserves.fromJS([["a", 1], ["b", 2], ["c", 3]]),
encodesTo: 'c3c2516111c2516212c2516313', },
{ expected: 13, },
{ expected: 127, },
{ expected: -128, },
{ expected: -127, },
{ expected: -4, },
{ expected: 128, },
{ expected: 255, },
{ expected: 256, },
{ expected: 32767, },
{ expected: -257, },
{ expected: -256, },
{ expected: -255, },
{ expected: -254, },
{ expected: -129, },
{ expected: 32768, },
{ expected: 65535, },
{ expected: 65536, },
{ expected: 131072, },
{ expected: "hello", },
{ expected: Bytes.from("hello"), },
{ expected: Symbol.for("hello"), },
{ expected: Capture(Discard()), },
{ expected: Observe(new Record(Symbol.for('speak'), [Discard(), Capture(Discard())])), },
{ expected:
new Record([Symbol.for('titled'), Symbol.for('person'), 2, Symbol.for('thing'), 1],
[101, "Blackwell", new Record(Symbol.for('date'), [1821, 2, 3]), "Dr"]), },
{ expected: List([1, 2, 3, 4]), },
{ expected: List([-2, -1, 0, 1]), },
{ expected: Preserves.fromJS(["hello",
Symbol.for('there'),
Bytes.from('world'),
[],
Set(),
true,
false]), },
{ expected: manyFalses(14), },
{ expected: manyFalses(15), },
{ expected: manyFalses(100), },
{ expected: manyFalses(200), },
{ expected:
Map()
.set(Symbol.for('a'), 1)
.set('b', true)
.set(Preserves.fromJS([1, 2, 3]), Bytes.from('c'))
.set(Map().set(Symbol.for('first-name'), 'Elizabeth'),
Map().set(Symbol.for('surname'), 'Blackwell')), },
];
samples.forEach((s, sampleIndex) => {
it('[' + sampleIndex + '] ' + s.toHex() + ' should decode OK', () => {
const actual = new Decoder(s, { shortForms }).next();
const expected = samplesExpected[sampleIndex].expected;
expect(is(actual, expected),
'[' + sampleIndex + '] actual ' + util.inspect(actual) +
', expected ' + util.inspect(expected))
.to.be.true;
});
});
samples.forEach((s, sampleIndex) => {
it('[' + sampleIndex + '] ' + s.toHex() + ' should encode OK', () => {
const entry = samplesExpected[sampleIndex];
const actualHex = entry.encodesTo || s.toHex();
const expected = new Encoder({ shortForms }).push(entry.expected).contents();
expect(actualHex).to.equal(expected.toHex());
});
const tests = samples.item.get(1).item;
tests.forEach((t0, tName0) => {
const tName = Symbol.keyFor(tName0.strip());
const t = t0.item;
switch (t.label.item) {
case Symbol.for('Test'):
runTestCase('normal', tName, t.get(0).strip(), t.get(1));
break;
case Symbol.for('StreamingTest'):
runTestCase('streaming', tName, t.get(0).strip(), t.get(1));
break;
case Symbol.for('NondeterministicTest'):
runTestCase('nondeterministic', tName, t.get(0).strip(), t.get(1));
break;
case Symbol.for('DecodeError'):
describe(tName, () => {
it('should fail with DecodeError', () => {
try {
D(t.get(0).strip());
assert.fail("but it didn't");
} catch (e) {
assert(e instanceof DecodeError);
}
});
});
break;
case Symbol.for('DecodeShort'):
describe(tName, () => {
it('should fail with ShortPacket', () => {
try {
D(t.get(0).strip());
assert.fail("but it didn't");
} catch (e) {
assert(e instanceof ShortPacket);
}
});
});
break;
case Symbol.for('ParseError'):
case Symbol.for('ParseShort'):
/* Skipped for now, until we have an implementation of text syntax */
break;
default:{
const e = new Error('Unsupported test kind');
e.irritant = t;
e.testKind = t.label.item;
console.error(e);
throw e;
}
}
});
});
@ -162,6 +235,6 @@ describe('parsing from subarray', () => {
it('should maintain alignment of nextbytes', () => {
const u = Uint8Array.of(57, 57, 57, 57, 83, 51, 51, 51);
const bs = Bytes.from(u.subarray(4));
expect(new Decoder(bs).next()).to.equal("333");
assert.strictEqual(new Decoder(bs).next(), "333");
});
});

View File

@ -4,10 +4,7 @@
// there's a bunch of singleton hackery in values.js. These tests
// check that separate loads don't cause separate instances.
const chai = require('chai');
const expect = chai.expect;
chai.use(require('chai-immutable'));
const assert = require('assert');
const Immutable = require('immutable');
describe('reloading values.js', () => {
@ -19,25 +16,25 @@ describe('reloading values.js', () => {
const C2 = V2.Record.makeConstructor('c', ['a', 'b']);
it('should reuse RecordConstructorInfo (1)', () => {
expect(C1.constructorInfo instanceof V1.RecordConstructorInfo).to.be.true;
assert(C1.constructorInfo instanceof V1.RecordConstructorInfo);
});
it('should reuse RecordConstructorInfo (2)', () => {
expect(C1.constructorInfo instanceof V2.RecordConstructorInfo).to.be.true;
assert(C1.constructorInfo instanceof V2.RecordConstructorInfo);
});
it('should identify RecordConstructorInfo', () => {
expect(Object.is(V1.RecordConstructorInfo, V2.RecordConstructorInfo)).to.be.true;
assert(Object.is(V1.RecordConstructorInfo, V2.RecordConstructorInfo));
});
it('should produce identical module instances', () => { expect(V1 === V2).to.be.true; });
it('should produce identical module instances', () => { assert.strictEqual(V1, V2); });
it('should produce distinct constructor instances', () => { expect(C1 === C2).to.be.false; });
it('should produce distinct constructor instances', () => { assert.notStrictEqual(C1, C2); });
it('should produce distinct constructor info', () => {
expect(Object.is(C1.constructorInfo, C2.constructorInfo)).to.be.false;
assert(!Object.is(C1.constructorInfo, C2.constructorInfo));
});
it('should produce compatible constructor info', () => {
expect(Immutable.is(C1.constructorInfo, C2.constructorInfo)).to.be.true;
assert(Immutable.is(C1.constructorInfo, C2.constructorInfo));
});
it('should produce compatible record instances', () => {
expect(Immutable.is(C1(1,2), C2(1,2))).to.be.true;
assert(Immutable.is(C1(1,2), C2(1,2)));
});
});
@ -46,6 +43,6 @@ describe('reloading index.js', () => {
const I1 = require('../src/index.js');
delete require.cache[require.resolve('../src/index.js')];
const I2 = require('../src/index.js');
expect(Object.is(I1, I2)).to.be.true;
assert(Object.is(I1, I2));
});
});

View File

@ -1,21 +1,18 @@
"use strict";
const chai = require('chai');
const expect = chai.expect;
chai.use(require('chai-immutable'));
const assert = require('assert');
const Immutable = require('immutable');
const { is, Single, Double, fromJS } = require('../src/index.js');
describe('Single', () => {
it('should print reasonably', () => {
expect(Single(123.45).toString()).to.equal("123.45");
assert.strictEqual(Single(123.45).toString(), "123.45");
});
});
describe('Double', () => {
it('should print reasonably', () => {
expect(Double(123.45).toString()).to.equal("123.45");
assert.strictEqual(Double(123.45).toString(), "123.45");
});
});