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);
+ }
+ });
+
+ }
+
+ });
+
+});