Comparing version 0.0.16 to 0.1.0
{ | ||
"name": "preserves", | ||
"version": "0.0.16", | ||
"version": "0.1.0", | ||
"description": "Experimental data serialization format", | ||
"homepage": "https://gitlab.com/tonyg/preserves", | ||
"license": "MIT", | ||
"homepage": "https://gitlab.com/preserves/preserves", | ||
"license": "Apache-2.0", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": "gitlab:tonyg/preserves", | ||
"repository": "gitlab:preserves/preserves", | ||
"scripts": { | ||
@@ -18,10 +18,8 @@ "test": "mocha", | ||
"devDependencies": { | ||
"chai": "^4.2.0", | ||
"chai-immutable": "^2.0.0-rc.2", | ||
"mocha": "^5.2.0", | ||
"nyc": "^13.1.0" | ||
"nyc": "^14.1.1" | ||
}, | ||
"dependencies": { | ||
"immutable": "^3.8.2" | ||
"immutable": "^4.0.0-rc.12" | ||
} | ||
} |
137
src/codec.js
@@ -10,3 +10,4 @@ "use strict"; | ||
const Values = require('./values.js'); | ||
const { List, Map, Set, Bytes, Record, Single, Double } = Values; | ||
const Annotations = require('./annotations.js'); | ||
const { fromJS, List, Map, Set, Bytes, Record, Single, Double } = Values; | ||
@@ -31,3 +32,4 @@ const { PreserveOn } = require('./symbols.js'); | ||
this.index = 0; | ||
this.shortForms = options.shortForms || {}; | ||
this.placeholders = fromJS(options.placeholders || {}); | ||
this.includeAnnotations = options.includeAnnotations || false; | ||
} | ||
@@ -80,5 +82,4 @@ | ||
peekend(arg) { | ||
const [a,i,r] = this.nextop(); | ||
const result = (a === 0) && (i === 3) && (r === arg); | ||
peekend() { | ||
const result = this.nextbyte() === 4; | ||
if (!result) this.index--; | ||
@@ -88,5 +89,5 @@ return result; | ||
binarystream(arg, minor) { | ||
binarystream(minor) { | ||
const result = []; | ||
while (!this.peekend(arg)) { | ||
while (!this.peekend()) { | ||
const chunk = this.next(); | ||
@@ -106,6 +107,6 @@ if (ArrayBuffer.isView(chunk)) { | ||
valuestream(arg, minor, decoder) { | ||
valuestream(minor) { | ||
const result = []; | ||
while (!this.peekend(arg)) result.push(this.next()); | ||
return decoder(minor, result); | ||
while (!this.peekend()) result.push(this.next()); | ||
return this.decodecompound(minor, result); | ||
} | ||
@@ -131,19 +132,11 @@ | ||
decoderecord(minor, vs) { | ||
if (minor === 3) { | ||
if (vs.length === 0) throw new DecodeError("Too few elements in encoded record"); | ||
return new Record(vs[0], vs.slice(1)); | ||
} else { | ||
const label = this.shortForms[minor]; | ||
if (label === void 0) throw new DecodeError("Use of unconfigured short form " + minor); | ||
return new Record(label, vs); | ||
} | ||
} | ||
decodecollection(minor, vs) { | ||
decodecompound(minor, vs) { | ||
switch (minor) { | ||
case 0: return List(vs); | ||
case 1: return Set(vs); | ||
case 2: return this.mapFromArray(vs); | ||
case 3: throw new DecodeError("Invalid collection type"); | ||
case 0: { | ||
if (vs.length === 0) throw new DecodeError("Too few elements in encoded record"); | ||
return new Record(vs[0], vs.slice(1)); | ||
} | ||
case 1: return List(vs); | ||
case 2: return Set(vs); | ||
case 3: return this.mapFromArray(vs); | ||
} | ||
@@ -160,2 +153,13 @@ } | ||
wrap(v) { | ||
return this.includeAnnotations ? new Annotations.Annotated(v) : v; | ||
} | ||
unshiftAnnotation(a, v) { | ||
if (this.includeAnnotations) { | ||
v.annotations.unshift(a); | ||
} | ||
return v; | ||
} | ||
next() { | ||
@@ -168,9 +172,24 @@ const [major, minor, arg] = this.nextop(); | ||
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: { | ||
const a = this.next(); | ||
const v = this.next(); | ||
return this.unshiftAnnotation(a, v); | ||
} | ||
default: throw new DecodeError("Illegal format A lead byte"); | ||
} | ||
case 1: | ||
return (arg > 12) ? arg - 16 : arg; | ||
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: { | ||
@@ -180,17 +199,16 @@ const t = arg >> 2; | ||
switch (t) { | ||
case 0: throw new DecodeError("Invalid format C start byte"); | ||
case 1: return this.binarystream(arg, n); | ||
case 2: return this.valuestream(arg, n, this.decoderecord.bind(this)); | ||
case 3: return this.valuestream(arg, n, this.decodecollection.bind(this)); | ||
case 1: return this.wrap(this.binarystream(n)); | ||
case 2: return this.wrap(this.valuestream(n)); | ||
default: throw new DecodeError("Invalid format C start byte"); | ||
} | ||
} | ||
case 3: | ||
throw new DecodeError("Invalid format C end byte"); | ||
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.decoderecord(minor, this.nextvalues(this.wirelength(arg))); | ||
return this.wrap(this.decodecompound(minor, this.nextvalues(this.wirelength(arg)))); | ||
case 3: | ||
return this.decodecollection(minor, this.nextvalues(this.wirelength(arg))); | ||
throw new DecodeError("Invalid lead byte (major 3)"); | ||
} | ||
@@ -213,2 +231,12 @@ } | ||
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 { | ||
@@ -220,3 +248,3 @@ constructor(options) { | ||
this.index = 0; | ||
this.shortForms = options.shortForms || {}; | ||
this.placeholders = fromJS(options.placeholders || {}); | ||
} | ||
@@ -289,3 +317,3 @@ | ||
encodecollection(minor, items) { | ||
this.header(3, minor, items.size); | ||
this.header(2, minor, items.size); | ||
for (const item of items) { this.push(item); } | ||
@@ -298,7 +326,11 @@ } | ||
for (const item of items) { this.push(item); } | ||
this.header(0, 3, tn); | ||
this.header(0, 0, 4); | ||
} | ||
push(v) { | ||
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); | ||
@@ -311,3 +343,3 @@ } | ||
if (v >= -3 && v <= 12) { | ||
this.leadbyte(0, 1, v >= 0 ? v : v + 16); | ||
this.leadbyte(0, 3, v >= 0 ? v : v + 16); | ||
} else { | ||
@@ -340,9 +372,9 @@ this.encodeint(v); | ||
else if (List.isList(v)) { | ||
this.encodecollection(0, v); | ||
this.encodecollection(1, v); | ||
} | ||
else if (Set.isSet(v)) { | ||
this.encodecollection(1, v); | ||
this.encodecollection(2, v); | ||
} | ||
else if (Map.isMap(v)) { | ||
this.encodecollection(2, List().withMutations((l) => { | ||
this.encodecollection(3, List().withMutations((l) => { | ||
v.forEach((val, key) => { l.push(key).push(val); }); | ||
@@ -352,3 +384,3 @@ })); | ||
else if (typeof v === 'object' && v !== null && typeof v[Symbol.iterator] === 'function') { | ||
this.encodestream(3, 0, v); | ||
this.encodestream(2, 1, v); | ||
} | ||
@@ -362,2 +394,6 @@ else { | ||
function encode(v, options) { | ||
return new Encoder(options).push(v).contents(); | ||
} | ||
Object.assign(module.exports, { | ||
@@ -368,3 +404,6 @@ DecodeError, | ||
Decoder, | ||
decode, | ||
decodeWithAnnotations, | ||
Encoder, | ||
encode, | ||
}); |
@@ -11,1 +11,2 @@ "use strict"; | ||
Object.assign(module.exports, require('./values.js')); | ||
Object.assign(module.exports, require('./annotations.js')); |
@@ -13,3 +13,3 @@ "use strict"; | ||
const Immutable = require('immutable'); | ||
const { List, isList, Map, Set, is } = Immutable; | ||
const { List, Map, Set, is, hash } = Immutable; | ||
@@ -345,12 +345,4 @@ const { PreserveOn, AsPreserve } = require('./symbols.js'); | ||
Record.prototype[PreserveOn] = function (encoder) { | ||
if (is(encoder.shortForms[0], this.label)) { | ||
encoder.header(2, 0, this.fields.size); | ||
} else if (is(encoder.shortForms[1], this.label)) { | ||
encoder.header(2, 1, this.fields.size); | ||
} else if (is(encoder.shortForms[2], this.label)) { | ||
encoder.header(2, 2, this.fields.size); | ||
} else { | ||
encoder.header(2, 3, this.fields.size + 1); | ||
encoder.push(this.label); | ||
} | ||
encoder.header(2, 0, this.fields.size + 1); | ||
encoder.push(this.label); | ||
for (const field of this.fields) { encoder.push(field); } | ||
@@ -394,4 +386,4 @@ }; | ||
if (!ctor.isClassOf(r)) { | ||
throw new Error("Record: attempt to retrieve field "+label+"."+name+ | ||
" from non-"+label+": "+(r && r.toString())); | ||
throw new Error("Record: attempt to retrieve field "+label.toString()+"."+name+ | ||
" from non-"+label.toString()+": "+(r && r.toString())); | ||
} | ||
@@ -427,3 +419,3 @@ return r.get(i); | ||
fromJS, | ||
List, Map, Set, is, | ||
List, Map, Set, is, hash, | ||
Float, Single, Double, | ||
@@ -430,0 +422,0 @@ Bytes, |
"use strict"; | ||
const chai = require('chai'); | ||
const expect = chai.expect; | ||
chai.use(require('chai-immutable')); | ||
const assert = require('assert'); | ||
const Immutable = require('immutable'); | ||
@@ -15,16 +12,17 @@ | ||
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); | ||
}); | ||
@@ -34,55 +32,55 @@ it('should implement forEach', () => { | ||
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); | ||
}); | ||
}); | ||
}); |
"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; | ||
@@ -15,8 +20,2 @@ const fs = require('fs'); | ||
const shortForms = { | ||
0: Symbol.for('discard'), | ||
1: Symbol.for('capture'), | ||
2: Symbol.for('observe'), | ||
}; | ||
const Discard = Record.makeConstructor('discard', []); | ||
@@ -28,8 +27,8 @@ const Capture = Record.makeConstructor('capture', ['pattern']); | ||
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); | ||
}); | ||
@@ -42,10 +41,10 @@ }) | ||
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)); | ||
}); | ||
@@ -56,107 +55,182 @@ }); | ||
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); | ||
} | ||
} | ||
// 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')), }, | ||
]; | ||
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); }} | ||
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; | ||
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 !== '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)); | ||
} | ||
}); | ||
}); | ||
} | ||
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 = 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('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; | ||
} | ||
} | ||
}); | ||
@@ -169,4 +243,4 @@ }); | ||
const bs = Bytes.from(u.subarray(4)); | ||
expect(new Decoder(bs).next()).to.equal("333"); | ||
assert.strictEqual(new Decoder(bs).next(), "333"); | ||
}); | ||
}); |
@@ -7,6 +7,3 @@ "use strict"; | ||
const chai = require('chai'); | ||
const expect = chai.expect; | ||
chai.use(require('chai-immutable')); | ||
const assert = require('assert'); | ||
const Immutable = require('immutable'); | ||
@@ -23,21 +20,21 @@ | ||
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))); | ||
}); | ||
@@ -51,4 +48,4 @@ }); | ||
const I2 = require('../src/index.js'); | ||
expect(Object.is(I1, I2)).to.be.true; | ||
assert(Object.is(I1, I2)); | ||
}); | ||
}); |
"use strict"; | ||
const chai = require('chai'); | ||
const expect = chai.expect; | ||
chai.use(require('chai-immutable')); | ||
const assert = require('assert'); | ||
const Immutable = require('immutable'); | ||
@@ -13,3 +10,3 @@ | ||
it('should print reasonably', () => { | ||
expect(Single(123.45).toString()).to.equal("123.45"); | ||
assert.strictEqual(Single(123.45).toString(), "123.45"); | ||
}); | ||
@@ -20,4 +17,4 @@ }); | ||
it('should print reasonably', () => { | ||
expect(Double(123.45).toString()).to.equal("123.45"); | ||
assert.strictEqual(Double(123.45).toString(), "123.45"); | ||
}); | ||
}); |
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
42445
2
1192
+ Addedimmutable@4.3.7(transitive)
- Removedimmutable@3.8.2(transitive)
Updatedimmutable@^4.0.0-rc.12