241 lines
9.1 KiB
JavaScript
241 lines
9.1 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', ['cases']);
|
|
|
|
function DS(bs) {
|
|
return decode(bs);
|
|
}
|
|
function D(bs) {
|
|
return decodeWithAnnotations(bs);
|
|
}
|
|
function E(v) {
|
|
return encode(v);
|
|
}
|
|
|
|
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('DecodeEOF'): // fall through
|
|
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('ParseEOF'):
|
|
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");
|
|
});
|
|
});
|