preserves/implementations/javascript/test/codec.test.ts

179 lines
7.4 KiB
TypeScript
Raw Normal View History

import {
Value,
Dictionary,
decode, decodeWithAnnotations, encodeWithAnnotations,
isDecodeError, isShortPacket,
Bytes, Record,
annotate,
strip, peel,
preserves,
} from '../src/index';
import './test-utils';
import * as fs from 'fs';
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).toEqual(Symbol.for('discard'));
expect(Capture.constructorInfo.label).toEqual(Symbol.for('capture'));
expect(Observe.constructorInfo.label).toEqual(Symbol.for('observe'));
expect(Discard.constructorInfo.arity).toEqual(0);
expect(Capture.constructorInfo.arity).toEqual(1);
expect(Observe.constructorInfo.arity).toEqual(1);
});
})
describe('RecordConstructorInfo', () => {
const C1 = Record.makeBasicConstructor([1], ['x', 'y']);
const C2 = Record.makeBasicConstructor([1], ['z', 'w']);
it('instance comparison should ignore pointer and fieldname differences', () => {
expect(C1(9,9)).is(C2(9,9));
expect(C1(9,9)).not.is(C2(9,8));
});
it('comparison based on pointer equality should not work', () => {
expect(C1.constructorInfo).not.toBe(C2.constructorInfo);
});
it('comparison based on .equals should work', () => {
expect(C1.constructorInfo).toEqual(C2.constructorInfo);
});
});
describe('records', () => {
it('should have correct getConstructorInfo', () => {
expect(Discard().getConstructorInfo()).toEqual(Discard.constructorInfo);
expect(Capture(Discard()).getConstructorInfo()).toEqual(Capture.constructorInfo);
expect(Observe(Capture(Discard())).getConstructorInfo()).toEqual(Observe.constructorInfo);
});
});
describe('parsing from subarray', () => {
it('should maintain alignment of nextbytes', () => {
const u = Uint8Array.of(1, 1, 1, 1, 0xb1, 0x03, 0x33, 0x33, 0x33);
const bs = Bytes.from(u.subarray(4));
expect(decode(bs)).is("333");
});
});
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: Bytes) {
return decode(bs);
}
function D(bs: Bytes) {
return decodeWithAnnotations(bs);
}
function E(v: Value) {
return encodeWithAnnotations(v);
}
const expectedValues = {
annotation1: { forward: annotate(9, "abc"),
back: 9 },
annotation2: { forward: annotate([[], annotate([], "x")], "abc", "def"),
back: [[], []] },
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([], Symbol.for('a'), Symbol.for('b'), Symbol.for('c')),
back: [] },
list1: { forward: [1, 2, 3, 4],
back: [1, 2, 3, 4] },
record2: { value: Observe(new Record(Symbol.for("speak"), [
Discard(),
Capture(Discard())
])) },
};
type Variety = 'normal' | 'nondeterministic' | 'decode';
function runTestCase(variety: Variety, tName: string, binaryForm: Bytes, annotatedTextForm: Value) {
describe(tName, () => {
const textForm = strip(annotatedTextForm);
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', () => expect(textForm).is(back));
it('should round-trip', () => expect(DS(E(textForm))).is(back));
it('should go forward', () => expect(DS(E(forward))).is(back));
it('should go back', () => expect(DS(binaryForm)).is(back));
it('should go back with annotations',
() => expect(D(E(annotatedTextForm))).is(annotatedTextForm));
if (variety !== 'decode' && variety !== 'nondeterministic') {
it('should encode correctly', () => expect(E(forward)).is(binaryForm));
it('should encode correctly with annotations',
() => expect(E(annotatedTextForm)).is(binaryForm));
}
});
}
2021-01-09 15:21:25 +00:00
const tests = peel(TestCases._.cases(peel(samples))) as Dictionary<Value>;
tests.forEach((t0: Value, tName0: Value) => {
const tName = Symbol.keyFor(strip(tName0) as symbol);
const t = peel(t0) as Record;
switch (t.label) {
case Symbol.for('Test'):
runTestCase('normal', tName, strip(t[0]) as Bytes, t[1]);
break;
case Symbol.for('NondeterministicTest'):
runTestCase('nondeterministic', tName, strip(t[0]) as Bytes, t[1]);
break;
case Symbol.for('DecodeTest'):
runTestCase('decode', tName, strip(t[0]) as Bytes, t[1]);
break;
case Symbol.for('DecodeError'):
describe(tName, () => {
it('should fail with DecodeError', () => {
expect(() => D(strip(t[0]) as Bytes))
.toThrowFilter(e => isDecodeError(e) && !isShortPacket(e));
});
});
break;
case Symbol.for('DecodeEOF'): // fall through
case Symbol.for('DecodeShort'):
describe(tName, () => {
it('should fail with ShortPacket', () => {
expect(() => D(strip(t[0]) as Bytes))
.toThrowFilter(e => isShortPacket(e));
});
});
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(preserves`Unsupported test kind ${t}`);
console.error(e);
throw e;
}
}
});
});