Comparing version 0.1.8 to 0.1.9
@@ -9,4 +9,5 @@ // Copyright 2012 The Obvious Corporation. | ||
var type = require("./type"); | ||
var serial = require("./serial"); | ||
var iterate = require("./iterate"); | ||
var type = require("./type"); | ||
var serial = require("./serial"); | ||
@@ -19,2 +20,3 @@ | ||
module.exports = { | ||
iterate: iterate.iterate, | ||
parse: serial.parse, | ||
@@ -21,0 +23,0 @@ parseNoHead: serial.parseNoHead, |
{ | ||
"name": "bidar", | ||
"version": "0.1.8", | ||
"version": "0.1.9", | ||
"keywords": | ||
@@ -5,0 +5,0 @@ ["object", "serialization", "data", "graph"], |
@@ -200,2 +200,61 @@ Bidar: Binary Data Representation. | ||
### iterate(root, visitor) | ||
Starting at the given root object, iterate through all references | ||
reachable from it, calling various `visit*` methods on the given | ||
visitor. | ||
Some of the visit methods define an `innerVisitor` parameter. This | ||
is a function to call (with no arguments) in order to continue the | ||
iteration into the element indicated by the original call. For | ||
example, when `visitObject(x, innerVisitor)` is called, the | ||
`innerVisitor` it is passed will iterate over the prototype and | ||
name bindings of `x`. If `innerVisitor` is not called, then `x`'s | ||
contents will not be iterated over. | ||
Here is a rundown of the various called methods: | ||
* `visitObject(obj, innerVisitor)` -- called on non-array non-function | ||
objects. | ||
* `visitObjectPrototype(obj, proto, innerVisitor)` -- called for | ||
non-array non-function object prototypes as part of the inner visit | ||
to an object. It *won't* be called for the default object prototype | ||
(e.g. the prototype of the object returned by `{}`). If called, it will | ||
be the first call made during an inner visit. | ||
* `visitObjectBinding(obj, name, props, innerVisitor)` -- called for | ||
arbitrary non-index bindings of any object (regular, array, or function). | ||
Always called in sorted order of binding names. `props` is the return | ||
value from a call to `Object.getOwnPropertyDescriptor`. | ||
* `visitObjectGetter(obj, name, getter, innerVisitor)` -- called for | ||
synthetic object properties that have a defined getter. `getter` is | ||
the getter function. This is called from an object binding inner | ||
visitor. | ||
* `visitObjectSetter(obj, name, setter, innerVisitor)` -- called for | ||
synthetic object properties that have a defined setter. `setter` is | ||
the setter function. This is called from an object binding inner | ||
visitor. | ||
* `visitArray(arr, innerVisitor)` -- called on array objects. | ||
* `visitArrayElement(arr, index, value, innerVisitor)` -- called on array | ||
elements, in index order. This is called multiple times from an array | ||
inner visitor, and is always called before `visitObjectBinding() is | ||
called for the named object bindings. | ||
* `visitFunction(func, innerVisitor)` -- called on function objects. | ||
* `visitString(str)` -- called on strings. | ||
* `visitNumber(num)` -- called on numbers. | ||
* `visitBoolean(bool)` -- called on booleans. | ||
* `visitUndefined()` -- called when the `undefined` value is encountered. | ||
* `visitNull()` -- called when the `null` value is encountered. | ||
### type | ||
@@ -240,2 +299,6 @@ | ||
* Consider making holeFilter be able to do any of: (a) provide hole | ||
reconstruction info (as it currently does); (b) provide a non-hole | ||
replacement; (c) indicate the hole should be completely omitted. | ||
Known Deficiencies: | ||
@@ -242,0 +305,0 @@ |
473
test/test.js
@@ -7,476 +7,7 @@ // Copyright 2012 The Obvious Corporation. | ||
/* | ||
* Modules used | ||
*/ | ||
"use strict"; | ||
var assert = require("assert"); | ||
var util = require("util"); | ||
require("./iterate").test(); | ||
require("./serial").test(); | ||
var bidar = require("../lib/bidar"); | ||
/* | ||
* Helper functions | ||
*/ | ||
/** | ||
* Serialize as appropriate. | ||
*/ | ||
function doSerialize(obj, header) { | ||
if (header) { | ||
return bidar.serialize(obj); | ||
} else { | ||
return bidar.serializeNoHead(obj); | ||
} | ||
} | ||
/** | ||
* Parse as appropriate. | ||
*/ | ||
function doParse(buf, header) { | ||
if (header) { | ||
return bidar.parse(buf); | ||
} else { | ||
return bidar.parseNoHead(buf); | ||
} | ||
} | ||
/** | ||
* Do a round trip. | ||
*/ | ||
function roundTrip(obj, header) { | ||
var serialized = doSerialize(obj, header); | ||
return doParse(serialized, header); | ||
} | ||
/** | ||
* Test a round trip that ought to be comparable with identity equals (===). | ||
*/ | ||
function testRoundTripSame(obj, header) { | ||
var parsed = roundTrip(obj, header); | ||
assert.equal(obj, parsed); | ||
} | ||
/** | ||
* Test a round trip whose JSON-stringified form ought to compare as equal. | ||
*/ | ||
function testRoundTripJson(obj, header) { | ||
var parsed = roundTrip(obj, header); | ||
assert.equal(JSON.stringify(obj), JSON.stringify(parsed)); | ||
} | ||
/* | ||
* Tests | ||
*/ | ||
function testSames(header) { | ||
testRoundTripSame(undefined, header); | ||
testRoundTripSame(false, header); | ||
testRoundTripSame(true, header); | ||
testRoundTripSame(null, header); | ||
// "-1e-2000" is just a way of saying "negative zero." | ||
testRoundTripSame(-1e-2000, header); | ||
for (var i = -1000; i < 1000; i++) { | ||
testRoundTripSame(i, header); | ||
} | ||
for (var i = 0, v = 1.5e-10; i < 1000; i++) { | ||
v = v * -123.45; | ||
testRoundTripSame(v, header); | ||
} | ||
testRoundTripSame("", header); | ||
testRoundTripSame("a", header); | ||
testRoundTripSame("bb", header); | ||
testRoundTripSame("ccc", header); | ||
testRoundTripSame("muffin", header); | ||
testRoundTripSame("\u0123\u2345", header); | ||
testRoundTripSame("xy\0z", header); | ||
} | ||
/** | ||
* Tests that a bunch of 64-bit int values actually get represented | ||
* as ints. | ||
*/ | ||
function testInts(header) { | ||
function verify(value) { | ||
var buf = doSerialize(value, header); | ||
var typeChar = buf[header ? 8 : 0]; | ||
assert((typeChar >= 0x30) && (typeChar <= 0x38)); | ||
assert.equal(doParse(buf, header), value); | ||
} | ||
// The starting value here is the most positive 64-bit int representable | ||
// as an IEEE 64-bit float. | ||
for (var i = 0x7ffffffffffffc00; i != 0; i = Math.floor(i/2)) { | ||
verify(i); | ||
verify(i - 0x1234); | ||
verify(i - 0x12345678); | ||
} | ||
// The starting value here is the most negative 64-bit int representable | ||
// as an IEEE 64-bit float. | ||
for (var i = -0x8000000000000000; i != -1; i /= 2) { | ||
verify(i); | ||
verify(i + 0x9876); | ||
verify(i + 0x98765432); | ||
} | ||
} | ||
function testJsons(header) { | ||
testRoundTripJson([], header); | ||
testRoundTripJson([1], header); | ||
testRoundTripJson([1, null], header); | ||
testRoundTripJson([1, null, "foo"], header); | ||
testRoundTripJson([1, null, "foo", "bar"], header); | ||
testRoundTripJson([1, null, "foo", "bar", "foo", "foo", "baz"], header); | ||
testRoundTripJson({}, header); | ||
testRoundTripJson({a: 10}, header); | ||
testRoundTripJson({a: 10, b: false}, header); | ||
testRoundTripJson({a: 10, b: false, cdefg: "scone", hijkl: "scone"}, | ||
header); | ||
} | ||
function testBuffer() { | ||
var buf = new Buffer(100); | ||
for (var i = 0, v = 1; i < buf.length; i++) { | ||
buf[i] = v; | ||
v = (v * 123 + 12) & 0xff; | ||
} | ||
var serialized = bidar.serialize(buf); | ||
var parsed = bidar.parse(serialized); | ||
assert(Buffer.isBuffer(parsed)); | ||
assert.equal(parsed.length, buf.length); | ||
for (var i = 0; i < buf.length; i++) { | ||
assert.equal(parsed[i], buf[i]); | ||
} | ||
} | ||
function testLargeBuffer() { | ||
var buf = new Buffer(10000); | ||
for (var i = 0, v = 10; i < buf.length; i++) { | ||
buf[i] = v; | ||
v = (v * 123 + 12) & 0xff; | ||
} | ||
var serialized = bidar.serialize(buf); | ||
var parsed = bidar.parse(serialized); | ||
checkBuf(parsed); | ||
serialized = bidar.serialize([ buf, { a: buf, b: [ buf ]}]); | ||
parsed = bidar.parse(serialized); | ||
assert.equal(parsed[0], parsed[1].a); | ||
assert.equal(parsed[0], parsed[1].b[0]); | ||
checkBuf(parsed[0]); | ||
function checkBuf(b) { | ||
assert.equal(b.length, buf.length); | ||
for (var i = 0; i < buf.length; i++) { | ||
assert.equal(b[i], buf[i]); | ||
} | ||
} | ||
} | ||
function testStringSharing() { | ||
// Make sure that string sharing works, by doing a round trip test | ||
// and noting that the encoded length is less than what it'd be if | ||
// the duplicated strings were all included. | ||
var bigString = "Hello. This is a nice big string for you. " + | ||
"Do you like it? I think it's rather nice myself."; | ||
var strings = [ bigString, bigString, bigString, bigString, | ||
bigString, bigString, bigString, bigString ]; | ||
testRoundTripJson(strings, true); | ||
var serialized = bidar.serialize(strings); | ||
assert(serialized.length < (bigString.length * 2)); | ||
} | ||
function testArraySharing() { | ||
// Make sure a duplicated array is only represented once, and | ||
// comes out with properly id-equal (===) references. | ||
var array1 = [ 1, 2, 3 ]; | ||
var array2 = [ 1, 2, 3 ]; | ||
var arrays = [ array1, array1, array1, array2, array2, array1 ]; | ||
testRoundTripJson(arrays, true); | ||
var serialized = bidar.serialize(arrays); | ||
var parsed = bidar.parse(serialized); | ||
assert.equal(parsed[0], parsed[1]); | ||
assert.equal(parsed[0], parsed[2]); | ||
assert.equal(parsed[0], parsed[5]); | ||
assert.equal(parsed[3], parsed[4]); | ||
assert.notEqual(parsed[0], parsed[3]); | ||
} | ||
function testObjectSharing() { | ||
// Make sure a duplicated object is only represented once, and | ||
// comes out with properly id-equal (===) references. | ||
var obj1 = { "blort": "fizmo" }; | ||
var obj2 = { "blort": "fizmo" }; | ||
var objects = { a: obj1, b: obj2, c: obj1, d: obj2 }; | ||
testRoundTripJson(objects, true); | ||
var serialized = bidar.serialize(objects); | ||
var parsed = bidar.parse(serialized); | ||
assert.equal(parsed.a, parsed.c); | ||
assert.equal(parsed.b, parsed.d); | ||
assert.notEqual(parsed.a, parsed.b); | ||
} | ||
function testArrayMissingElements() { | ||
var arr = []; | ||
arr[5] = 500; | ||
arr[10] = 1000; | ||
var serialized = bidar.serialize(arr); | ||
var parsed = bidar.parse(serialized); | ||
var keys = Object.keys(parsed); | ||
assert.equal(parsed.length, 11); | ||
assert.equal(parsed[5], 500); | ||
assert.equal(parsed[10], 1000); | ||
assert.equal(keys.length, 2); | ||
assert.equal(keys[0], 5); | ||
assert.equal(keys[1], 10); | ||
} | ||
function testArrayWithNameBindings() { | ||
var arr = [ "a", "b", "c" ]; | ||
arr.foo = "fooey"; | ||
arr.bar = "barry"; | ||
arr.baz = "bazzy"; | ||
var serialized = bidar.serialize(arr); | ||
var parsed = bidar.parse(serialized); | ||
var keys = Object.keys(parsed); | ||
assert.equal(parsed.length, 3); | ||
assert.equal(parsed[0], "a"); | ||
assert.equal(parsed[1], "b"); | ||
assert.equal(parsed[2], "c"); | ||
assert.equal(parsed.foo, "fooey"); | ||
assert.equal(parsed.bar, "barry"); | ||
assert.equal(parsed.baz, "bazzy"); | ||
} | ||
function testCircularArray() { | ||
var arr = []; | ||
arr[0] = arr; | ||
arr[1] = arr; | ||
arr[2] = "hey"; | ||
var serialized = bidar.serialize(arr); | ||
var parsed = bidar.parse(serialized); | ||
assert.equal(parsed.length, 3); | ||
assert.equal(parsed[0], parsed); | ||
assert.equal(parsed[1], parsed); | ||
assert.equal(parsed[2], "hey"); | ||
} | ||
function testCircularObject() { | ||
var obj = {}; | ||
obj.x = obj; | ||
obj.y = obj; | ||
obj.z = 99; | ||
var serialized = bidar.serialize(obj); | ||
var parsed = bidar.parse(serialized); | ||
var keys = Object.keys(parsed); | ||
assert.equal(keys.length, 3); | ||
assert.equal(parsed.x, parsed); | ||
assert.equal(parsed.y, parsed); | ||
assert.equal(parsed.z, 99); | ||
} | ||
function testSimpleHole() { | ||
var serialized = bidar.serialize(theHole, holeFilter); | ||
var parsed = bidar.parse(serialized, holeFiller); | ||
assert.equal(parsed, theNewHole); | ||
function theHole() { | ||
return "yo"; | ||
} | ||
function theNewHole() { | ||
return "yay"; | ||
} | ||
function holeFilter(x) { | ||
assert.equal(x, theHole); | ||
return "blort"; | ||
} | ||
function holeFiller(x) { | ||
assert.equal(x, "blort"); | ||
return theNewHole; | ||
} | ||
} | ||
function testInnerHole() { | ||
var obj = { x: hole1, y: hole2, z: [ hole1, hole2, "stuff" ] }; | ||
var serialized = bidar.serialize(obj, holeFilter); | ||
var parsed = bidar.parse(serialized, holeFiller); | ||
assert.equal(parsed.x, "[one]"); | ||
assert.equal(parsed.y, "[two]"); | ||
assert.equal(parsed.z[0], "[one]"); | ||
assert.equal(parsed.z[1], "[two]"); | ||
assert.equal(parsed.z[2], "stuff"); | ||
function hole1() { | ||
return "one"; | ||
} | ||
function hole2() { | ||
return "two"; | ||
} | ||
function holeFilter(x) { | ||
return x(); | ||
} | ||
function holeFiller(x) { | ||
return "[" + x + "]"; | ||
} | ||
} | ||
function test_fail_hole() { | ||
var obj = { a: hole }; | ||
function hole() { | ||
return "holy!"; | ||
} | ||
function f1() { | ||
bidar.serialize(obj); | ||
} | ||
assert.throws(f1, /Hole-ful graph, but no hole filter/); | ||
function f2() { | ||
bidar.serialize(obj, filter); | ||
function filter(x) { | ||
// An arbitrary-but-different hole. | ||
return filter; | ||
} | ||
} | ||
assert.throws(f2, /Holes not allowed in holes/); | ||
function f3() { | ||
bidar.serialize(obj, filter); | ||
function filter(x) { | ||
// The same hole. | ||
return x; | ||
} | ||
} | ||
assert.throws(f3, /Holes not allowed in holes/); | ||
} | ||
function testLargeString() { | ||
var string1 = "abcdefg1234567890---THIS IS YOUR CAPTAIN SPEAKING---" + | ||
"\0\u2000\u2010\u2011\u2022\ufffe---ARE YOU RECEIVING ME?---"; | ||
var biggie = "[start]"; | ||
for (var i = 0; i < 1000; i++) { | ||
biggie += string1; | ||
} | ||
biggie += "[fin]"; | ||
testRoundTripSame(biggie, true); | ||
testRoundTripJson([ "xyz", biggie, biggie, biggie, "pdq" ], true); | ||
testRoundTripJson({ a: biggie, b: "blort", c: biggie }, true); | ||
} | ||
function test_parsePartial() { | ||
var arr = [ 1, 2, 300 ]; | ||
var serialized = bidar.serialize(arr); | ||
var buf = new Buffer(serialized.length + 10); | ||
buf.fill(0); | ||
serialized.copy(buf, 5); | ||
var result = bidar.parsePartial(buf, 5); | ||
assert.deepEqual(result.root, arr); | ||
assert.equal(result.bytesConsumed, serialized.length); | ||
} | ||
function test_parsePartialNoHead() { | ||
var arr = [ 1, 2, 300 ]; | ||
var serialized = bidar.serializeNoHead(arr); | ||
var buf = new Buffer(serialized.length + 10); | ||
buf.fill(0); | ||
serialized.copy(buf, 5); | ||
var result = bidar.parsePartialNoHead(buf, 5); | ||
assert.deepEqual(result.root, arr); | ||
assert.equal(result.bytesConsumed, serialized.length); | ||
} | ||
function test_fail_parse() { | ||
var arr = [ 1, 2, 300 ]; | ||
var serialized = bidar.serialize(arr); | ||
var buf = new Buffer(serialized.length + 10); | ||
buf.fill(0); | ||
serialized.copy(buf); | ||
assert.throws(tryParse, /Extra data at end of buffer/); | ||
buf = serialized.slice(0, serialized.length - 5); | ||
assert.throws(tryParse, /Insufficient data for read request/); | ||
function tryParse() { | ||
bidar.parse(buf); | ||
} | ||
} | ||
testSames(true); | ||
testInts(true); | ||
testJsons(true); | ||
testSames(false); | ||
testInts(false); | ||
testJsons(false); | ||
testBuffer(); | ||
testLargeBuffer(); | ||
testStringSharing(); | ||
testArraySharing(); | ||
testObjectSharing(); | ||
testArrayMissingElements(); | ||
testArrayWithNameBindings(); | ||
testCircularArray(); | ||
testCircularObject(); | ||
testSimpleHole(); | ||
testInnerHole(); | ||
test_fail_hole(); | ||
testLargeString(); | ||
test_parsePartial(); | ||
test_fail_parse(); | ||
console.log("All tests pass!"); |
81880
13
1923
345