You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by bl...@apache.org on 2015/12/03 22:35:44 UTC

svn commit: r1717850 [4/4] - in /avro/trunk: ./ lang/js/ lang/js/doc/ lang/js/etc/ lang/js/etc/browser/ lang/js/etc/deprecated/ lang/js/lib/ lang/js/test/ lang/js/test/dat/

Added: avro/trunk/lang/js/test/test_schemas.js
URL: http://svn.apache.org/viewvc/avro/trunk/lang/js/test/test_schemas.js?rev=1717850&view=auto
==============================================================================
--- avro/trunk/lang/js/test/test_schemas.js (added)
+++ avro/trunk/lang/js/test/test_schemas.js Thu Dec  3 21:35:44 2015
@@ -0,0 +1,2459 @@
+/* jshint node: true, mocha: true */
+
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+'use strict';
+
+var utils = require('../lib/utils'),
+    schemas = require('../lib/schemas'),
+    assert = require('assert'),
+    util = require('util');
+
+var Tap = utils.Tap;
+var createType = schemas.createType;
+var types = schemas.types;
+
+
+suite('types', function () {
+
+  suite('BooleanType', function () {
+
+    var data = [
+      {
+        valid: [true, false],
+        invalid: [null, 'hi', undefined, 1.5, 1e28, 123124123123213]
+      }
+    ];
+
+    testType(types.BooleanType, data);
+
+    test('to JSON', function () {
+      var t = new types.BooleanType();
+      assert.equal(t.toJSON(), 'boolean');
+    });
+
+    test('compare buffers', function () {
+      var t = new types.BooleanType();
+      var bt = t.toBuffer(true);
+      var bf = t.toBuffer(false);
+      assert.equal(t.compareBuffers(bt, bf), 1);
+      assert.equal(t.compareBuffers(bf, bt), -1);
+      assert.equal(t.compareBuffers(bt, bt), 0);
+    });
+
+  });
+
+  suite('IntType', function () {
+
+    var data = [
+      {
+        valid: [1, -3, 12314, 0, 1e9],
+        invalid: [null, 'hi', undefined, 1.5, 1e28, 123124123123213]
+      }
+    ];
+
+    testType(types.IntType, data);
+
+    test('toBuffer int', function () {
+
+      var type = createType('int');
+      assert.equal(type.fromBuffer(new Buffer([0x80, 0x01])), 64);
+      assert(new Buffer([0]).equals(type.toBuffer(0)));
+
+    });
+
+    test('resolve int > long', function () {
+      var intType = createType('int');
+      var longType = createType('long');
+      var buf = intType.toBuffer(123);
+      assert.equal(
+        longType.fromBuffer(buf, longType.createResolver(intType)),
+        123
+      );
+    });
+
+    test('resolve int > [null, int]', function () {
+      var wt = createType('int');
+      var rt = createType(['null', 'int']);
+      var buf = wt.toBuffer(123);
+      assert.deepEqual(
+        rt.fromBuffer(buf, rt.createResolver(wt)),
+        {'int': 123}
+      );
+    });
+
+    test('resolve int > float', function () {
+      var wt = createType('int');
+      var rt = createType('float');
+      var buf = wt.toBuffer(123);
+      assert.deepEqual(rt.fromBuffer(buf, rt.createResolver(wt)), 123);
+    });
+
+    test('resolve int > double', function () {
+      var wt = createType('int');
+      var rt = createType('double');
+      var n = Math.pow(2, 30) + 1;
+      var buf = wt.toBuffer(n);
+      assert.deepEqual(rt.fromBuffer(buf, rt.createResolver(wt)), n);
+    });
+
+    test('toString', function () {
+      assert.equal(createType('int').toString(), '"int"');
+    });
+
+    test('clone', function () {
+      var t = createType('int');
+      assert.equal(t.clone(123), 123);
+      assert.throws(function () { t.clone(''); });
+    });
+
+    test('resolve invalid', function () {
+      assert.throws(function () { getResolver('int', 'long'); });
+    });
+
+  });
+
+  suite('LongType', function () {
+
+    var data = [
+      {
+        valid: [1, -3, 12314, 9007199254740990, 900719925474090],
+        invalid: [null, 'hi', undefined, 9007199254740991, 1.3, 1e67]
+      }
+    ];
+
+    testType(types.LongType, data);
+
+    test('resolve invalid', function () {
+      assert.throws(function () { getResolver('long', 'double'); });
+    });
+
+    test('resolve long > float', function () {
+      var t1 = createType('long');
+      var t2 = createType('float');
+      var n = 9007199254740990; // Number.MAX_SAFE_INTEGER - 1
+      var buf = t1.toBuffer(n);
+      var f = t2.fromBuffer(buf, t2.createResolver(t1));
+      assert(Math.abs(f - n) / n < 1e-7);
+      assert(t2.isValid(f));
+    });
+
+    test('precision loss', function () {
+      var type = createType('long');
+      var buf = new Buffer([0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20]);
+      assert.throws(function () { type.fromBuffer(buf); });
+    });
+
+    test('using missing methods', function () {
+      assert.throws(function () { types.LongType.using(); });
+    });
+
+  });
+
+  suite('StringType', function () {
+
+    var data = [
+      {
+        valid: ['', 'hi'],
+        invalid: [null, undefined, 1, 0]
+      }
+    ];
+
+    testType(types.StringType, data);
+
+    test('fromBuffer string', function () {
+      var type = createType('string');
+      var buf = new Buffer([0x06, 0x68, 0x69, 0x21]);
+      var s = 'hi!';
+      assert.equal(type.fromBuffer(buf), s);
+      assert(buf.equals(type.toBuffer(s)));
+    });
+
+    test('toBuffer string', function () {
+      var type = createType('string');
+      var buf = new Buffer([0x06, 0x68, 0x69, 0x21]);
+      assert(buf.equals(type.toBuffer('hi!', 1)));
+    });
+
+    test('resolve string > bytes', function () {
+      var stringT = createType('string');
+      var bytesT = createType('bytes');
+      var buf = stringT.toBuffer('\x00\x01');
+      assert.deepEqual(
+        bytesT.fromBuffer(buf, bytesT.createResolver(stringT)),
+        new Buffer([0, 1])
+      );
+    });
+
+    test('encode resize', function () {
+      var t = createType('string');
+      var s = 'hello';
+      var b, pos;
+      b = new Buffer(2);
+      pos = t.encode(s, b);
+      assert(pos < 0);
+      b = new Buffer(2 - pos);
+      pos = t.encode(s, b);
+      assert(pos >= 0);
+      assert.equal(s, t.fromBuffer(b)); // Also checks exact length match.
+    });
+
+  });
+
+  suite('NullType', function () {
+
+    var data = [
+      {
+        schema: 'null',
+        valid: [null],
+        invalid: [0, 1, 'hi', undefined]
+      }
+    ];
+
+    testType(types.NullType, data);
+
+  });
+
+  suite('FloatType', function () {
+
+    var data = [
+      {
+        valid: [1, -3, 123e7],
+        invalid: [null, 'hi', undefined],
+        check: function (a, b) { assert(floatEquals(a, b)); }
+      }
+    ];
+
+    testType(types.FloatType, data);
+
+    test('compare buffer', function () {
+      var t = createType('float');
+      var b1 = t.toBuffer(0.5);
+      assert.equal(t.compareBuffers(b1, b1), 0);
+      var b2 = t.toBuffer(-0.75);
+      assert.equal(t.compareBuffers(b1, b2), 1);
+      var b3 = t.toBuffer(175);
+      assert.equal(t.compareBuffers(b1, b3), -1);
+    });
+
+    test('resolver double > float', function () {
+      assert.throws(function () { getResolver('float', 'double'); });
+    });
+
+    test('fromString', function () {
+      var t = createType('float');
+      var f = t.fromString('3.1');
+      assert(t.isValid(f));
+    });
+
+    test('clone from double', function () {
+      var t = createType('float');
+      var d = 3.1;
+      var f;
+      f = t.clone(d);
+      assert(t.isValid(f));
+    });
+
+  });
+
+  suite('DoubleType', function () {
+
+    var data = [
+      {
+        valid: [1, -3.4, 12314e31, 5e37],
+        invalid: [null, 'hi', undefined],
+        check: function (a, b) { assert(floatEquals(a, b), '' + [a, b]); }
+      }
+    ];
+
+    testType(types.DoubleType, data);
+
+    test('resolver string > double', function () {
+      assert.throws(function () { getResolver('double', 'string'); });
+    });
+
+    test('compare buffer', function () {
+      var t = createType('double');
+      var b1 = t.toBuffer(0.5);
+      assert.equal(t.compareBuffers(b1, b1), 0);
+      var b2 = t.toBuffer(-0.75);
+      assert.equal(t.compareBuffers(b1, b2), 1);
+      var b3 = t.toBuffer(175);
+      assert.equal(t.compareBuffers(b1, b3), -1);
+    });
+
+  });
+
+  suite('BytesType', function () {
+
+    var data = [
+      {
+        valid: [new Buffer(1), new Buffer('abc')],
+        invalid: [null, 'hi', undefined, 1, 0, -3.5]
+      }
+    ];
+
+    testType(types.BytesType, data);
+
+    test('clone', function () {
+      var t = createType('bytes');
+      var s = '\x01\x02';
+      var buf = new Buffer(s);
+      var clone;
+      clone = t.clone(buf);
+      assert.deepEqual(clone, buf);
+      clone[0] = 0;
+      assert.equal(buf[0], 1);
+      assert.throws(function () { t.clone(s); });
+      clone = t.clone(buf.toJSON(), {coerceBuffers: true});
+      assert.deepEqual(clone, buf);
+      assert.throws(function () { t.clone(1, {coerceBuffers: true}); });
+    });
+
+    test('fromString', function () {
+      var t = createType('bytes');
+      var s = '\x01\x02';
+      var buf = new Buffer(s);
+      var clone = t.fromString(JSON.stringify(s));
+      assert.deepEqual(clone, buf);
+    });
+
+    test('compare', function () {
+      var t = createType('bytes');
+      var b1 = t.toBuffer(new Buffer([0, 2]));
+      assert.equal(t.compareBuffers(b1, b1), 0);
+      var b2 = t.toBuffer(new Buffer([0, 2, 3]));
+      assert.equal(t.compareBuffers(b1, b2), -1);
+      var b3 = t.toBuffer(new Buffer([1]));
+      assert.equal(t.compareBuffers(b3, b1), 1);
+    });
+
+  });
+
+  suite('UnionType', function () {
+
+    var data = [
+      {
+        name: 'null & string',
+        schema: ['null', 'string'],
+        valid: [null, {string: 'hi'}],
+        invalid: ['null', undefined, {string: 1}],
+        check: assert.deepEqual
+      },
+      {
+        name: 'qualified name',
+        schema: ['null', {type: 'fixed', name: 'a.B', size: 2}],
+        valid: [null, {'a.B': new Buffer(2)}],
+        invalid: [new Buffer(2)],
+        check: assert.deepEqual
+      },
+      {
+        name: 'array int',
+        schema: ['int', {type: 'array', items: 'int'}],
+        valid: [{'int': 1}, {array: [1,3]}],
+        invalid: [null, 2, {array: ['a']}, [4], 2],
+        check: assert.deepEqual
+      },
+      {
+        name: 'null',
+        schema: ['null'],
+        valid: [null],
+        invalid: [{array: ['a']}, [4], 'null'],
+        check: assert.deepEqual
+      }
+    ];
+
+    var schemas = [
+      {},
+      [],
+      ['null', 'null'],
+      ['null', {type: 'map', values: 'int'}, {type: 'map', values: 'long'}],
+      ['null', ['int', 'string']]
+    ];
+
+    testType(types.UnionType, data, schemas);
+
+    test('getTypes', function () {
+      var t = createType(['null', 'int']);
+      assert.deepEqual(t.getTypes(), [createType('null'), createType('int')]);
+    });
+
+    test('instanceof Union', function () {
+      var type = new types.UnionType(['null', 'int']);
+      assert(type instanceof types.UnionType);
+    });
+
+    test('missing name write', function () {
+      var type = new types.UnionType(['null', 'int']);
+      assert.throws(function () {
+        type.toBuffer({b: 'a'});
+      });
+    });
+
+    test('read invalid index', function () {
+      var type = new types.UnionType(['null', 'int']);
+      var buf = new Buffer([1, 0]);
+      assert.throws(function () { type.fromBuffer(buf); });
+    });
+
+    test('non wrapped write', function () {
+      var type = new types.UnionType(['null', 'int']);
+      assert.throws(function () {
+        type.toBuffer(1, true);
+      }, Error);
+    });
+
+    test('to JSON', function () {
+      var type = new types.UnionType(['null', 'int']);
+      assert.equal(JSON.stringify(type), '["null","int"]');
+    });
+
+    test('resolve int to [long, int]', function () {
+      var t1 = createType('int');
+      var t2 = createType(['long', 'int']);
+      var a = t2.createResolver(t1);
+      var buf = t1.toBuffer(23);
+      assert.deepEqual(t2.fromBuffer(buf, a), {'long': 23});
+    });
+
+    test('resolve null to [null, int]', function () {
+      var t1 = createType('null');
+      var t2 = createType(['null', 'int']);
+      var a = t2.createResolver(t1);
+      assert.deepEqual(t2.fromBuffer(new Buffer(0), a), null);
+    });
+
+    test('resolve [string, int] to [long, string]', function () {
+      var t1 = createType(['string', 'int']);
+      var t2 = createType(['int', 'bytes']);
+      var a = t2.createResolver(t1);
+      var buf;
+      buf = t1.toBuffer({string: 'hi'});
+      assert.deepEqual(t2.fromBuffer(buf, a), {'bytes': new Buffer('hi')});
+      buf = t1.toBuffer({'int': 1});
+      assert.deepEqual(t2.fromBuffer(buf, a), {'int': 1});
+    });
+
+    test('clone', function () {
+      var t = new types.UnionType(['null', 'int']);
+      var o = {'int': 1};
+      assert.strictEqual(t.clone(null), null);
+      var c = t.clone(o);
+      assert.deepEqual(c, o);
+      c.int = 2;
+      assert.equal(o.int, 1);
+      assert.throws(function () { t.clone([]); });
+      assert.throws(function () { t.clone(undefined); });
+    });
+
+    test('clone and wrap', function () {
+      var t = createType(['string', 'int']);
+      var o;
+      o = t.clone('hi', {wrapUnions: true});
+      assert.deepEqual(o, {'string': 'hi'});
+      o = t.clone(3, {wrapUnions: true});
+      assert.deepEqual(o, {'int': 3});
+      assert.throws(function () { t.clone(null, {wrapUnions: 2}); });
+    });
+
+    test('invalid multiple keys', function () {
+      var t = createType(['null', 'int']);
+      var o = {'int': 2};
+      assert(t.isValid(o));
+      o.foo = 3;
+      assert(!t.isValid(o));
+    });
+
+    test('clone multiple keys', function () {
+      var t = createType(['null', 'int']);
+      var o = {'int': 2, foo: 3};
+      assert.throws(function () { t.clone(o); });
+    });
+
+    test('clone unqualified names', function () {
+      var t = createType({
+        name: 'Person',
+        type: 'record',
+        fields: [
+          {name: 'id1', type: {name: 'an.Id', type: 'fixed', size: 1}},
+          {name: 'id2', type: ['null', 'an.Id']}
+        ]
+      });
+      var b = new Buffer([0]);
+      var o = {id1: b, id2: {Id: b}};
+      assert.deepEqual(t.clone(o), {id1: b, id2: {'an.Id': b}});
+    });
+
+    test('clone unqualified names', function () {
+      var t = createType({
+        name: 'Person',
+        type: 'record',
+        fields: [
+          {name: 'id1', type: {name: 'Id', type: 'fixed', size: 1}},
+          {name: 'id2', type: ['null', 'Id']}
+        ]
+      });
+      var b = new Buffer([0]);
+      var o = {id1: b, id2: {'an.Id': b}};
+      assert.throws(function () { t.clone(o); });
+    });
+
+    test('compare buffers', function () {
+      var t = createType(['null', 'double']);
+      var b1 = t.toBuffer(null);
+      assert.equal(t.compareBuffers(b1, b1), 0);
+      var b2 = t.toBuffer({'double': 4});
+      assert.equal(t.compareBuffers(b2, b1), 1);
+      assert.equal(t.compareBuffers(b1, b2), -1);
+      var b3 = t.toBuffer({'double': 6});
+      assert.equal(t.compareBuffers(b3, b2), 1);
+    });
+
+    test('compare', function () {
+      var t;
+      t = createType(['null', 'int']);
+      assert.equal(t.compare(null, {'int': 3}), -1);
+      assert.equal(t.compare(null, null), 0);
+      t = createType(['int', 'float']);
+      assert.equal(t.compare({'int': 2}, {'float': 0.5}), -1);
+      assert.equal(t.compare({'int': 20}, {'int': 5}), 1);
+    });
+
+  });
+
+  suite('EnumType', function () {
+
+    var data = [
+      {
+        name: 'single symbol',
+        schema: {name: 'Foo', symbols: ['HI']},
+        valid: ['HI'],
+        invalid: ['HEY', null, undefined, 0]
+      },
+      {
+        name: 'number-ish as symbol',
+        schema: {name: 'Foo', symbols: ['HI', 'A0']},
+        valid: ['HI', 'A0'],
+        invalid: ['HEY', null, undefined, 0, 'a0']
+      }
+    ];
+
+    var schemas = [
+      {name: 'Foo', symbols: []},
+      {name: 'Foo'},
+      {symbols: ['hi']},
+      {name: 'G', symbols: ['0']}
+    ];
+
+    testType(types.EnumType, data, schemas);
+
+    test('get full name', function () {
+      var t = createType({
+        type: 'enum',
+        symbols: ['A', 'B'],
+        name: 'Letter',
+        namespace: 'latin'
+      });
+      assert.equal(t.getName(), 'latin.Letter');
+    });
+
+    test('get aliases', function () {
+      var t = createType({
+        type: 'enum',
+        symbols: ['A', 'B'],
+        name: 'Letter',
+        namespace: 'latin',
+        aliases: ['Character', 'alphabet.Letter']
+      });
+      var aliases = t.getAliases();
+      assert.deepEqual(aliases, ['latin.Character', 'alphabet.Letter']);
+      aliases.push('Char');
+      assert.equal(t.getAliases().length, 3);
+    });
+
+    test('get symbols', function () {
+      var t = createType({type: 'enum', symbols: ['A', 'B'], name: 'Letter'});
+      var symbols = t.getSymbols();
+      assert.deepEqual(symbols, ['A', 'B']);
+      symbols.push('Char');
+      assert.equal(t.getSymbols().length, 2);
+    });
+
+    test('duplicate symbol', function () {
+      assert.throws(function () {
+        createType({type: 'enum', symbols: ['A', 'B', 'A'], name: 'B'});
+      });
+    });
+
+    test('write invalid', function () {
+      var type = createType({type: 'enum', symbols: ['A'], name: 'a'});
+      assert.throws(function () {
+        type.toBuffer('B');
+      });
+    });
+
+    test('read invalid index', function () {
+      var type = new types.EnumType({type: 'enum', symbols: ['A'], name: 'a'});
+      var buf = new Buffer([2]);
+      assert.throws(function () { type.fromBuffer(buf); });
+    });
+
+    test('resolve', function () {
+      var t1, t2, buf, resolver;
+      t1 = newEnum('Foo', ['bar', 'baz']);
+      t2 = newEnum('Foo', ['bar', 'baz']);
+      resolver = t1.createResolver(t2);
+      buf = t2.toBuffer('bar');
+      assert.equal(t1.fromBuffer(buf, resolver), 'bar');
+      t2 = newEnum('Foo', ['baz', 'bar']);
+      buf = t2.toBuffer('bar');
+      resolver = t1.createResolver(t2);
+      assert.notEqual(t1.fromBuffer(buf), 'bar');
+      assert.equal(t1.fromBuffer(buf, resolver), 'bar');
+      t1 = newEnum('Foo2', ['foo', 'baz', 'bar'], ['Foo']);
+      resolver = t1.createResolver(t2);
+      assert.equal(t1.fromBuffer(buf, resolver), 'bar');
+      t2 = newEnum('Foo', ['bar', 'bax']);
+      assert.throws(function () { t1.createResolver(t2); });
+      assert.throws(function () {
+        t1.createResolver(createType('int'));
+      });
+      function newEnum(name, symbols, aliases, namespace) {
+        var obj = {type: 'enum', name: name, symbols: symbols};
+        if (aliases !== undefined) {
+          obj.aliases = aliases;
+        }
+        if (namespace !== undefined) {
+          obj.namespace = namespace;
+        }
+        return new types.EnumType(obj);
+      }
+    });
+
+    test('clone', function () {
+      var t = createType({type: 'enum', name: 'Foo', symbols: ['bar', 'baz']});
+      assert.equal(t.clone('bar'), 'bar');
+      assert.throws(function () { t.clone('BAR'); });
+      assert.throws(function () { t.clone(null); });
+    });
+
+    test('compare buffers', function () {
+      var t = createType({type: 'enum', name: 'Foo', symbols: ['bar', 'baz']});
+      var b1 = t.toBuffer('bar');
+      var b2 = t.toBuffer('baz');
+      assert.equal(t.compareBuffers(b1, b1), 0);
+      assert.equal(t.compareBuffers(b2, b1), 1);
+    });
+
+    test('compare', function () {
+      var t = createType({type: 'enum', name: 'Foo', symbols: ['b', 'a']});
+      assert.equal(t.compare('b', 'a'), -1);
+      assert.equal(t.compare('a', 'a'), 0);
+    });
+
+  });
+
+  suite('FixedType', function () {
+
+    var data = [
+      {
+        name: 'size 1',
+        schema: {name: 'Foo', size: 2},
+        valid: [new Buffer([1, 2]), new Buffer([2, 3])],
+        invalid: ['HEY', null, undefined, 0, new Buffer(1), new Buffer(3)],
+        check: function (a, b) { assert(a.equals(b)); }
+      }
+    ];
+
+    var schemas = [
+      {name: 'Foo', size: 0},
+      {name: 'Foo', size: -2},
+      {size: 2},
+      {name: 'Foo'},
+      {}
+    ];
+
+    testType(types.FixedType, data, schemas);
+
+    test('get full name', function () {
+      var t = createType({
+        type: 'fixed',
+        size: 2,
+        name: 'Id',
+        namespace: 'id'
+      });
+      assert.equal(t.getName(), 'id.Id');
+    });
+
+    test('get aliases', function () {
+      var t = createType({
+        type: 'fixed',
+        size: 3,
+        name: 'Id'
+      });
+      var aliases = t.getAliases();
+      assert.deepEqual(aliases, []);
+      aliases.push('ID');
+      assert.equal(t.getAliases().length, 1);
+    });
+
+    test('get size', function () {
+      var t = createType({type: 'fixed', size: 5, name: 'Id'});
+      assert.equal(t.getSize(), 5);
+    });
+
+    test('resolve', function () {
+      var t1 = new types.FixedType({name: 'Id', size: 4});
+      var t2 = new types.FixedType({name: 'Id', size: 4});
+      assert.doesNotThrow(function () { t2.createResolver(t1); });
+      t2 = new types.FixedType({name: 'Id2', size: 4});
+      assert.throws(function () { t2.createResolver(t1); });
+      t2 = new types.FixedType({name: 'Id2', size: 4, aliases: ['Id']});
+      assert.doesNotThrow(function () { t2.createResolver(t1); });
+      t2 = new types.FixedType({name: 'Id2', size: 5, aliases: ['Id']});
+      assert.throws(function () { t2.createResolver(t1); });
+    });
+
+    test('clone', function () {
+      var t = new types.FixedType({name: 'Id', size: 2});
+      var s = '\x01\x02';
+      var buf = new Buffer(s);
+      var clone;
+      clone = t.clone(buf);
+      assert.deepEqual(clone, buf);
+      clone[0] = 0;
+      assert.equal(buf[0], 1);
+      assert.throws(function () { t.clone(s); });
+      clone = t.clone(buf.toJSON(), {coerceBuffers: true});
+      assert.deepEqual(clone, buf);
+      assert.throws(function () { t.clone(1, {coerceBuffers: true}); });
+      assert.throws(function () { t.clone(new Buffer([2])); });
+    });
+
+    test('getSchema with extra fields', function () {
+      var t = createType({type: 'fixed', name: 'Id', size: 2, three: 3});
+      t.one = 1;
+      assert.equal(t.getSchema(), '{"name":"Id","type":"fixed","size":2}');
+      assert.equal(t.getSchema(true), '"Id"');
+    });
+
+    test('fromString', function () {
+      var t = new types.FixedType({name: 'Id', size: 2});
+      var s = '\x01\x02';
+      var buf = new Buffer(s);
+      var clone = t.fromString(JSON.stringify(s));
+      assert.deepEqual(clone, buf);
+    });
+
+    test('compare buffers', function () {
+      var t = createType({type: 'fixed', name: 'Id', size: 2});
+      var b1 = new Buffer([1, 2]);
+      assert.equal(t.compareBuffers(b1, b1), 0);
+      var b2 = new Buffer([2, 2]);
+      assert.equal(t.compareBuffers(b1, b2), -1);
+    });
+
+  });
+
+  suite('MapType', function () {
+
+    var data = [
+      {
+        name: 'int',
+        schema: {values: 'int'},
+        valid: [{one: 1}, {two: 2, o: 0}],
+        invalid: [1, {o: null}, [], undefined, {o: 'hi'}, {1: '', 2: 3}, ''],
+        check: assert.deepEqual
+      },
+      {
+        name: 'enum',
+        schema: {values: {type: 'enum', name: 'a', symbols: ['A', 'B']}},
+        valid: [{a: 'A'}, {a: 'A', b: 'B'}, {}],
+        invalid: [{o: 'a'}, {1: 'A', 2: 'b'}, {a: 3}],
+        check: assert.deepEqual
+      },
+      {
+        name: 'array of string',
+        schema: {values: {type: 'array', items: 'string'}},
+        valid: [{a: []}, {a: ['A'], b: ['B', '']}, {}],
+        invalid: [{o: 'a', b: []}, {a: [1, 2]}, {a: {b: ''}}],
+        check: assert.deepEqual
+      }
+    ];
+
+    var schemas = [
+      {},
+      {values: ''},
+      {values: {type: 'array'}}
+    ];
+
+    testType(types.MapType, data, schemas);
+
+    test('get values type', function () {
+      var t = new types.MapType({type: 'map', values: 'int'});
+      assert.deepEqual(t.getValuesType(), createType('int'));
+    });
+
+    test('write int', function () {
+      var t = new types.MapType({type: 'map', values: 'int'});
+      var buf = t.toBuffer({'\x01': 3, '\x02': 4});
+      assert.deepEqual(buf, new Buffer([4, 2, 1, 6, 2, 2, 8, 0]));
+    });
+
+    test('read long', function () {
+      var t = new types.MapType({type: 'map', values: 'long'});
+      var buf = new Buffer([4, 2, 1, 6, 2, 2, 8, 0]);
+      assert.deepEqual(t.fromBuffer(buf), {'\x01': 3, '\x02': 4});
+    });
+
+    test('read with sizes', function () {
+      var t = new types.MapType({type: 'map', values: 'int'});
+      var buf = new Buffer([1,6,2,97,2,0]);
+      assert.deepEqual(t.fromBuffer(buf), {a: 1});
+    });
+
+    test('skip', function () {
+      var v1 = createType({
+        name: 'Foo',
+        type: 'record',
+        fields: [
+          {name: 'map', type: {type: 'map', values: 'int'}},
+          {name: 'val', type: 'int'}
+        ]
+      });
+      var v2 = createType({
+        name: 'Foo',
+        type: 'record',
+        fields: [{name: 'val', type: 'int'}]
+      });
+      var b1 = new Buffer([2,2,97,2,0,6]); // Without sizes.
+      var b2 = new Buffer([1,6,2,97,2,0,6]); // With sizes.
+      var resolver = v2.createResolver(v1);
+      assert.deepEqual(v2.fromBuffer(b1, resolver), {val: 3});
+      assert.deepEqual(v2.fromBuffer(b2, resolver), {val: 3});
+    });
+
+    test('resolve int > long', function () {
+      var t1 = new types.MapType({type: 'map', values: 'int'});
+      var t2 = new types.MapType({type: 'map', values: 'long'});
+      var resolver = t2.createResolver(t1);
+      var obj = {one: 1, two: 2};
+      var buf = t1.toBuffer(obj);
+      assert.deepEqual(t2.fromBuffer(buf, resolver), obj);
+    });
+
+    test('resolve double > double', function () {
+      var t = new types.MapType({type: 'map', values: 'double'});
+      var resolver = t.createResolver(t);
+      var obj = {one: 1, two: 2};
+      var buf = t.toBuffer(obj);
+      assert.deepEqual(t.fromBuffer(buf, resolver), obj);
+    });
+
+    test('resolve invalid', function () {
+      var t1 = new types.MapType({type: 'map', values: 'int'});
+      var t2 = new types.MapType({type: 'map', values: 'string'});
+      assert.throws(function () { t2.createResolver(t1); });
+      t2 = new types.ArrayType({type: 'array', items: 'string'});
+      assert.throws(function () { t2.createResolver(t1); });
+    });
+
+    test('resolve fixed', function () {
+      var t1 = createType({
+        type: 'map', values: {name: 'Id', type: 'fixed', size: 2}
+      });
+      var t2 = createType({
+        type: 'map', values: {
+          name: 'Id2', aliases: ['Id'], type: 'fixed', size: 2
+        }
+      });
+      var resolver = t2.createResolver(t1);
+      var obj = {one: new Buffer([1, 2])};
+      var buf = t1.toBuffer(obj);
+      assert.deepEqual(t2.fromBuffer(buf, resolver), obj);
+    });
+
+    test('clone', function () {
+      var t = new types.MapType({type: 'map', values: 'int'});
+      var o = {one: 1, two: 2};
+      var c = t.clone(o);
+      assert.deepEqual(c, o);
+      c.one = 3;
+      assert.equal(o.one, 1);
+      assert.throws(function () { t.clone(undefined); });
+    });
+
+    test('clone coerce buffers', function () {
+      var t = new types.MapType({type: 'map', values: 'bytes'});
+      var o = {one: {type: 'Buffer', data: [1]}};
+      assert.throws(function () { t.clone(o); });
+      var c = t.clone(o, {coerceBuffers: true});
+      assert.deepEqual(c, {one: new Buffer([1])});
+    });
+
+    test('compare buffers', function () {
+      var t = new types.MapType({type: 'map', values: 'bytes'});
+      var b1 = t.toBuffer({});
+      assert.throws(function () { t.compareBuffers(b1, b1); });
+    });
+
+    test('isValid hook', function () {
+      var t = new types.MapType({type: 'map', values: 'int'});
+      var o = {one: 1, two: 'deux', three: null, four: 4};
+      var errs = {};
+      assert(!t.isValid(o, {errorHook: hook}));
+      assert.deepEqual(errs, {two: 'deux', three: null});
+
+      function hook(path, obj, type) {
+        assert.strictEqual(type, t.getValuesType());
+        assert.equal(path.length, 1);
+        errs[path[0]] = obj;
+      }
+    });
+
+    test('getName', function () {
+      var t = new types.MapType({type: 'map', values: 'int'});
+      assert.strictEqual(t.getName(), undefined);
+    });
+
+  });
+
+  suite('ArrayType', function () {
+
+    var data = [
+      {
+        name: 'int',
+        schema: {items: 'int'},
+        valid: [[1,3,4], []],
+        invalid: [1, {o: null}, undefined, ['a'], [true]],
+        check: assert.deepEqual
+      }
+    ];
+
+    var schemas = [
+      {},
+      {items: ''},
+    ];
+
+    testType(types.ArrayType, data, schemas);
+
+    test('get items type', function () {
+      var t = new types.ArrayType({type: 'array', items: 'int'});
+      assert.deepEqual(t.getItemsType(), createType('int'));
+    });
+
+    test('read with sizes', function () {
+      var t = new types.ArrayType({type: 'array', items: 'int'});
+      var buf = new Buffer([1,2,2,0]);
+      assert.deepEqual(t.fromBuffer(buf), [1]);
+    });
+
+    test('skip', function () {
+      var v1 = createType({
+        name: 'Foo',
+        type: 'record',
+        fields: [
+          {name: 'array', type: {type: 'array', items: 'int'}},
+          {name: 'val', type: 'int'}
+        ]
+      });
+      var v2 = createType({
+        name: 'Foo',
+        type: 'record',
+        fields: [{name: 'val', type: 'int'}]
+      });
+      var b1 = new Buffer([2,2,0,6]); // Without sizes.
+      var b2 = new Buffer([1,2,2,0,6]); // With sizes.
+      var resolver = v2.createResolver(v1);
+      assert.deepEqual(v2.fromBuffer(b1, resolver), {val: 3});
+      assert.deepEqual(v2.fromBuffer(b2, resolver), {val: 3});
+    });
+
+    test('resolve string items to bytes items', function () {
+      var t1 = new types.ArrayType({type: 'array', items: 'string'});
+      var t2 = new types.ArrayType({type: 'array', items: 'bytes'});
+      var resolver = t2.createResolver(t1);
+      var obj = ['\x01\x02'];
+      var buf = t1.toBuffer(obj);
+      assert.deepEqual(t2.fromBuffer(buf, resolver), [new Buffer([1, 2])]);
+    });
+
+    test('resolve invalid', function () {
+      var t1 = new types.ArrayType({type: 'array', items: 'string'});
+      var t2 = new types.ArrayType({type: 'array', items: 'long'});
+      assert.throws(function () { t2.createResolver(t1); });
+      t2 = new types.MapType({type: 'map', values: 'string'});
+      assert.throws(function () { t2.createResolver(t1); });
+    });
+
+    test('clone', function () {
+      var t = new types.ArrayType({type: 'array', items: 'int'});
+      var o = [1, 2];
+      var c = t.clone(o);
+      assert.deepEqual(c, o);
+      c.one = 3;
+      assert.equal(o[0], 1);
+      assert.throws(function () { t.clone({}); });
+    });
+
+    test('clone coerce buffers', function () {
+      var t = createType({
+        type: 'array',
+        items: {type: 'fixed', name: 'Id', size: 2}
+      });
+      var o = [{type: 'Buffer', data: [1, 2]}];
+      assert.throws(function () { t.clone(o); });
+      var c = t.clone(o, {coerceBuffers: true});
+      assert.deepEqual(c, [new Buffer([1, 2])]);
+    });
+
+    test('compare buffers', function () {
+      var t = createType({type: 'array', items: 'int'});
+      assert.equal(t.compareBuffers(t.toBuffer([]), t.toBuffer([])), 0);
+      assert.equal(t.compareBuffers(t.toBuffer([1, 2]), t.toBuffer([])), 1);
+      assert.equal(t.compareBuffers(t.toBuffer([1]), t.toBuffer([1, -1])), -1);
+      assert.equal(t.compareBuffers(t.toBuffer([1]), t.toBuffer([2])), -1);
+      assert.equal(t.compareBuffers(t.toBuffer([1, 2]), t.toBuffer([1])), 1);
+    });
+
+    test('compare', function () {
+      var t = createType({type: 'array', items: 'int'});
+      assert.equal(t.compare([], []), 0);
+      assert.equal(t.compare([], [-1]), -1);
+      assert.equal(t.compare([1], [1]), 0);
+      assert.equal(t.compare([2], [1, 2]), 1);
+    });
+
+    test('isValid hook invalid array', function () {
+      var t = createType({type: 'array', items: 'int'});
+      var hookCalled = false;
+      assert(!t.isValid({}, {errorHook: hook}));
+      assert(hookCalled);
+
+      function hook(path, obj, type) {
+        assert.strictEqual(type, t);
+        assert.deepEqual(path, []);
+        hookCalled = true;
+      }
+    });
+
+    test('isValid hook invalid elems', function () {
+      var t = createType({type: 'array', items: 'int'});
+      var paths = [];
+      assert(!t.isValid([0, 3, 'hi', 5, 'hey'], {errorHook: hook}));
+      assert.deepEqual(paths, [['2'], ['4']]);
+
+      function hook(path, obj, type) {
+        assert.strictEqual(type, t.getItemsType());
+        assert.equal(typeof obj, 'string');
+        paths.push(path);
+      }
+    });
+
+  });
+
+  suite('RecordType', function () {
+
+    var data = [
+      {
+        name: 'union field null and string with default',
+        schema: {
+          type: 'record',
+          name: 'a',
+          fields: [{name: 'b', type: ['null', 'string'], 'default': null}]
+        },
+        valid: [],
+        invalid: [],
+        check: assert.deepEqual
+      }
+    ];
+
+    var schemas = [
+      {type: 'record', name: 'a', fields: ['null', 'string']},
+      {type: 'record', name: 'a', fields: [{type: ['null', 'string']}]},
+      {
+        type: 'record',
+        name: 'a',
+        fields: [{name: 'b', type: ['null', 'string'], 'default': 'a'}]
+      },
+      {type: 'record', name: 'a', fields: {type: 'int', name: 'age'}}
+    ];
+
+    testType(types.RecordType, data, schemas);
+
+    test('duplicate field names', function () {
+      assert.throws(function () {
+        createType({
+          type: 'record',
+          name: 'Person',
+          fields: [{name: 'age', type: 'int'}, {name: 'age', type: 'float'}]
+        });
+      });
+    });
+
+    test('default constructor', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'age', type: 'int', 'default': 25}]
+      });
+      var Person = type.getRecordConstructor();
+      var p = new Person();
+      assert.equal(p.age, 25);
+      assert.strictEqual(p.constructor, Person);
+    });
+
+    test('default check & write', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {name: 'age', type: 'int', 'default': 25},
+          {name: 'name', type: 'string', 'default': '\x01'}
+        ]
+      });
+      assert.deepEqual(type.toBuffer({}), new Buffer([50, 2, 1]));
+    });
+
+    test('fixed string default', function () {
+      var s = '\x01\x04';
+      var b = new Buffer(s);
+      var type = createType({
+        type: 'record',
+        name: 'Object',
+        fields: [
+          {
+            name: 'id',
+            type: {type: 'fixed', size: 2, name: 'Id'},
+            'default': s
+          }
+        ]
+      });
+      var obj = new (type.getRecordConstructor())();
+      assert.deepEqual(obj.id, new Buffer([1, 4]));
+      assert.deepEqual(type.toBuffer({}), b);
+    });
+
+    test('fixed buffer invalid default', function () {
+      assert.throws(function () {
+        createType({
+          type: 'record',
+          name: 'Object',
+          fields: [
+            {
+              name: 'id',
+              type: {type: 'fixed', size: 2, name: 'Id'},
+              'default': new Buffer([0])
+            }
+          ]
+        });
+      });
+    });
+
+    test('union invalid default', function () {
+      assert.throws(function () {
+        createType({
+          type: 'record',
+          name: 'Person',
+          fields: [{name: 'name', type: ['null', 'string'], 'default': ''}]
+        });
+      });
+    });
+
+    test('record default', function () {
+      var d = {street: null, zip: 123};
+      var Person = createType({
+        name: 'Person',
+        type: 'record',
+        fields: [
+          {
+            name: 'address',
+            type: {
+              name: 'Address',
+              type: 'record',
+              fields: [
+                {name: 'street', type: ['null', 'string']},
+                {name: 'zip', type: ['int', 'string']}
+              ]
+            },
+            'default': d
+          }
+        ]
+      }).getRecordConstructor();
+      var p = new Person();
+      assert.deepEqual(p.address, {street: null, zip: {'int': 123}});
+    });
+
+    test('record keyword field name', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'null', type: 'int'}]
+      });
+      var Person = type.getRecordConstructor();
+      assert.deepEqual(new Person(2), {'null': 2});
+    });
+
+    test('record isValid', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'age', type: 'int'}]
+      });
+      var Person = type.getRecordConstructor();
+      assert((new Person(20)).$isValid());
+      assert(!(new Person()).$isValid());
+      assert(!(new Person('a')).$isValid());
+    });
+
+    test('record toBuffer', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'age', type: 'int'}]
+      });
+      var Person = type.getRecordConstructor();
+      assert.deepEqual((new Person(48)).$toBuffer(), new Buffer([96]));
+      assert.throws(function () { (new Person()).$toBuffer(); });
+    });
+
+    test('record compare', function () {
+      var P = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {name: 'data', type: {type: 'map', values: 'int'}, order:'ignore'},
+          {name: 'age', type: 'int'}
+        ]
+      }).getRecordConstructor();
+      var p1 = new P({}, 1);
+      var p2 = new P({}, 2);
+      assert.equal(p1.$compare(p2), -1);
+      assert.equal(p2.$compare(p2), 0);
+      assert.equal(p2.$compare(p1), 1);
+    });
+
+    test('Record type', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'age', type: 'int'}]
+      });
+      var Person = type.getRecordConstructor();
+      assert.strictEqual(Person.getType(), type);
+    });
+
+    test('mutable defaults', function () {
+      var Person = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {
+            name: 'friends',
+            type: {type: 'array', items: 'string'},
+            'default': []
+          }
+        ]
+      }).getRecordConstructor();
+      var p1 = new Person(undefined);
+      assert.deepEqual(p1.friends, []);
+      p1.friends.push('ann');
+      var p2 = new Person(undefined);
+      assert.deepEqual(p2.friends, []);
+    });
+
+    test('resolve alias', function () {
+      var v1 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'name', type: 'string'}]
+      });
+      var p = v1.random();
+      var buf = v1.toBuffer(p);
+      var v2 = createType({
+        type: 'record',
+        name: 'Human',
+        aliases: ['Person'],
+        fields: [{name: 'name', type: 'string'}]
+      });
+      var resolver = v2.createResolver(v1);
+      assert.deepEqual(v2.fromBuffer(buf, resolver), p);
+      var v3 = createType({
+        type: 'record',
+        name: 'Human',
+        fields: [{name: 'name', type: 'string'}]
+      });
+      assert.throws(function () { v3.createResolver(v1); });
+    });
+
+    test('resolve alias with namespace', function () {
+      var v1 = createType({
+        type: 'record',
+        name: 'Person',
+        namespace: 'earth',
+        fields: [{name: 'name', type: 'string'}]
+      });
+      var v2 = createType({
+        type: 'record',
+        name: 'Human',
+        aliases: ['Person'],
+        fields: [{name: 'name', type: 'string'}]
+      });
+      assert.throws(function () { v2.createResolver(v1); });
+      var v3 = createType({
+        type: 'record',
+        name: 'Human',
+        aliases: ['earth.Person'],
+        fields: [{name: 'name', type: 'string'}]
+      });
+      assert.doesNotThrow(function () { v3.createResolver(v1); });
+    });
+
+    test('resolve skip field', function () {
+      var v1 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {name: 'age', type: 'int'},
+          {name: 'name', type: 'string'}
+        ]
+      });
+      var p = {age: 25, name: 'Ann'};
+      var buf = v1.toBuffer(p);
+      var v2 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'name', type: 'string'}]
+      });
+      var resolver = v2.createResolver(v1);
+      assert.deepEqual(v2.fromBuffer(buf, resolver), {name: 'Ann'});
+    });
+
+    test('resolve new field', function () {
+      var v1 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'name', type: 'string'}]
+      });
+      var p = {name: 'Ann'};
+      var buf = v1.toBuffer(p);
+      var v2 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {name: 'age', type: 'int', 'default': 25},
+          {name: 'name', type: 'string'}
+        ]
+      });
+      var resolver = v2.createResolver(v1);
+      assert.deepEqual(v2.fromBuffer(buf, resolver), {name: 'Ann', age: 25});
+    });
+
+    test('resolve new field no default', function () {
+      var v1 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'name', type: 'string'}]
+      });
+      var v2 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {name: 'age', type: 'int'},
+          {name: 'name', type: 'string'}
+        ]
+      });
+      assert.throws(function () { v2.createResolver(v1); });
+    });
+
+    test('resolve from recursive schema', function () {
+      var v1 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'friends', type: {type: 'array', items: 'Person'}}]
+      });
+      var v2 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'age', type: 'int', 'default': -1}]
+      });
+      var resolver = v2.createResolver(v1);
+      var p1 = {friends: [{friends: []}]};
+      var p2 = v2.fromBuffer(v1.toBuffer(p1), resolver);
+      assert.deepEqual(p2, {age: -1});
+    });
+
+    test('resolve to recursive schema', function () {
+      var v1 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'age', type: 'int', 'default': -1}]
+      });
+      var v2 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {
+            name: 'friends',
+            type: {type: 'array', items: 'Person'},
+            'default': []
+          }
+        ]
+      });
+      var resolver = v2.createResolver(v1);
+      var p1 = {age: 25};
+      var p2 = v2.fromBuffer(v1.toBuffer(p1), resolver);
+      assert.deepEqual(p2, {friends: []});
+    });
+
+    test('resolve from both recursive schema', function () {
+      var v1 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {name: 'friends', type: {type: 'array', items: 'Person'}},
+          {name: 'age', type: 'int'}
+        ]
+      });
+      var v2 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'friends', type: {type: 'array', items: 'Person'}}]
+      });
+      var resolver = v2.createResolver(v1);
+      var p1 = {friends: [{age: 1, friends: []}], age: 10};
+      var p2 = v2.fromBuffer(v1.toBuffer(p1), resolver);
+      assert.deepEqual(p2, {friends: [{friends: []}]});
+    });
+
+    test('resolve multiple matching aliases', function () {
+      var v1 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {name: 'phone', type: 'string'},
+          {name: 'number', type: 'string'}
+        ]
+      });
+      var v2 = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'number', type: 'string', aliases: ['phone']}]
+      });
+      assert.throws(function () { v2.createResolver(v1); });
+    });
+
+    test('getSchema', function () {
+      var t = createType({
+        type: 'record',
+        name: 'Person',
+        doc: 'Hi!',
+        namespace: 'earth',
+        aliases: ['Human'],
+        fields: [
+          {name: 'friends', type: {type: 'array', items: 'string'}},
+          {name: 'age', aliases: ['years'], type: {type: 'int'}}
+        ]
+      });
+      assert.equal(
+        t.getSchema(),
+        '{"name":"earth.Person","type":"record","fields":[{"name":"friends","type":{"type":"array","items":"string"}},{"name":"age","type":"int"}]}'
+      );
+      assert.equal(t.getSchema(true), '"earth.Person"');
+    });
+
+    test('getSchema recursive schema', function () {
+      var t = createType({
+        type: 'record',
+        name: 'Person',
+        namespace: 'earth',
+        fields: [
+          {name: 'friends', type: {type: 'array', items: 'Person'}},
+        ]
+      });
+      assert.equal(
+        t.getSchema(),
+        '{"name":"earth.Person","type":"record","fields":[{"name":"friends","type":{"type":"array","items":"earth.Person"}}]}'
+      );
+      assert.equal(t.getSchema(true), '"earth.Person"');
+    });
+
+    test('toString record', function () {
+      var T = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'pwd', type: 'bytes'}]
+      }).getRecordConstructor();
+      var r = new T(new Buffer([1, 2]));
+      assert.equal(r.$toString(), T.getType().toString(r));
+    });
+
+    test('clone', function () {
+      var t = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'age', type: 'int'}, {name: 'name', type: 'string'}]
+      });
+      var Person = t.getRecordConstructor();
+      var o = {age: 25, name: 'Ann'};
+      var c = t.clone(o);
+      assert.deepEqual(c, o);
+      assert(c instanceof Person);
+      c.age = 26;
+      assert.equal(o.age, 25);
+      assert.strictEqual(c.$getType(), t);
+      assert.deepEqual(c.$clone(), c);
+    });
+
+    test('clone field hook', function () {
+      var t = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'age', type: 'int'}, {name: 'name', type: 'string'}]
+      });
+      var o = {name: 'Ann', age: 25};
+      var c = t.clone(o, {fieldHook: function (f, o, r) {
+        assert.strictEqual(r, t);
+        return f._type instanceof types.StringType ? o.toUpperCase() : o;
+      }});
+      assert.deepEqual(c, {name: 'ANN', age: 25});
+    });
+
+    test('get full name & aliases', function () {
+      var t = createType({
+        type: 'record',
+        name: 'Person',
+        namespace: 'a',
+        fields: [{name: 'age', type: 'int'}, {name: 'name', type: 'string'}]
+      });
+      assert.equal(t.getName(), 'a.Person');
+      assert.deepEqual(t.getAliases(), []);
+    });
+
+    test('field getters', function () {
+      var t = createType({
+        type: 'record',
+        name: 'Person',
+        namespace: 'a',
+        fields: [
+          {name: 'age', type: 'int'},
+          {name: 'name', type: 'string', aliases: ['word'], namespace: 'b'}
+        ]
+      });
+      var fields = t.getFields();
+      assert.deepEqual(fields[0].getAliases(), []);
+      assert.deepEqual(fields[1].getAliases(), ['word']);
+      assert.equal(fields[1].getName(), 'name'); // Namespaces are ignored.
+      assert.deepEqual(fields[1].getType(), createType('string'));
+      fields.push('null');
+      assert.equal(t.getFields().length, 2); // No change.
+    });
+
+    test('field order', function () {
+      var t = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'age', type: 'int'}]
+      });
+      var field = t.getFields()[0];
+      assert.equal(field.getOrder(), 'ascending'); // Default.
+    });
+
+    test('compare buffers default order', function () {
+      var t = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {name: 'age', type: 'long'},
+          {name: 'name', type: 'string'},
+          {name: 'weight', type: 'float'},
+        ]
+      });
+      var b1 = t.toBuffer({age: 20, name: 'Ann', weight: 0.5});
+      assert.equal(t.compareBuffers(b1, b1), 0);
+      var b2 = t.toBuffer({age: 20, name: 'Bob', weight: 0});
+      assert.equal(t.compareBuffers(b1, b2), -1);
+      var b3 = t.toBuffer({age: 19, name: 'Carrie', weight: 0});
+      assert.equal(t.compareBuffers(b1, b3), 1);
+    });
+
+    test('compare buffers custom order', function () {
+      var t = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {name: 'meta', type: {type: 'map', values: 'int'}, order: 'ignore'},
+          {name: 'name', type: 'string', order: 'descending'}
+        ]
+      });
+      var b1 = t.toBuffer({meta: {}, name: 'Ann'});
+      assert.equal(t.compareBuffers(b1, b1), 0);
+      var b2 = t.toBuffer({meta: {foo: 1}, name: 'Bob'});
+      assert.equal(t.compareBuffers(b1, b2), 1);
+      var b3 = t.toBuffer({meta: {foo: 0}, name: 'Alex'});
+      assert.equal(t.compareBuffers(b1, b3), -1);
+    });
+
+    test('compare buffers invalid order', function () {
+      assert.throws(function () { createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'age', type: 'int', order: 'up'}]
+      }); });
+    });
+
+    test('error type', function () {
+      var t = createType({
+        type: 'error',
+        name: 'Ouch',
+        fields: [{name: 'name', type: 'string'}]
+      });
+      var E = t.getRecordConstructor();
+      var err = new E('MyError');
+      assert(err instanceof Error);
+    });
+
+    test('isValid hook', function () {
+      var t = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {name: 'age', type: 'int'},
+          {name: 'names', type: {type: 'array', items: 'string'}}
+        ]
+      });
+      var hasErr = false;
+      try {
+        assert(!t.isValid({age: 23, names: ['ann', null]}, {errorHook: hook}));
+      } catch (err) {
+        hasErr = true;
+      }
+      assert(hasErr);
+      hasErr = false;
+      try {
+        // Again to make sure `PATH` was correctly reset.
+        assert(!t.isValid({age: 23, names: ['ann', null]}, {errorHook: hook}));
+      } catch (err) {
+        hasErr = true;
+      }
+      assert(hasErr);
+
+      function hook(path, obj, type) {
+        assert.strictEqual(type, t.getFields()[1].getType().getItemsType());
+        assert.deepEqual(path, ['names', '1']);
+        throw new Error();
+      }
+    });
+
+    test('isValid empty record', function () {
+      var t = createType({type: 'record', name: 'Person', fields: []});
+      assert(t.isValid({}));
+    });
+
+  });
+
+  suite('AbstractLongType', function () {
+
+    var fastLongType = new types.LongType();
+
+    suite('unpacked', function () {
+
+      var slowLongType = types.LongType.using({
+        fromBuffer: function (buf) {
+          var neg = buf[7] >> 7;
+          if (neg) { // Negative number.
+            invert(buf);
+          }
+          var n = buf.readInt32LE() + Math.pow(2, 32) * buf.readInt32LE(4);
+          if (neg) {
+            invert(buf);
+            n = -n - 1;
+          }
+          return n;
+        },
+        toBuffer: function (n) {
+          var buf = new Buffer(8);
+          var neg = n < 0;
+          if (neg) {
+            invert(buf);
+            n = -n - 1;
+          }
+          buf.writeInt32LE(n | 0);
+          var h = n / Math.pow(2, 32) | 0;
+          buf.writeInt32LE(h ? h : (n >= 0 ? 0 : -1), 4);
+          if (neg) {
+            invert(buf);
+          }
+          return buf;
+        },
+        isValid: function (n) {
+          return typeof n == 'number' && n % 1 === 0;
+        },
+        fromJSON: function (n) { return n; },
+        toJSON: function (n) { return n; },
+        compare: function (n1, n2) {
+          return n1 === n2 ? 0 : (n1 < n2 ? -1 : 1);
+        }
+      });
+
+      test('encode', function () {
+        [123, -1, 321414, 900719925474090].forEach(function (n) {
+          assert.deepEqual(slowLongType.toBuffer(n), fastLongType.toBuffer(n));
+        });
+      });
+
+      test('decode', function () {
+        [123, -1, 321414, 900719925474090].forEach(function (n) {
+          var buf = fastLongType.toBuffer(n);
+          assert.deepEqual(slowLongType.fromBuffer(buf), n);
+        });
+      });
+
+      test('clone', function () {
+        assert.equal(slowLongType.clone(123), 123);
+        assert.equal(slowLongType.fromString('-1'), -1);
+        assert.equal(slowLongType.toString(-1), '-1');
+      });
+
+      test('random', function () {
+        assert(slowLongType.isValid(slowLongType.random()));
+      });
+
+      test('isValid hook', function () {
+        var s = 'hi';
+        var errs = [];
+        assert(!slowLongType.isValid(s, {errorHook: hook}));
+        assert.deepEqual(errs, [s]);
+        assert.throws(function () { slowLongType.toBuffer(s); });
+
+        function hook(path, obj, type) {
+          assert.strictEqual(type, slowLongType);
+          assert.equal(path.length, 0);
+          errs.push(obj);
+        }
+      });
+
+    });
+
+    suite('packed', function () {
+
+      var slowLongType = types.LongType.using({
+        fromBuffer: function (buf) {
+          var tap = new Tap(buf);
+          return tap.readLong();
+        },
+        toBuffer: function (n) {
+          var buf = new Buffer(10);
+          var tap = new Tap(buf);
+          tap.writeLong(n);
+          return buf.slice(0, tap.pos);
+        },
+        fromJSON: function (n) { return n; },
+        toJSON: function (n) { return n; },
+        isValid: function (n) { return typeof n == 'number' && n % 1 === 0; },
+        compare: function (n1, n2) {
+          return n1 === n2 ? 0 : (n1 < n2 ? -1 : 1);
+        }
+      }, true);
+
+      test('encode', function () {
+        [123, -1, 321414, 900719925474090].forEach(function (n) {
+          assert.deepEqual(slowLongType.toBuffer(n), fastLongType.toBuffer(n));
+        });
+      });
+
+      test('decode', function () {
+        [123, -1, 321414, 900719925474090].forEach(function (n) {
+          var buf = fastLongType.toBuffer(n);
+          assert.deepEqual(slowLongType.fromBuffer(buf), n);
+        });
+      });
+
+      test('clone', function () {
+        assert.equal(slowLongType.clone(123), 123);
+        assert.equal(slowLongType.fromString('-1'), -1);
+        assert.equal(slowLongType.toString(-1), '-1');
+      });
+
+      test('random', function () {
+        assert(slowLongType.isValid(slowLongType.random()));
+      });
+
+    });
+
+    test('incomplete buffer', function () {
+      // Check that `fromBuffer` doesn't get called.
+      var slowLongType = new types.LongType.using({
+        fromBuffer: function () { throw new Error('no'); },
+        toBuffer: null,
+        fromJSON: null,
+        toJSON: null,
+        isValid: null,
+        compare: null
+      });
+      var buf = fastLongType.toBuffer(12314);
+      assert.deepEqual(
+        slowLongType.decode(buf.slice(0, 1)),
+        {value: undefined, offset: -1}
+      );
+    });
+
+  });
+
+  suite('LogicalType', function () {
+
+    function DateType(attrs, opts) {
+      types.LogicalType.call(this, attrs, opts, [types.LongType]);
+    }
+    util.inherits(DateType, types.LogicalType);
+
+    DateType.prototype._fromValue = function (val) { return new Date(val); };
+
+    DateType.prototype._toValue = function (date) { return +date; };
+
+    DateType.prototype._resolve = function (type) {
+      if (type instanceof types.StringType) {
+        return function (str) { return new Date(Date.parse(str)); };
+      } else if (type instanceof types.LongType) {
+        return this.fromValue;
+      }
+    };
+
+    function AgeType(attrs, opts) {
+      types.LogicalType.call(this, attrs, opts, [types.IntType]);
+    }
+    util.inherits(AgeType, types.LogicalType);
+
+    AgeType.prototype._fromValue = function (val) {
+      if (val < 0) { throw new Error('invalid age'); }
+      return val;
+    };
+
+    AgeType.prototype._toValue = AgeType.prototype._fromValue;
+
+    var logicalTypes = {age: AgeType, date: DateType};
+
+    test('valid type', function () {
+      var t = createType({
+        type: 'long',
+        logicalType: 'date'
+      }, {logicalTypes: logicalTypes});
+      assert(t instanceof DateType);
+      assert(t.getUnderlyingType() instanceof types.LongType);
+      assert(t.isValid(t.random()));
+      var d = new Date(123);
+      assert.equal(t.toString(d), '123');
+      assert.deepEqual(t.fromString('123'), d);
+      assert.deepEqual(t.clone(d), d);
+      assert.equal(t.compare(d, d), 0);
+      assert.equal(t.getSchema(), '"long"');
+    });
+
+    test('invalid type', function () {
+      var attrs = {
+        type: 'int',
+        logicalType: 'date'
+      };
+      var t;
+      t = createType(attrs); // Missing.
+      assert(t instanceof types.IntType);
+      t = createType(attrs, {logicalTypes: logicalTypes}); // Invalid.
+      assert(t instanceof types.IntType);
+      assert.throws(function () {
+        createType(attrs, {
+          logicalTypes: logicalTypes,
+          assertLogicalType: true
+        });
+      });
+    });
+
+    test('nested types', function () {
+      var attrs = {
+        name: 'Person',
+        type: 'record',
+        fields: [
+          {name: 'age', type: {type: 'int', logicalType: 'age'}},
+          {name: 'time', type: {type: 'long', logicalType: 'date'}}
+        ]
+      };
+      var base = createType(attrs);
+      var derived = createType(attrs, {logicalTypes: logicalTypes});
+      var fields = derived.getFields();
+      assert(fields[0].getType() instanceof AgeType);
+      assert(fields[1].getType() instanceof DateType);
+      var date = new Date(Date.now());
+      var buf = base.toBuffer({age: 12, time: +date});
+      var person = derived.fromBuffer(buf);
+      assert.deepEqual(person.age, 12);
+      assert.deepEqual(person.time, date);
+      assert.throws(function () { derived.toBuffer({age: -1, date: date}); });
+    });
+
+    test('recursive', function () {
+
+      function Person(friends) { this.friends = friends || []; }
+
+      function PersonType(attrs, opts) {
+        types.LogicalType.call(this, attrs, opts);
+      }
+      util.inherits(PersonType, types.LogicalType);
+
+      PersonType.prototype._fromValue = function (val) {
+        return new Person(val.friends);
+      };
+
+      PersonType.prototype._toValue = function (val) { return val; };
+
+      var t = createType({
+        type: 'record',
+        name: 'Person',
+        namespace: 'earth',
+        logicalType: 'person',
+        fields: [
+          {name: 'friends', type: {type: 'array', items: 'Person'}},
+        ]
+      }, {logicalTypes: {'person': PersonType}});
+
+      var p1 = new Person([new Person()]);
+      var buf = t.toBuffer(p1);
+      var p2 = t.fromBuffer(buf);
+      assert(p2 instanceof Person);
+      assert(p2.friends[0] instanceof Person);
+      assert.deepEqual(p2, p1);
+    });
+
+    test('resolve underlying > logical', function () {
+      var t1 = createType({type: 'string'});
+      var t2 = createType({
+        type: 'long',
+        logicalType: 'date'
+      }, {logicalTypes: logicalTypes});
+
+      var d1 = new Date(Date.now());
+      var buf = t1.toBuffer('' + d1);
+      var res = t2.createResolver(t1);
+      assert.throws(function () { t2.createResolver(createType('float')); });
+      var d2 = t2.fromBuffer(buf, res);
+      assert.deepEqual('' + d2, '' + d1); // Rounding error on date objects.
+    });
+
+    test('resolve logical > underlying', function () {
+      var t1 = createType({
+        type: 'long',
+        logicalType: 'date'
+      }, {logicalTypes: logicalTypes});
+      var t2 = createType({type: 'double'}); // Note long > double too.
+
+      var d = new Date(Date.now());
+      var buf = t1.toBuffer(d);
+      var res = t2.createResolver(t1);
+      assert.throws(function () { createType('int').createResolver(t1); });
+      assert.equal(t2.fromBuffer(buf, res), +d);
+    });
+
+  });
+
+  suite('createType', function  () {
+
+    test('null type', function () {
+      assert.throws(function () { createType(null); });
+    });
+
+    test('unknown types', function () {
+      assert.throws(function () { createType('a'); });
+      assert.throws(function () { createType({type: 'b'}); });
+    });
+
+    test('namespaced type', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Human',
+        namespace: 'earth',
+        fields: [
+          {
+            name: 'id',
+            type: {type: 'fixed', name: 'Id', size: 2, namespace: 'all'}
+          },
+          {
+            name: 'alien',
+            type: {
+              type: 'record',
+              name: 'Alien',
+              namespace: 'all',
+              fields: [
+                {name: 'friend', type: 'earth.Human'},
+                {name: 'id', type: 'Id'},
+              ]
+            }
+          }
+        ]
+      });
+      assert.equal(type._name, 'earth.Human');
+      assert.equal(type._fields[0]._type._name, 'all.Id');
+      assert.equal(type._fields[1]._type._name, 'all.Alien');
+    });
+
+    test('wrapped primitive', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'nothing', type: {type: 'null'}}]
+      });
+      assert.strictEqual(type._fields[0]._type.constructor, types.NullType);
+    });
+
+    test('fromBuffer truncated', function () {
+      var type = createType('int');
+      assert.throws(function () {
+        type.fromBuffer(new Buffer([128]));
+      });
+    });
+
+    test('fromBuffer bad resolver', function () {
+      var type = createType('int');
+      assert.throws(function () {
+        type.fromBuffer(new Buffer([0]), 123, {});
+      });
+    });
+
+    test('fromBuffer trailing', function () {
+      var type = createType('int');
+      assert.throws(function () {
+        type.fromBuffer(new Buffer([0, 2]));
+      });
+    });
+
+    test('fromBuffer trailing with resolver', function () {
+      var type = createType('int');
+      var resolver = type.createResolver(createType(['int']));
+      assert.equal(type.fromBuffer(new Buffer([0, 2]), resolver), 1);
+    });
+
+    test('toBuffer', function () {
+      var type = createType('int');
+      assert.throws(function () { type.toBuffer('abc'); });
+      assert.doesNotThrow(function () { type.toBuffer(123); });
+    });
+
+    test('toBuffer and resize', function () {
+      var type = createType('string');
+      assert.deepEqual(type.toBuffer('\x01', 1), new Buffer([2, 1]));
+    });
+
+    test('type hook', function () {
+      var refs = [];
+      var ts = [];
+      var o = {
+        type: 'record',
+        name: 'Human',
+        fields: [
+          {name: 'age', type: 'int'},
+          {name: 'name', type: {type: 'string'}}
+        ]
+      };
+      createType(o, {typeHook: hook});
+      assert.equal(ts.length, 1);
+      assert.equal(ts[0].getName(), 'Human');
+
+      function hook(schema, opts) {
+        if (~refs.indexOf(schema)) {
+          // Already seen this schema.
+          return;
+        }
+        refs.push(schema);
+
+        var type = createType(schema, opts);
+        if (type instanceof types.RecordType) {
+          ts.push(type);
+        }
+        return type;
+      }
+    });
+
+    test('type hook invalid return value', function () {
+      assert.throws(function () {
+        createType({type: 'int'}, {typeHook: hook});
+      });
+
+      function hook() { return 'int'; }
+    });
+
+    test('fingerprint', function () {
+      var t = createType('int');
+      var buf = new Buffer('ef524ea1b91e73173d938ade36c1db32', 'hex');
+      assert.deepEqual(t.getFingerprint('md5'), buf);
+      assert.deepEqual(t.getFingerprint(), buf);
+    });
+
+    test('getSchema default', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Human',
+        fields: [
+          {name: 'id1', type: ['string', 'null'], 'default': ''},
+          {name: 'id2', type: ['null', 'string'], 'default': null}
+        ]
+      });
+      assert.deepEqual(
+        JSON.parse(type.getSchema()),
+        {
+          type: 'record',
+          name: 'Human',
+          fields: [
+            {name: 'id1', type: ['string', 'null']}, // Stripped defaults.
+            {name: 'id2', type: ['null', 'string']}
+          ]
+        }
+      );
+    });
+
+  });
+
+  suite('fromString', function () {
+
+    test('int', function () {
+      var t = createType('int');
+      assert.equal(t.fromString('2'), 2);
+      assert.throws(function () { t.fromString('"a"'); });
+    });
+
+    test('string', function () {
+      var t = createType('string');
+      assert.equal(t.fromString('"2"'), '2');
+      assert.throws(function () { t.fromString('a'); });
+    });
+
+    test('coerce buffers', function () {
+      var t = createType({
+        name: 'Ids',
+        type: 'record',
+        fields: [{name: 'id1', type: {name: 'Id1', type: 'fixed', size: 2}}]
+      });
+      var o = {id1: new Buffer([0, 1])};
+      var s = '{"id1": "\\u0000\\u0001"}';
+      var c = t.fromString(s);
+      assert.deepEqual(c, o);
+      assert(c instanceof t.getRecordConstructor());
+    });
+
+  });
+
+  suite('toString', function () {
+
+    test('int', function () {
+      var t = createType('int');
+      assert.equal(t.toString(2), '2');
+      assert.throws(function () { t.toString('a'); });
+    });
+
+  });
+
+  suite('resolve', function () {
+
+    test('non type', function () {
+      var t = createType({type: 'map', values: 'int'});
+      var obj = {type: 'map', values: 'int'};
+      assert.throws(function () { t.createResolver(obj); });
+    });
+
+    test('union to valid union', function () {
+      var t1 = createType(['int', 'string']);
+      var t2 = createType(['null', 'string', 'long']);
+      var resolver = t2.createResolver(t1);
+      var buf = t1.toBuffer({'int': 12});
+      assert.deepEqual(t2.fromBuffer(buf, resolver), {'long': 12});
+    });
+
+    test('union to invalid union', function () {
+      var t1 = createType(['int', 'string']);
+      var t2 = createType(['null', 'long']);
+      assert.throws(function () { t2.createResolver(t1); });
+    });
+
+    test('union to non union', function () {
+      var t1 = createType(['int', 'long']);
+      var t2 = createType('long');
+      var resolver = t2.createResolver(t1);
+      var buf = t1.toBuffer({'int': 12});
+      assert.equal(t2.fromBuffer(buf, resolver), 12);
+      buf = new Buffer([4, 0]);
+      assert.throws(function () { t2.fromBuffer(buf, resolver); });
+    });
+
+    test('union to invalid non union', function () {
+      var t1 = createType(['int', 'long']);
+      var t2 = createType('int');
+      assert.throws(function() { t2.createResolver(t1); });
+    });
+
+  });
+
+  suite('type names', function () {
+
+    test('existing', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'so', type: 'Person'}]
+      });
+      assert.strictEqual(type, type._fields[0]._type);
+    });
+
+    test('namespaced', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [
+          {
+            name: 'so',
+            type: {
+              type: 'record',
+              name: 'Person',
+              fields: [{name: 'age', type: 'int'}],
+              namespace: 'a'
+            }
+          }
+        ]
+      });
+      assert.equal(type._name, 'Person');
+      assert.equal(type._fields[0]._type._name, 'a.Person');
+    });
+
+    test('redefining', function () {
+      assert.throws(function () {
+        createType({
+          type: 'record',
+          name: 'Person',
+          fields: [
+            {
+              name: 'so',
+              type: {
+                type: 'record',
+                name: 'Person',
+                fields: [{name: 'age', type: 'int'}]
+              }
+            }
+          ]
+        });
+      });
+    });
+
+    test('missing', function () {
+      assert.throws(function () {
+        createType({
+          type: 'record',
+          name: 'Person',
+          fields: [{name: 'so', type: 'Friend'}]
+        });
+      });
+    });
+
+    test('redefining primitive', function () {
+      assert.throws( // Unqualified.
+        function () { createType({type: 'fixed', name: 'int', size: 2}); }
+      );
+      assert.throws( // Qualified.
+        function () {
+          createType({type: 'fixed', name: 'int', size: 2, namespace: 'a'});
+        }
+      );
+    });
+
+    test('aliases', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Person',
+        namespace: 'a',
+        aliases: ['Human', 'b.Being'],
+        fields: [{name: 'age', type: 'int'}]
+      });
+      assert.deepEqual(type._aliases, ['a.Human', 'b.Being']);
+    });
+
+    test('invalid', function () {
+      // Name.
+      assert.throws(function () {
+        createType({type: 'fixed', name: 'ID$', size: 3});
+      });
+      // Namespace.
+      assert.throws(function () {
+        createType({type: 'fixed', name: 'ID', size: 3, namespace: '1a'});
+      });
+      // Qualified.
+      assert.throws(function () {
+        createType({type: 'fixed', name: 'a.2.ID', size: 3});
+      });
+    });
+
+  });
+
+  suite('decode', function () {
+
+    test('long valid', function () {
+      var t = createType('long');
+      var buf = new Buffer([0, 128, 2, 0]);
+      var res = t.decode(buf, 1);
+      assert.deepEqual(res, {value: 128, offset: 3});
+    });
+
+    test('bytes invalid', function () {
+      var t = createType('bytes');
+      var buf = new Buffer([4, 1]);
+      var res = t.decode(buf, 0);
+      assert.deepEqual(res, {value: undefined, offset: -1});
+    });
+
+  });
+
+  suite('encode', function () {
+
+    test('int valid', function () {
+      var t = createType('int');
+      var buf = new Buffer(2);
+      buf.fill(0);
+      var n = t.encode(5, buf, 1);
+      assert.equal(n, 2);
+      assert.deepEqual(buf, new Buffer([0, 10]));
+    });
+
+    test('string invalid', function () {
+      var t = createType('string');
+      var buf = new Buffer(1);
+      var n = t.encode('\x01\x02', buf, 0);
+      assert.equal(n, -2);
+    });
+
+    test('invalid', function () {
+      var t = createType('float');
+      var buf = new Buffer(2);
+      assert.throws(function () { t.encode('hi', buf, 0); });
+    });
+
+  });
+
+  suite('inspect', function () {
+
+    test('type', function () {
+      assert.equal(createType('int').inspect(), '<IntType>');
+      assert.equal(
+        createType({type: 'map', values: 'string'}).inspect(),
+        '<MapType {"values":"string"}>'
+      );
+      assert.equal(
+        createType({type: 'fixed', name: 'Id', size: 2}).inspect(),
+        '<FixedType "Id">'
+      );
+    });
+
+    test('field', function () {
+      var type = createType({
+        type: 'record',
+        name: 'Person',
+        fields: [{name: 'age', type: 'int'}]
+      });
+      var field = type.getFields()[0];
+      assert.equal(field.inspect(), '<Field "age">');
+    });
+
+    test('resolver', function () {
+      var t1 = createType('int');
+      var t2 = createType('double');
+      var resolver = t2.createResolver(t1);
+      assert.equal(resolver.inspect(), '<Resolver>');
+    });
+
+  });
+
+  test('reset', function () {
+    types.Type.__reset(0);
+    var t = createType('string');
+    var buf = t.toBuffer('\x01');
+    assert.deepEqual(buf, new Buffer([2, 1]));
+  });
+
+});
+
+function testType(Type, data, invalidSchemas) {
+
+  data.forEach(function (elem) {
+    test('roundtrip', function () {
+      var type = new Type(elem.schema);
+      elem.valid.forEach(function (v) {
+        assert(type.isValid(v), '' + v);
+        var fn = elem.check || assert.deepEqual;
+        fn(type.fromBuffer(type.toBuffer(v)), v);
+        fn(type.fromString(type.toString(v), {coerceBuffers: true}), v);
+      });
+      elem.invalid.forEach(function (v) {
+        assert(!type.isValid(v), '' + v);
+        assert.throws(function () { type.isValid(v, {errorHook: hook}); });
+        assert.throws(function () { type.toBuffer(v); });
+
+        function hook() { throw new Error(); }
+      });
+      var n = 50;
+      while (n--) {
+        // Run a few times to make sure we cover any branches.
+        assert(type.isValid(type.random()));
+      }
+    });
+  });
+
+  test('skip', function () {
+    data.forEach(function (elem) {
+      var fn = elem.check || assert.deepEqual;
+      var items = elem.valid;
+      if (items.length > 1) {
+        var type = new Type(elem.schema);
+        var buf = new Buffer(1024);
+        var tap = new Tap(buf);
+        type._write(tap, items[0]);
+        type._write(tap, items[1]);
+        tap.pos = 0;
+        type._skip(tap);
+        fn(type._read(tap), items[1]);
+      }
+    });
+  });
+
+  if (invalidSchemas) {
+    test('invalid', function () {
+      invalidSchemas.forEach(function (schema) {
+        assert.throws(function () { new Type(schema); });
+      });
+    });
+  }
+
+}
+
+function getResolver(reader, writer) {
+  return createType(reader).createResolver(createType(writer));
+}
+
+function floatEquals(a, b) {
+  return Math.abs((a - b) / Math.min(a, b)) < 1e-7;
+}
+
+function invert(buf) {
+  var len = buf.length;
+  while (len--) {
+    buf[len] = ~buf[len];
+  }
+}

Added: avro/trunk/lang/js/test/test_utils.js
URL: http://svn.apache.org/viewvc/avro/trunk/lang/js/test/test_utils.js?rev=1717850&view=auto
==============================================================================
--- avro/trunk/lang/js/test/test_utils.js (added)
+++ avro/trunk/lang/js/test/test_utils.js Thu Dec  3 21:35:44 2015
@@ -0,0 +1,397 @@
+/* jshint node: true, mocha: true */
+
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+'use strict';
+
+var utils = require('../lib/utils'),
+    assert = require('assert');
+
+
+suite('utils', function () {
+
+  test('capitalize', function () {
+    assert.equal(utils.capitalize('abc'), 'Abc');
+    assert.equal(utils.capitalize(''), '');
+    assert.equal(utils.capitalize('aBc'), 'ABc');
+  });
+
+  test('hasDuplicates', function () {
+    assert(utils.hasDuplicates([1, 3, 1]));
+    assert(!utils.hasDuplicates([]));
+    assert(!utils.hasDuplicates(['ab', 'cb']));
+    assert(utils.hasDuplicates(['ab', 'cb'], function (s) { return s[1]; }));
+  });
+
+  test('single index of', function () {
+    assert.equal(utils.singleIndexOf(null, 1), -1);
+    assert.equal(utils.singleIndexOf([2], 2), 0);
+    assert.equal(utils.singleIndexOf([3, 3], 3), -2);
+    assert.equal(utils.singleIndexOf([2, 4], 4), 1);
+  });
+
+  test('abstract function', function () {
+    assert.throws(utils.abstractFunction, utils.Error);
+  });
+
+  test('OrderedQueue', function () {
+
+    var seqs = [
+      [0],
+      [0,1],
+      [0,1,2],
+      [2,1,0],
+      [0,2,1,3],
+      [1,3,2,4,0],
+      [0,1,2,3]
+    ];
+
+    var i;
+    for (i = 0; i < seqs.length; i++) {
+      check(seqs[i]);
+    }
+
+    function check(seq) {
+      var q = new utils.OrderedQueue();
+      var i;
+      assert.strictEqual(q.pop(), null);
+      for (i = 0; i < seq.length; i++) {
+        q.push({index: seq[i]});
+      }
+      for (i = 0; i < seq.length; i++) {
+        var j = q.pop();
+        assert.equal(j !== null && j.index, i, seq.join());
+      }
+    }
+
+  });
+
+  suite('Lcg', function () {
+
+    test('seed', function () {
+      var r1 = new utils.Lcg(48);
+      var r2 = new utils.Lcg(48);
+      assert.equal(r1.nextInt(), r2.nextInt());
+    });
+
+    test('integer', function () {
+      var r = new utils.Lcg(48);
+      var i;
+      i = r.nextInt();
+      assert(i >= 0 && i === (i | 0));
+      i = r.nextInt(1);
+      assert.equal(i, 0);
+      i = r.nextInt(1, 2);
+      assert.equal(i, 1);
+    });
+
+    test('float', function () {
+      var r = new utils.Lcg(48);
+      var f;
+      f = r.nextFloat();
+      assert(0 <= f && f < 1);
+      f = r.nextFloat(0);
+      assert.equal(f, 0);
+      f = r.nextFloat(1, 1);
+      assert.equal(f, 1);
+    });
+
+    test('boolean', function () {
+      var r = new utils.Lcg(48);
+      assert(typeof r.nextBoolean() == 'boolean');
+    });
+
+    test('choice', function () {
+      var r = new utils.Lcg(48);
+      var arr = ['a'];
+      assert(r.choice(arr), 'a');
+      assert.throws(function () { r.choice([]); });
+    });
+
+    test('string', function () {
+      var r = new utils.Lcg(48);
+      var s;
+      s = r.nextString(10, 'aA#!');
+      assert.equal(s.length, 10);
+      s = r.nextString(5, '#!');
+      assert.equal(s.length, 5);
+    });
+
+  });
+
+  suite('Tap', function () {
+
+    var Tap = utils.Tap;
+
+    suite('int & long', function () {
+
+      testWriterReader({
+        elems: [0, -1, 109213, -1211, -1312411211, 900719925474090],
+        reader: function () { return this.readLong(); },
+        skipper: function () { this.skipLong(); },
+        writer: function (n) { this.writeLong(n); }
+      });
+
+      test('write', function () {
+
+        var tap = newTap(6);
+        tap.writeLong(1440756011948);
+        var buf = new Buffer(['0xd8', '0xce', '0x80', '0xbc', '0xee', '0x53']);
+        assert(tap.isValid());
+        assert(buf.equals(tap.buf));
+
+      });
+
+      test('read', function () {
+
+        var buf = new Buffer(['0xd8', '0xce', '0x80', '0xbc', '0xee', '0x53']);
+        assert.equal((new Tap(buf)).readLong(), 1440756011948);
+
+      });
+
+    });
+
+    suite('boolean', function () {
+
+      testWriterReader({
+        elems: [true, false],
+        reader: function () { return this.readBoolean(); },
+        skipper: function () { this.skipBoolean(); },
+        writer: function (b) { this.writeBoolean(b); }
+      });
+
+    });
+
+    suite('float', function () {
+
+      testWriterReader({
+        elems: [1, 3,1, -5, 1e9],
+        reader: function () { return this.readFloat(); },
+        skipper: function () { this.skipFloat(); },
+        writer: function (b) { this.writeFloat(b); }
+      });
+
+    });
+
+    suite('double', function () {
+
+      testWriterReader({
+        elems: [1, 3,1, -5, 1e12],
+        reader: function () { return this.readDouble(); },
+        skipper: function () { this.skipDouble(); },
+        writer: function (b) { this.writeDouble(b); }
+      });
+
+    });
+
+    suite('string', function () {
+
+      testWriterReader({
+        elems: ['ahierw', '', 'alh hewlii! rew'],
+        reader: function () { return this.readString(); },
+        skipper: function () { this.skipString(); },
+        writer: function (s) { this.writeString(s); }
+      });
+
+    });
+
+    suite('bytes', function () {
+
+      testWriterReader({
+        elems: [new Buffer('abc'), new Buffer(0), new Buffer([1, 5, 255])],
+        reader: function () { return this.readBytes(); },
+        skipper: function () { this.skipBytes(); },
+        writer: function (b) { this.writeBytes(b); }
+      });
+
+    });
+
+    suite('fixed', function () {
+
+      testWriterReader({
+        elems: [new Buffer([1, 5, 255])],
+        reader: function () { return this.readFixed(3); },
+        skipper: function () { this.skipFixed(3); },
+        writer: function (b) { this.writeFixed(b, 3); }
+      });
+
+    });
+
+    suite('binary', function () {
+
+      test('write valid', function () {
+        var tap = newTap(3);
+        var s = '\x01\x02';
+        tap.writeBinary(s, 2);
+        assert.deepEqual(tap.buf, new Buffer([1,2,0]));
+      });
+
+      test('write invalid', function () {
+        var tap = newTap(1);
+        var s = '\x01\x02';
+        tap.writeBinary(s, 2);
+        assert.deepEqual(tap.buf, new Buffer([0]));
+      });
+
+    });
+
+    suite('pack & unpack longs', function () {
+
+      test('unpack single byte', function () {
+        var t = newTap(10);
+        t.writeLong(5);
+        t.pos = 0;
+        assert.deepEqual(
+          t.unpackLongBytes(),
+          new Buffer([5, 0, 0, 0, 0, 0, 0, 0])
+        );
+        t.pos = 0;
+        t.writeLong(-5);
+        t.pos = 0;
+        assert.deepEqual(
+          t.unpackLongBytes(),
+          new Buffer([-5, -1, -1, -1, -1, -1, -1, -1])
+        );
+        t.pos = 0;
+      });
+
+      test('unpack multiple bytes', function () {
+        var t = newTap(10);
+        var l;
+        l = 18932;
+        t.writeLong(l);
+        t.pos = 0;
+        assert.deepEqual(t.unpackLongBytes().readInt32LE(), l);
+        t.pos = 0;
+        l = -3210984;
+        t.writeLong(l);
+        t.pos = 0;
+        assert.deepEqual(t.unpackLongBytes().readInt32LE(), l);
+      });
+
+      test('pack single byte', function () {
+        var t = newTap(10);
+        var b = new Buffer(8);
+        b.fill(0);
+        b.writeInt32LE(12);
+        t.packLongBytes(b);
+        assert.equal(t.pos, 1);
+        t.pos = 0;
+        assert.deepEqual(t.readLong(), 12);
+        t.pos = 0;
+        b.writeInt32LE(-37);
+        b.writeInt32LE(-1, 4);
+        t.packLongBytes(b);
+        assert.equal(t.pos, 1);
+        t.pos = 0;
+        assert.deepEqual(t.readLong(), -37);
+        t.pos = 0;
+        b.writeInt32LE(-1);
+        b.writeInt32LE(-1, 4);
+        t.packLongBytes(b);
+        assert.deepEqual(t.buf.slice(0, t.pos), new Buffer([1]));
+        t.pos = 0;
+        assert.deepEqual(t.readLong(), -1);
+      });
+
+      test('roundtrip', function () {
+        roundtrip(1231514);
+        roundtrip(-123);
+        roundtrip(124124);
+        roundtrip(109283109271);
+        roundtrip(Number.MAX_SAFE_INTEGER);
+        roundtrip(Number.MIN_SAFE_INTEGER);
+        roundtrip(0);
+        roundtrip(-1);
+
+        function roundtrip(n) {
+          var t1 = newTap(10);
+          var t2 = newTap(10);
+          t1.writeLong(n);
+          t1.pos = 0;
+          t2.packLongBytes(t1.unpackLongBytes());
+          assert.deepEqual(t2, t1);
+        }
+      });
+
+    });
+
+    function newTap(n) {
+
+      var buf = new Buffer(n);
+      buf.fill(0);
+      return new Tap(buf);
+
+    }
+
+    function testWriterReader(opts) {
+
+      var size = opts.size;
+      var elems = opts.elems;
+      var writeFn = opts.writer;
+      var readFn = opts.reader;
+      var skipFn = opts.skipper;
+      var name = opts.name || '';
+
+      test('write read ' + name, function () {
+        var tap = newTap(size || 1024);
+        var i, l, elem;
+        for (i = 0, l = elems.length; i < l; i++) {
+          tap.buf.fill(0);
+          tap.pos = 0;
+          elem = elems[i];
+          writeFn.call(tap, elem);
+          tap.pos = 0;
+          assert.deepEqual(readFn.call(tap), elem);
+        }
+      });
+
+      test('read over ' + name, function () {
+        var tap = new Tap(new Buffer(0));
+        readFn.call(tap); // Shouldn't throw.
+        assert(!tap.isValid());
+      });
+
+      test('write over ' + name, function () {
+        var tap = new Tap(new Buffer(0));
+        writeFn.call(tap, elems[0]); // Shouldn't throw.
+        assert(!tap.isValid());
+      });
+
+      test('skip ' + name, function () {
+        var tap = newTap(size || 1024);
+        var i, l, elem, pos;
+        for (i = 0, l = elems.length; i < l; i++) {
+          tap.buf.fill(0);
+          tap.pos = 0;
+          elem = elems[i];
+          writeFn.call(tap, elem);
+          pos = tap.pos;
+          tap.pos = 0;
+          skipFn.call(tap, elem);
+          assert.equal(tap.pos, pos);
+        }
+      });
+
+    }
+
+  });
+
+});