preserves/implementations/javascript/test/test-codec.js

245 lines
9.5 KiB
JavaScript

"use strict";
const assert = require('assert');
const Immutable = require('immutable');
const Preserves = require('../src/index.js');
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 Discard = Record.makeConstructor('discard', []);
const Capture = Record.makeConstructor('capture', ['pattern']);
const Observe = Record.makeConstructor('observe', ['pattern']);
describe('record constructors', () => {
it('should have constructorInfo', () => {
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);
});
})
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', () => {
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', () => {
assert.notStrictEqual(C1.constructorInfo, C2.constructorInfo);
});
it('comparison based on .equals should work', () => {
assert(is(C1.constructorInfo, C2.constructorInfo));
});
});
describe('records', () => {
it('should have correct getConstructorInfo', () => {
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));
});
});
class SimpleStream {
constructor(t, n, items) {
this.t = t;
this.n = n;
this.items = items;
}
[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); }}
// Not needed -- an ordinary array will do!
// 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 TestCases = Record.makeConstructor('TestCases', ['mapping', 'cases']);
const ExpectedPlaceholderMapping =
Record.makeConstructor('ExpectedPlaceholderMapping', ['table']);
const expectedPlaceholderMapping = TestCases._mapping(samples.peel()).strip();
const placeholders_decode = ExpectedPlaceholderMapping._table(expectedPlaceholderMapping);
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: [1, 2, 3, 4],
back: List([1, 2, 3, 4]) },
list2: {
forward: [ new StringStream([Bytes('abc')]),
new StringStream([Bytes('def')]), ],
back: List(["abc", "def"])
},
list3: {
forward: [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);
}
})();
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 !== 'decode' && variety !== 'nondeterministic') {
it('should encode correctly',
() => assert(is(E(forward), binaryForm),
E(forward) + ' ' + binaryForm));
}
if (variety !== 'decode' && variety !== 'nondeterministic' && variety !== 'streaming') {
it('should encode correctly with annotations',
() => assert(is(E(annotatedTextForm), binaryForm),
E(annotatedTextForm) + ' ' + binaryForm));
}
});
}
const tests = TestCases._cases(samples.peel()).peel();
tests.forEach((t0, tName0) => {
const tName = Symbol.keyFor(tName0.strip());
const t = t0.peel();
switch (t.label) {
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('DecodeTest'):
runTestCase('decode', 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);
assert(!(e instanceof ShortPacket));
}
});
});
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;
console.error(e);
throw e;
}
}
});
});
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));
assert.strictEqual(new Decoder(bs).next(), "333");
});
});