protobufjs-no-cli
Version:
Protocol Buffers for JavaScript. Finally.
1,227 lines (1,150 loc) • 93.5 kB
JavaScript
/*
Copyright 2013 Daniel Wirtz <dcode@dcode.io>
Licensed 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.
*/
/**
* ProtoBuf.js Test Suite.
* @author Daniel Wirtz <dcode@dcode.io>
*/
(function(global) {
var FILE = "protobuf.js";
var BROWSER = !!global.window;
var StdOutFixture = require('fixture-stdout');
var fixture = new StdOutFixture();
var ProtoBuf = BROWSER ? global.dcodeIO.ProtoBuf : require(__dirname+"/../dist/"+FILE),
ByteBuffer = BROWSER ? global.dcodeIO.ByteBuffer : ByteBuffer || require("bytebuffer"),
util = BROWSER ? null : require("util"),
fs = BROWSER ? null : require("fs");
if (typeof __dirname == 'undefined') {
__dirname = document.location.href.replace(/[\/\\][^\/\\]*$/, "");
}
/**
* Constructs a new Sandbox for module loaders and shim testing.
* @param {Object.<string,*>} properties Additional properties to set
* @constructor
*/
var Sandbox = function(properties) {
this.ByteBuffer = function() {};
for (var i in properties) {
this[i] = properties[i];
}
this.console = {
log: function(s) {
console.log(s);
}
};
};
function fail(e) {
throw(e);
}
/**
* Validates the complexDotProto and complexInline tests.
* @param {*} test Nodeunit test
* @param {Object} Game Game namespace
*/
function validateComplex(test, Game) {
var Car = Game.Cars.Car,
Vendor = Car.Vendor,
Speed = Car.Speed;
var vendor;
// Car from class with argument list properties
var car = new Car(
"Rusty",
// Vendor from class with object properties
vendor = new Vendor({
"name": "Iron Inc.",
// Address from object
"address": {
"country": "US"
},
"models": ["m1"]
}),
// Speed from enum object
Speed.SUPERFAST
);
test.equal(car.model, "Rusty");
test.equal(car.vendor.name, "Iron Inc.");
test.equal(car.vendor.address.country, "US");
test.equal(car.vendor.address.country, car.getVendor().get_address().country);
var bb = new ByteBuffer(32);
car.encode(bb);
test.equal(bb.flip().toString("debug"), "<0A 05 52 75 73 74 79 12 15 0A 09 49 72 6F 6E 20 49 6E 63 2E 12 04 0A 02 55 53 1A 02 6D 31 18 02>");
var carDec = Car.decode(bb);
test.equal(carDec.model, "Rusty");
test.equal(carDec.vendor.name, "Iron Inc.");
test.equal(carDec.vendor.address.country, "US");
test.equal(carDec.vendor.address.country, carDec.getVendor().get_address().country);
test.equal(carDec.vendor.models[0], "m1");
}
/**
* Test suite.
* @type {Object.<string,function>}
*/
var suite = {
"init": function(test) {
test.ok(typeof ProtoBuf == "object");
test.ok(typeof ProtoBuf.Reflect == 'object');
test.ok(typeof ProtoBuf.loadProto == "function");
test.ok(typeof ProtoBuf.loadProtoFile == "function");
test.strictEqual(ProtoBuf.loadProto, ProtoBuf.protoFromString);
test.strictEqual(ProtoBuf.loadProtoFile, ProtoBuf.protoFromFile);
test.ok(ProtoBuf.ByteBuffer);
test.done();
},
"IS_NODE": function(test) {
test.ok(ProtoBuf.Util.IS_NODE);
test.done();
},
// Example "A Simple Message" from the protobuf docs
// https://developers.google.com/protocol-buffers/docs/encoding#simple
"example1": function(test) {
try{
var builder = ProtoBuf.loadProtoFile(__dirname+"/example1.proto");
var Test1 = builder.build("Test1");
test.ok(typeof Test1 == 'function');
var inst = new Test1(150);
test.ok(inst instanceof ProtoBuf.Builder.Message);
test.equal(inst.a, 150);
test.equal(inst.getA(), 150);
test.equal(inst.get_a(), 150);
inst.setA(151);
test.equal(inst.a, 151);
test.equal(inst.getA(), 151);
test.equal(inst.get_a(), 151);
inst.set_a(152);
test.equal(inst.a, 152);
test.equal(inst.toString(), ".Test1");
test.throws(function() {
inst.setA(null); // required
});
test.throws(function() {
inst.setA([]);
});
var size = inst.calculate();
var bb = new ByteBuffer(3);
inst.encode(bb);
test.strictEqual(bb.offset, size);
test.equal(bb.flip().toString("debug"), "<08 98 01>");
var instDec = Test1.decode(bb);
test.equal(instDec.a, 152);
} catch (e) {
fail(e);
}
test.done();
},
// Basically the same as example1, but with an unsigned value.
"example1u": function(test) {
try{
var builder = ProtoBuf.loadProtoFile(__dirname+"/example1u.proto");
var Test1u = builder.build("Test1u");
test.ok(typeof Test1u == 'function');
var inst = new Test1u(-1);
test.strictEqual(inst.a, 4294967295);
var bb = new ByteBuffer(6);
var size = inst.calculate();
inst.encode(bb);
test.strictEqual(bb.offset, size);
test.equal(bb.flip().toString("debug"), "<08 FF FF FF FF 0F>");
var instDec = Test1u.decode(bb);
test.strictEqual(instDec.a, 4294967295);
} catch (e) {
fail(e);
}
test.done();
},
// Example "Strings" from the protobuf docs
// https://developers.google.com/protocol-buffers/docs/encoding#types
"example2": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/example2.proto");
var Test2 = builder.build("Test2");
var inst = new Test2("testing");
var bb = new ByteBuffer(9);
var size = inst.calculate();
inst.encode(bb);
test.strictEqual(bb.offset, size);
test.equal(bb.flip().toString("debug"), "<12 07 74 65 73 74 69 6E 67>");
var instDec = Test2.decode(bb);
test.equal(instDec.b, "testing");
} catch (e) {
fail(e);
}
test.done();
},
// Example "Embedded Messages" from the protobuf docs
// https://developers.google.com/protocol-buffers/docs/encoding#embedded
"example3": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/example3.proto");
var root = builder.build();
var Test1 = root.Test1;
var Test3 = root.Test3;
var inst = new Test3(new Test1(150));
var bb = new ByteBuffer(5);
test.equal(inst.c.a, 150);
var size = inst.calculate();
inst.encode(bb);
test.strictEqual(bb.offset, size);
test.equal(bb.flip().toString("debug"), "<1A 03 08 96 01>");
var instDec = Test3.decode(bb);
test.equal(instDec.c.a, 150);
} catch(e) {
fail(e);
}
test.done();
},
"example4": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/example4.proto");
var Test4 = builder.build("Test4");
var inst = new Test4([3, 270, 86942]);
var bb = new ByteBuffer(8);
test.equal(inst.d.length, 3);
var size = inst.calculate();
inst.encode(bb);
test.strictEqual(bb.offset, size);
test.equal(bb.flip().toString("debug"), "<22 06 03 8E 02 9E A7 05>");
var instDec = Test4.decode(bb);
test.equal(bb.toString("debug"), "22 06 03 8E 02 9E A7 05|");
test.equal(instDec.d.length, 3);
test.equal(instDec.d[2], 86942);
} catch(e) {
fail(e);
}
test.done();
},
"example5": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/example5.proto");
builder.build();
} catch(e) {
fail(e);
}
test.done();
},
"constructor": function(test) {
var builder = ProtoBuf.loadProtoFile(__dirname+"/example1.proto");
var Test1 = builder.build("Test1");
var t1 = new Test1(123),
t2 = new Test1({a: 123}),
t3 = new Test1(t1);
test.deepEqual(t1, t2);
test.deepEqual(t2, t3);
test.done();
},
"numberFormats": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/numberformats.proto");
var Formats = builder.build("Formats");
test.strictEqual(Formats.DEC, 1);
test.strictEqual(Formats.HEX, 31);
test.strictEqual(Formats.OCT, 15);
var Msg = builder.build("Msg");
var msg = new Msg();
test.strictEqual(msg.dec, -1);
test.strictEqual(msg.hex, -31);
test.strictEqual(msg.hexUC, 521);
test.strictEqual(msg.oct, -15);
test.strictEqual(msg.exp, 0.1e5);
test.strictEqual(msg.nod, 1.);
test.strictEqual(msg.exn, 1e8);
test.strictEqual(msg.sp1, Infinity);
test.strictEqual(msg.sp2, -Infinity);
test.ok(isNaN(msg.sp3));
} catch (e) {
fail(e);
}
test.done();
},
// Check encode/decode against a table of known correct pairs.
// Note that javascript ArrayBuffer does not support signed Zero or NaN
// bertdouglas (https://github.com/bertdouglas)
"float": function(test) {
try {
var str_proto = "message Float {"
+ " required float f = 1;"
+ "}";
var builder = ProtoBuf.loadProto(str_proto);
var root = builder.build();
var Float = root.Float;
var in_tolerance = function (reference,actual) {
var tol = 1e-6;
var scale = 1.0;
if (reference != 0.0 ) {
scale = reference;
};
var err = Math.abs(reference - actual)/scale;
return err < tol;
};
var f_vals = [
// hex values are shown here in big-endian following IEEE754 notation
// protobuf is little-endian
// { f: -0.0 , b: "80 00 00 00" },
{ f: +0.0 , b: "00 00 00 00" },
{ f: -1e-10 , b: "AE DB E6 FF" },
{ f: +1e-10 , b: "2E DB E6 FF" },
{ f: -2e+10 , b: "D0 95 02 F9" },
{ f: +2e+10 , b: "50 95 02 F9" },
{ f: -3e-30 , b: "8E 73 63 90" },
{ f: +3e-30 , b: "0E 73 63 90" },
{ f: -4e+30 , b: "F2 49 F2 CA" },
{ f: +4e+30 , b: "72 49 F2 CA" },
{ f: -123456789.0 , b: "CC EB 79 A3" },
{ f: +123456789.0 , b: "4C EB 79 A3" },
{ f: -0.987654321 , b: "BF 7C D6 EA" },
{ f: +0.987654321 , b: "3F 7C D6 EA" },
{ f: -Infinity , b: "FF 80 00 00" },
{ f: +Infinity , b: "7F 80 00 00" }
// { f: -NaN , b: "FF C0 00 00>" },
// { f: +NaN , b: "7F C0 00 00" }
];
f_vals.map( function(x) {
// check encode
var m1 = new Float();
var b1 = new ByteBuffer();
m1.f = x.f;
m1.encode(b1);
var q1 = b1.slice(1,5).compact().reverse();
test.strictEqual('<' + x.b + '>', q1.toString("debug"));
// check decode
var b2 = new ByteBuffer();
var s1 = x.b + ' 0D';
var s2 = s1.split(" ");
var s3 = s2.reverse();
var i1 = s3.map(function(y) { return parseInt(y,16) } );
i1.map(function(y) { b2.writeUint8(y) });
b2.limit = b2.offset;
b2.offset = 0;
var m2 = Float.decode(b2);
var s4 = "" + x.f +" " + m2.f;
if ( isNaN(x.f) ) {
test.ok( isNaN(m2.f), s4 );
}
else if ( ! isFinite( x.f) ) {
test.ok( x.f === m2.f, s4 );
}
else {
test.ok( in_tolerance(x.f, m2.f), s4 );
}
});
} catch(e) {
fail(e);
}
test.done();
},
"bytes": function(test) {
try {
var str_proto = "message Test { required bytes b = 1; }";
var builder = ProtoBuf.loadProto(str_proto);
var Test = builder.build("Test");
var bb = new ByteBuffer(4).writeUint32(0x12345678).flip();
var myTest = new Test(bb);
test.strictEqual(myTest.b.array, bb.array);
var bb2 = new ByteBuffer(6);
var size = myTest.calculate();
myTest.encode(bb2);
test.strictEqual(bb2.offset, size);
test.equal(bb2.flip().toString("debug"), "<0A 04 12 34 56 78>");
myTest = Test.decode(bb2);
test.equal(myTest.b.BE().readUint32(), 0x12345678);
} catch (e) {
fail(e);
}
test.done();
},
"bytesFromFile": function(test) {
try {
var builder = ProtoBuf.loadProto("message Image { required bytes data = 1; }"),
Image = builder.build("Image"),
data = fs.readFileSync(__dirname+"/../protobuf.png"),
image = new Image({ data: data }),
bb = image.encode(),
imageDec = Image.decode(bb),
dataDec = imageDec.data.toBuffer();
test.strictEqual(data.length, dataDec.length);
test.deepEqual(data, dataDec);
} catch (e) {
fail(e);
}
test.done();
},
"notEnoughBytes": function(test) {
var builder = ProtoBuf.loadProto("message Test { required bytes b = 1; }");
var Test = builder.build("Test");
var bb = new ByteBuffer().writeUint32(0x12345678).flip();
var encoded = new ByteBuffer(6);
new Test(bb).encode(encoded);
test.equal(encoded.flip().toString("debug"), "<0A 04 12 34 56 78>");
encoded = encoded.slice(0, 5); // chop off the last byte
var err = null;
try {
Test.decode(encoded);
} catch (caught) {
err = caught;
}
test.ok(err && err.message && err.message.indexOf(": 4 required but got only 3") >= 0);
test.done();
},
"bool": function(test) {
try {
var builder = ProtoBuf.loadProto("message Test { optional bool ok = 1 [ default = false ]; }"),
Test = builder.build("Test"),
t = new Test();
test.strictEqual(t.ok, null); // Not set as it is optional
t.setOk(true);
test.strictEqual(t.ok, true);
test.strictEqual(Test.decode(t.encode()).ok, true);
t.setOk(false);
test.strictEqual(t.ok, false);
t.setOk(null); // Not set
test.strictEqual(Test.decode(t.encode()).ok, false); // = default when missing
} catch (err) {
fail(err);
}
test.done();
},
// As mentioned by Bill Katz
"T139": function(test) {
try{
var builder = ProtoBuf.loadProtoFile(__dirname+"/T139.proto");
var T139 = builder.build("T139");
test.ok(typeof T139 == 'function');
var inst = new T139(139,139);
test.equal(inst.a, 139);
test.equal(inst.b, 139);
inst.setA(139);
inst.setB(139);
test.equal(inst.a, 139);
test.equal(inst.b, 139);
var bb = new ByteBuffer(3);
inst.encode(bb);
test.equal(bb.flip().toString("debug"), "<08 8B 01 10 8B 01>");
var instDec = T139.decode(bb);
test.equal(instDec.a, 139);
test.equal(instDec.b, 139);
} catch (e) {
fail(e);
}
test.done();
},
"emptyDefaultString": function(test) {
try {
var builder = ProtoBuf.loadProto("message Test1 { required string test = 1 [default = \"\"]; }");
var Test1;
test.doesNotThrow(function() {
Test1 = builder.build("Test1");
});
var test1 = new Test1();
test.strictEqual(test1.test, "");
} catch (e) {
fail(e);
}
test.done();
},
"trailingSemicolon": function(test) {
try {
var builder = ProtoBuf.loadProto("message Test1 { optional string test = 1; };");
test.doesNotThrow(function() {
var Test1 = builder.build("Test1");
});
} catch (e) {
fail(e);
}
test.done();
},
"inner": {
"longstr": function(test) {
try {
var builder = ProtoBuf.loadProto("message Test { required Inner a = 1; message Inner { required string b = 1; } }");
var Test = builder.build("Test");
var t = new Test();
var data = "0123456789"; // 10: 20, 40, 80, 160, 320 bytes
for (var i=0; i<5; i++) data += data;
test.equal(data.length, 320);
t.a = new Test.Inner(data);
var bb = t.encode();
var t2 = Test.decode(bb);
test.equal(t2.a.b.length, 320);
test.equal(data, t2.a.b);
} catch (e) {
fail(e);
}
test.done();
},
"multiple": function(test) {
try {
var str = "";
for (var i=0; i<200; i++) str += 'a';
var builder = ProtoBuf.loadProtoFile(__dirname+"/inner.proto");
var fooCls = builder.build("Foo");
var barCls = builder.build("Bar");
var bazCls = builder.build("Baz");
var foo = new fooCls(new barCls(str), new bazCls(str));
var fooEncoded = foo.encode();
test.doesNotThrow(function() {
fooCls.decode(fooEncoded);
});
} catch (e) {
fail(e);
}
test.done();
},
"float": function(test) {
try {
var builder = ProtoBuf.loadProto("message Foo { required Bar bar = 1; } message Bar { required float baz = 1; }");
var root = builder.build();
var foo = new root.Foo(new root.Bar(4));
var bb = foo.encode();
var foo2 = root.Foo.decode(bb);
test.equal(foo.bar.baz, 4);
test.equal(foo2.bar.baz, foo.bar.baz);
} catch (e) {
fail(e);
}
test.done();
}
},
"truncated": function(test) {
try {
var builder = ProtoBuf.loadProto("message Test { required int32 a = 1; required int32 b = 2; }");
var Test = builder.build("Test");
var t = new Test(), bb = new ByteBuffer(2);
t.setA(1);
try {
bb = t.encode(bb).flip();
test.ok(false);
} catch (e) {
test.ok(e.encoded);
bb = e.encoded.flip();
test.equal(bb.toString("debug"), "<08 01>");
}
var t2;
try /* to decode truncated message */ {
t2 = Test.decode(bb);
test.ok(false); // ^ throws
} catch (e) {
// But still be able to access the rest
var t3 = e.decoded;
test.strictEqual(t3.a, 1);
test.strictEqual(t3.b, null);
}
test.strictEqual(t2, undefined);
} catch (e) {
fail(e);
}
test.done();
},
// Options on all levels
"options": {
"parse": function(test) {
try {
var parser = new ProtoBuf.DotProto.Parser(ProtoBuf.Util.fetch(__dirname+"/options.proto"));
var root = parser.parse();
test.equal(root["package"], "My");
test.strictEqual(root["options"]["(toplevel_1)"], 10);
test.equal(root["options"]["(toplevel_2)"], "Hello world!");
var opt = root["messages"][0]["fields"][0]["options"];
test.equal(opt["default"], "Max");
opt = root["messages"][0]["options"];
test.strictEqual(opt["(inmessage)"], "My.Test");
test.strictEqual(opt["(foo.my_option).bar"], false);
opt = root["messages"][0]["fields"][1]["options"];
test.strictEqual(opt["default"], "Shouldn't mix quotes");
opt = root["messages"][0]["fields"][2]["options"];
test.strictEqual(opt["default"], 'Shouldn"t mix quotes');
opt = root["messages"][0]["fields"][3]["options"];
test.strictEqual(opt["(foo_options).opt1"], 123);
test.strictEqual(opt["(foo_options).opt2"], "baz");
} catch (e) {
fail(e);
}
test.done();
},
"export": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/options.proto");
var My = builder.build("My");
test.deepEqual(My.$options, {
"(toplevel_1)": 10,
"(toplevel_2)": "Hello world!"
});
test.strictEqual(My.$options['(toplevel_1)'], 10);
test.deepEqual(My.Test.$options, {
"(inmessage)": "My.Test",
"(foo.my_option).bar": false
});
} catch (e) {
fail(e);
}
test.done();
}
},
// Comments
"comments": function(test) {
try {
var tn = new ProtoBuf.DotProto.Tokenizer(ProtoBuf.Util.fetch(__dirname+'/comments.proto'));
var token, tokens = [];
do {
token = tn.next();
tokens.push(token);
} while (token !== null);
test.deepEqual(tokens, ['message', 'TestC', '{', 'required', 'int32', 'a', '=', '1', ';', '}', null]);
} catch (e) {
fail(e);
}
test.done();
},
// A more or less complex proto with type references
"complexProto": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/complex.proto");
validateComplex(test, builder.build("Game"));
var TCars = builder.lookup("Game.Cars");
test.strictEqual(TCars.fqn(), ".Game.Cars");
} catch(e) {
fail(e);
}
test.done();
},
// The same created without calling upon the parser to do so
"complexJSON": function(test) {
try {
var builder = ProtoBuf.loadJsonFile(__dirname+"/complex.json");
validateComplex(test, builder.build("Game"));
} catch (e) {
fail(e);
}
test.done();
},
// Test error messages
"errorMessage": function(test) {
test.throws(function() {
var builder = ProtoBuf.loadJsonFile(__dirname+"/complex.json");
var Game = builder.build("Game");
var car = new Game.Cars.Car();
car.speed = "hello";
car.encode();
}, /Illegal value for speed/);
test.done();
},
// Builder reused to add definitions from multiple sources
"multiBuilder": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/example1.proto");
ProtoBuf.loadProtoFile(__dirname+"/example2.proto", builder);
var ns = builder.build();
test.ok(!!ns.Test1);
test.ok(!!ns.Test2);
} catch (e) {
fail(e);
}
test.done();
},
// Inner messages test
"inner": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/repeated.proto");
var root = builder.build(),
Outer = root.Outer,
Inner = root.Inner;
// Empty
var outer = new Outer();
var bb = new ByteBuffer(1).fill(0).flip();
outer.encode(bb);
test.equal(bb.flip().toString("debug"), "|00");
var douter = Outer.decode(bb);
test.ok(douter.inner instanceof Array);
test.equal(douter.inner.length, 0);
// Multiple
outer = new Outer({ inner: [new Inner(1), new Inner(2)] });
bb = new ByteBuffer(8);
var size = outer.calculate();
outer.encode(bb);
test.strictEqual(bb.offset, size);
test.equal(bb.flip().toString("debug"), "<0A 02 08 01 0A 02 08 02>");
douter = Outer.decode(bb);
test.ok(douter.inner instanceof Array);
test.equal(douter.inner.length, 2);
test.equal(douter.inner[0].inner_value, 1);
test.equal(douter.inner[1].inner_value, 2);
} catch (e) {
fail(e);
}
test.done();
},
// Packed vs. not packed repeated fields test
"packed": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/packed.proto");
var Message = builder.build("Message");
// Both empty
var message = new Message();
var bb = new ByteBuffer(1).fill(0).flip();
var size = message.calculate();
message.encode(bb);
test.strictEqual(bb.offset, size);
test.equal(bb.flip().toString("debug"), "|00");
message = Message.decode(bb);
test.ok(message.a instanceof Array);
test.equal(message.a.length, 0);
test.ok(message.b instanceof Array);
test.equal(message.b.length, 0);
// Both non-empty
message = new Message([1,2,3], [1,2,3]);
size = message.calculate();
message.encode(bb.resize(11));
test.strictEqual(bb.offset, size);
test.equal(bb.flip().toString("debug"), "<0A 03 01 02 03 10 01 10 02 10 03>");
message = Message.decode(bb);
test.ok(message.a instanceof Array);
test.equal(message.a.length, 3);
test.deepEqual(message.a, [1,2,3]);
test.ok(message.b instanceof Array);
test.equal(message.b.length, 3);
test.deepEqual(message.b, [1,2,3]);
} catch (e) {
fail(e);
}
test.done();
},
// Legacy groups test
"groups": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/groups.proto");
var root = builder.build();
var Outer = root.Outer;
var TOuter = builder.ns.getChild("Outer");
var TInner = TOuter.getChild("MyInner");
test.ok(TInner instanceof ProtoBuf.Reflect.Message);
test.strictEqual(TInner.isGroup, true);
var Tinner = TOuter.getChild("myinner");
test.ok(Tinner instanceof ProtoBuf.Reflect.Message.Field);
test.strictEqual(Tinner.id, 2);
test.deepEqual(Tinner.options, { "deprecated": true });
var Inner = root.Outer.MyInner;
var outer = new Outer("a", [new Inner("hello")], "b", new Inner("world"));
var bb = new ByteBuffer();
var size = outer.calculate();
outer.encode(bb);
test.strictEqual(bb.offset, size);
bb.flip().compact();
var wiredMsg = [
"0A", // 1|010 = id 1, wire type 2 (ldelim)
"01", // length 1
"61", // "a"
"13", // 10|011 = id 2, wire type 3 (start group)
"1A", // 11|010 = id 3, wire type 2 (ldelim)
"05", // length 5
"68 65 6C 6C 6F", // "hello"
"14", // 10|100 = id 2, wire type 4 (end group)
"22", // 100|010 = id 4, wire type 2 (ldelim)
"01", // length 1
"62", // "b"
"2B", // 101|011 = id 5, wire type = 3 (start group)
"1A", // 11|010 = id 3, wire type = 2 (ldelim)
"05", // length 5
"77 6F 72 6C 64", // "world"
"2C" // 101|100 = id 5, wire type = 4 (end group)
];
test.equal(bb.toString("debug"), "<" +wiredMsg.join(" ") + ">");
var douter = Outer.decode(bb);
test.strictEqual(douter.before, "a");
test.strictEqual(douter.myinner.length, 1);
test.strictEqual(douter.myinner[0].a, "hello");
test.strictEqual(douter.after, "b");
bb.offset = 0;
douter = root.OuterSparse.decode(bb);
test.strictEqual(bb.offset, bb.limit);
test.strictEqual(douter.before, "a");
test.strictEqual(douter.after, "b");
} catch (e) {
fail(e);
}
test.done();
},
"x64Fixed": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/x64.proto");
var Test = builder.build("Test");
var myTest = new Test();
test.ok(myTest.val instanceof ByteBuffer.Long);
test.equal(myTest.val.unsigned, false);
test.equal(myTest.val.toNumber(), -1);
test.ok(myTest.uval instanceof ByteBuffer.Long);
test.equal(myTest.uval.unsigned, true);
test.equal(myTest.uval.toNumber(), 1);
myTest.setVal(-2);
myTest.setUval(2);
var bb = new ByteBuffer(18); // 2x tag + 2x 64bit
var size = myTest.calculate();
myTest.encode(bb);
test.strictEqual(bb.offset, size);
test.equal(bb.flip().toString("debug"), "<09 FE FF FF FF FF FF FF FF 11 02 00 00 00 00 00 00 00>");
// ^ wireType=1, id=1 ^ wireType=1, id=2
myTest = Test.decode(bb);
test.ok(myTest.val instanceof ByteBuffer.Long);
test.equal(myTest.val.unsigned, false);
test.equal(myTest.val.toNumber(), -2);
test.ok(myTest.uval instanceof ByteBuffer.Long);
test.equal(myTest.uval.unsigned, true);
test.equal(myTest.uval.toNumber(), 2);
} catch (e) {
fail(e);
}
test.done();
},
"x64Varint": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/x64.proto");
var Test = builder.build("Test2");
var Test = builder.build("Test2");
var myTest = new Test();
test.ok(myTest.val instanceof ByteBuffer.Long);
test.equal(myTest.val.unsigned, false);
test.equal(myTest.val.toNumber(), -1);
test.ok(myTest.uval instanceof ByteBuffer.Long);
test.equal(myTest.uval.unsigned, true);
test.equal(myTest.uval.toNumber(), 1);
test.ok(myTest.sval instanceof ByteBuffer.Long);
test.equal(myTest.sval.unsigned, false);
test.equal(myTest.sval.toNumber(), -2);
myTest.setVal(-2);
myTest.setUval(2);
myTest.setSval(-3);
var bb = new ByteBuffer(3+10+2); // 3x tag + 1x varint 10byte + 2x varint 1byte
var size = myTest.calculate();
myTest.encode(bb);
test.strictEqual(bb.offset, size);
test.equal(bb.flip().toString("debug"), "<08 FE FF FF FF FF FF FF FF FF 01 10 02 18 05>");
// 08: wireType=0, id=1, 18: wireType=0, id=2, ?: wireType=0, id=3
myTest = Test.decode(bb);
test.ok(myTest.val instanceof ByteBuffer.Long);
test.equal(myTest.val.unsigned, false);
test.equal(myTest.val.toNumber(), -2);
test.ok(myTest.uval instanceof ByteBuffer.Long);
test.equal(myTest.uval.unsigned, true);
test.equal(myTest.uval.toNumber(), 2);
test.ok(myTest.sval instanceof ByteBuffer.Long);
test.equal(myTest.sval.unsigned, false);
test.equal(myTest.sval.toNumber(), -3);
} catch (e) {
fail(e);
}
test.done();
},
"keywords": function(test) {
try {
var builder = ProtoBuf.loadProto("message Reserved { optional string get = 1; }");
var My = builder.build();
var myTest = new My.Reserved("a");
test.doesNotThrow(function() {
myTest.encode();
});
} catch (e) {
fail(e);
}
test.done();
},
"imports": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/imports.proto");
var root = builder.build();
test.ok(!!root.Test1);
test.ok(!!root.Test2);
test.ok(!!root.My.Test3);
test.notEqual(root.Test2, root.My.Test2);
} catch (e) {
fail(e);
}
test.done();
},
"weakImports": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/imports-weak.proto");
var root = builder.build();
} catch (e) {
test.ok(e.message.indexOf("unresolvable type reference") >= 0);
test.done();
return;
}
var e = new Error("Weak import was imported.");
fail(e);
},
"importExtensions": function(test) {
var x = "package x; \
message Test { \
extensions 1 to 10; \
} \
extend Test { \
optional int32 first_val = 1; \
}";
var y = "package y; \
extend x.Test { \
optional int32 second_val = 2; \
}";
var builder = ProtoBuf.newBuilder();
ProtoBuf.loadProto(x, builder);
ProtoBuf.loadProto(y, builder);
var Test = builder.build('x.Test');
var inst = new Test();
test.strictEqual(inst[".x.first_val"], null);
test.strictEqual(inst[".y.second_val"], null);
test.done();
},
"toplevel": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/toplevel.proto");
var My = builder.build("My");
test.ok(!!My.MyEnum);
test.equal(My.MyEnum.ONE, 1);
test.equal(My.MyEnum.TWO, 2);
test.ok(!!My.Test);
var myTest = new My.Test();
test.equal(myTest.num, My.MyEnum.ONE);
} catch (e) {
fail(e);
}
test.done();
},
"importsToplevel": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/imports-toplevel.proto");
var My = builder.build("My");
test.ok(!!My.MyEnum);
test.equal(My.MyEnum1.ONE, 1);
test.equal(My.MyEnum1.TWO, 2);
test.ok(!!My.Test1);
var myTest = new My.Test1();
test.equal(myTest.num, My.MyEnum.ONE);
test.equal(myTest.num1, My.MyEnum1.ONE);
} catch (e) {
fail(e);
}
test.done();
},
"importDuplicate": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/import_a.proto");
test.doesNotThrow(function() {
ProtoBuf.loadProtoFile(__dirname+"/import_b.proto", builder);
});
var root = builder.build();
test.ok(root.A);
test.ok(root.B);
test.ok(root.Common);
} catch (e) {
fail(e);
}
test.done();
},
"importDuplicateDifferentBuilder": function(test) {
try {
var builderA = ProtoBuf.loadProtoFile(__dirname+"/import_a.proto");
var builderB;
test.doesNotThrow(function() {
builderB = ProtoBuf.loadProtoFile(__dirname+"/import_b.proto");
});
var rootA = builderA.build();
var rootB = builderB.build();
test.ok(rootA.A);
test.ok(rootB.B);
test.ok(rootA.Common);
test.ok(rootB.Common);
} catch (e) {
fail(e);
}
test.done();
},
"dupimport": function(test) {
try {
// Suppress logging result to stdout
fixture.capture(function() { return false;});
require(__dirname+"/../cli/pbjs.js").main(["node", "bin/pbjs", __dirname+"/dupimport/main.proto", "--quiet"]);
fixture.release();
} catch (e) {
fixture.release();
fail(e);
}
test.done();
},
"field_name_same_as_package": function(test) {
try {
fixture.capture(function() { return false;});
require(__dirname+"/../cli/pbjs.js").main(["node", "bin/pbjs", __dirname+"/field_name_same_as_package/main.proto", "--quiet"]);
fixture.release();
} catch (e) {
fixture.release();
fail(e);
}
test.done();
},
"importRoot": function(test) {
try {
var builder = ProtoBuf.loadProtoFile({
root: __dirname,
file: "importRoot/file1.proto"
});
var Test = builder.build("Test");
test.ok(new Test() instanceof ProtoBuf.Builder.Message);
} catch (e) {
fail(e);
}
test.done();
},
"extend": function(test) {
try {
var ast = new ProtoBuf.DotProto.Parser(fs.readFileSync(__dirname+"/extend.proto")).parse();
test.deepEqual(ast, { package: null,
messages:
[ { ref: 'google.protobuf.MessageOptions',
fields:
[ { rule: 'optional',
type: 'int32',
name: 'foo',
options: {},
id: 1001 } ] },
{ name: 'Foo',
fields: [],
enums: [],
messages: [],
options: {},
services: [],
oneofs: {},
extensions: [ [ 2, 536870911 ] ] },
{ ref: 'Foo',
fields:
[ { rule: 'optional',
type: 'string',
name: 'bar',
options: {},
id: 2 } ] },
{ name: 'Bar',
fields: [],
enums: [],
messages:
[ { name: 'Foo',
fields: [],
enums: [],
messages: [],
options: {},
services: [],
oneofs: {} },
{ ref: '.Foo',
fields: [ { rule: 'optional', type: 'Foo', name: 'foo', options: {}, id: 3 } ] } ],
options: {},
services: [],
oneofs: {} } ],
enums: [],
imports: [ 'google/protobuf/descriptor.proto' ],
options: {},
services: [] }
);
var builder = ProtoBuf.loadProtoFile(__dirname+"/extend.proto");
var TFoo = builder.lookup(".Foo"),
TBar = builder.lookup(".Bar"),
TBarFoo = builder.lookup(".Bar.Foo"),
fields = TFoo.getChildren(ProtoBuf.Reflect.Message.Field);
test.strictEqual(fields.length, 2);
test.strictEqual(fields[0].name, ".bar");
test.strictEqual(fields[0].id, 2);
test.strictEqual(fields[1].name, ".Bar.foo");
test.strictEqual(fields[1].id, 3);
test.deepEqual(TFoo.extensions, [[2, ProtoBuf.ID_MAX]]); // explicitly defined
test.strictEqual(TBar.extensions, undefined); // none defined
test.deepEqual(TBar.getChild("foo"), { builder: builder, parent: TBar, name: "foo", field: TFoo.getChild('.Bar.foo') });
test.strictEqual(TBar.getChildren(ProtoBuf.Reflect.Message.Field).length, 0);
var root = builder.build();
test.strictEqual(TFoo.getChild(".Bar.foo").resolvedType, TBarFoo); // .Bar.Foo, not .Foo
var foo = new root.Foo(),
bar = new root.Bar();
foo['.bar'] = "123";
foo['.Bar.foo'] = bar;
test.equal(foo.encode().compact().toString("debug"), "<12 03 31 32 33 1A 00>");
} catch (e) {
fail(e);
}
test.done();
},
// Custom options on all levels
// victorr (https://github.com/victorr)
"customOptions": function(test) {
try {
var parser = new ProtoBuf.DotProto.Parser(ProtoBuf.Util.fetch(__dirname+"/custom-options.proto"));
var root = parser.parse();
test.equal(root["options"]["(my_file_option)"], "Hello world!");
test.equal(root["messages"][7]["options"]["(my_message_option)"], 1234);
test.equal(root["messages"][7]["fields"][0]["options"]["(my_field_option)"], 4.5);
// test.equal(root["services"]["MyService"]["options"]["my_service_option"], "FOO");
// TODO: add tests for my_enum_option, my_enum_value_option
} catch (e) {
fail(e);
}
test.done();
},
"oneofs": function(test) {
try {
var builder = ProtoBuf.loadProtoFile(__dirname+"/oneof.proto"),
MyOneOf = builder.build("MyOneOf"),
TOneOf = builder.lookup(".MyOneOf");
test.ok(TOneOf.getChild("my_oneof"));
var myOneOf = new MyOneOf();
test.strictEqual(myOneOf.my_oneof, null);
myOneOf.set("id", 1);
test.strictEqual(myOneOf.my_oneof, "id");
myOneOf.set("name", "me");
test.strictEqual(myOneOf.my_oneof, "name");
test.strictEqual(myOneOf.id, null);
var bb = myOneOf.encode().compact();
test.strictEqual(bb.toString("debug"), "<12 02 6D 65>"); // id 2, wt 2, len 2
myOneOf = MyOneOf.decode(bb);
test.strictEqual(myOneOf.my_oneof, "name");
test.strictEqual(myOneOf.name, "me");
test.strictEqual(myOneOf.id, null);
} catch (e) {
fail(e);
}
test.done();
},
"services": function(test) {
try {
var parser = new ProtoBuf.DotProto.Parser(ProtoBuf.Util.fetch(__dirname+"/custom-options.proto"));
var root = parser.parse();
test.deepEqual(root["services"], [{
"name": "MyService",
"rpc": {
"MyMethod": {
"request": "RequestType",
"response": "ResponseType",
"request_stream": false,
"response_stream": false,
"options": {
"(my_method_option).foo": 567,
"(my_method_option).bar": "Some string"
}
}
},
"options": {
"(my_service_option)": "FOO"
}
}]);
var builder = ProtoBuf.loadProtoFile(__dirname+"/custom-options.proto");
var root = builder.build(),
MyService = root.MyService,
RequestType = root.RequestType,
ResponseType = root.ResponseType,
called = false;
test.deepEqual(MyService.$options, {
"(my_service_option)": "FOO"
});
test.deepEqual(MyService.MyMethod.$options, {
"(my_method_option).foo": 567,
"(my_method_option).bar": "Some string"
});