mirror of
https://github.com/hanzoai/protobuf.js.git
synced 2026-09-24 17:02:08 +00:00
255 lines
11 KiB
JavaScript
255 lines
11 KiB
JavaScript
var tape = require("tape");
|
|
|
|
var protobuf = require("..");
|
|
|
|
tape.test("uncommon statements", function(test) {
|
|
test.plan(3);
|
|
protobuf.load("tests/data/uncommon.proto", function(err, root) {
|
|
if (err || !root)
|
|
test.fail(err && err.message || "should parse without errors");
|
|
new protobuf.Root().load("tests/data/uncommon.proto", { keepCase: true }, function(err, root) {
|
|
if (err || !root) {
|
|
test.fail(err && err.message || "should parse without errors");
|
|
return;
|
|
}
|
|
test.pass("should parse without errors");
|
|
test.doesNotThrow(function() {
|
|
root.resolveAll();
|
|
}, "should resolve without errors");
|
|
test.doesNotThrow(function() {
|
|
traverseTypes(root, function(type) {
|
|
type.setup();
|
|
});
|
|
}, "should setup all types without errors");
|
|
test.end();
|
|
});
|
|
});
|
|
});
|
|
|
|
tape.test("numeric defaults", function(test) {
|
|
var Type = protobuf.parse("syntax = \"proto2\";\
|
|
message M {\
|
|
optional float float_value = 1 [default = -0];\
|
|
optional double double_value = 2 [default = -0];\
|
|
optional double zero_value = 3 [default = 0];\
|
|
optional int32 int_value = 4 [default = -0];\
|
|
optional int32 hex_int_value = 5 [default = -0x0];\
|
|
}").root.lookupType("M");
|
|
|
|
Type.resolveAll();
|
|
test.ok(Object.is(Type.fields.floatValue.defaultValue, -0), "should preserve a negative zero float default");
|
|
test.ok(Object.is(Type.fields.doubleValue.defaultValue, -0), "should preserve a negative zero double default");
|
|
test.ok(Object.is(Type.fields.zeroValue.defaultValue, 0), "should preserve a positive zero default");
|
|
test.ok(Object.is(Type.fields.intValue.defaultValue, 0), "should normalize a negative integer zero default");
|
|
test.ok(Object.is(Type.fields.hexIntValue.defaultValue, 0), "should normalize a negative hexadecimal zero default");
|
|
var Enum = protobuf.parse("syntax = \"proto3\"; enum E { ZERO = -0; }").root.lookupEnum("E");
|
|
test.ok(Object.is(Enum.values.ZERO, 0), "should normalize a negative zero enum value");
|
|
test.throws(function() {
|
|
protobuf.parse("syntax = \"proto2\"; message M { optional uint32 value = 1 [default = -0]; }");
|
|
}, /illegal integer '-0'/, "should reject a negative unsigned default");
|
|
test.throws(function() {
|
|
protobuf.parse("syntax = \"proto2\"; message M { optional int32 value = 1 [default = 1.5]; }");
|
|
}, /illegal integer '1\.5'/, "should reject a non-integer default");
|
|
test.end();
|
|
});
|
|
|
|
tape.test("negative enum reserved values", function(test) {
|
|
test.doesNotThrow(function() {
|
|
var Enum = protobuf.parse("syntax = \"proto3\"; enum Values { reserved -1; INVALID = 0; OK = 1; }").root.lookupEnum("Values");
|
|
test.same(Enum.reserved, [[-1, -1]], "should parse negative singleton enum reserved value");
|
|
}, "should parse negative enum reserved values");
|
|
|
|
test.throws(function() {
|
|
protobuf.parse("syntax = \"proto3\"; enum Values { reserved -2 to -1; INVALID = 0; RESERVED = -1; }");
|
|
}, /id -1 is reserved/, "should reject negative reserved enum range end ids");
|
|
|
|
test.throws(function() {
|
|
protobuf.parse("syntax = \"proto3\"; enum Values { reserved 1 to max; INVALID = 0; RESERVED = 2147483647; }");
|
|
}, /id 2147483647 is reserved/, "should use int32 max for enum reserved max");
|
|
|
|
test.throws(function() {
|
|
protobuf.parse("syntax = \"proto3\"; enum Values { reserved -1; INVALID = 0; RESERVED = -1; }");
|
|
}, /id -1 is reserved/, "should reject negative reserved enum value ids");
|
|
|
|
test.throws(function() {
|
|
protobuf.parse("syntax = \"proto3\"; message M { reserved -1; int32 value = 1; }");
|
|
}, /illegal id '-1'/, "should still reject negative message reserved field ids");
|
|
|
|
test.end();
|
|
});
|
|
|
|
tape.test("enum declarations are order-independent", function(test) {
|
|
var Enum = protobuf.parse("syntax = \"proto3\"; enum Values { INVALID = 0; UNKNOWN = 0; option allow_alias = true; OK = 1; }").root.lookupEnum("Values");
|
|
test.same(Object.keys(Enum.values).map(function(name) {
|
|
return [name, Enum.values[name]];
|
|
}), [
|
|
["INVALID", 0],
|
|
["UNKNOWN", 0],
|
|
["OK", 1]
|
|
], "should allow aliases declared before allow_alias");
|
|
|
|
test.throws(function() {
|
|
protobuf.parse("syntax = \"proto3\"; enum Values { INVALID = 0; UNKNOWN = 0; option allow_alias = false; }");
|
|
}, /duplicate id 0/, "should reject aliases when allow_alias is false");
|
|
|
|
test.throws(function() {
|
|
protobuf.parse("syntax = \"proto3\"; enum Values { INVALID = 0; RESERVED = 1; reserved 1; }");
|
|
}, /id 1 is reserved/, "should apply reserved declarations to preceding values");
|
|
|
|
test.end();
|
|
});
|
|
|
|
function traverseTypes(current, fn) {
|
|
if (current instanceof protobuf.Type) // and/or protobuf.Enum, protobuf.Service etc.
|
|
fn(current);
|
|
if (current.nestedArray)
|
|
current.nestedArray.forEach(function(nested) {
|
|
traverseTypes(nested, fn);
|
|
});
|
|
}
|
|
|
|
tape.test("invalid lookup", async function(test) {
|
|
try {
|
|
await protobuf.load("tests/data/invalid-lookup.proto");
|
|
test.fail("should have thrown");
|
|
} catch(err) {
|
|
test.match(err.message, /illegal token 'required'/, "failed to parse");
|
|
}
|
|
});
|
|
|
|
tape.test("parser nesting", function(test) {
|
|
function nestedMessages(depth) {
|
|
var source = "";
|
|
for (var i = 0; i < depth; ++i)
|
|
source += "message M" + i + " {";
|
|
for (i = 0; i < depth; ++i)
|
|
source += "}";
|
|
return source;
|
|
}
|
|
|
|
function nestedGroups(depth) {
|
|
var source = "syntax = \"proto2\"; message Root {";
|
|
for (var i = 0; i < depth; ++i)
|
|
source += "optional group Group" + i + " = " + (i + 1) + " {";
|
|
for (i = 0; i < depth; ++i)
|
|
source += "}";
|
|
return source + "}";
|
|
}
|
|
|
|
function nestedExportMessages(depth) {
|
|
var source = "edition = \"2024\";";
|
|
for (var i = 0; i < depth; ++i)
|
|
source += "export message M" + i + " {";
|
|
for (i = 0; i < depth; ++i)
|
|
source += "}";
|
|
return source;
|
|
}
|
|
|
|
function nestedOptionValue(depth) {
|
|
var source = "message M { option foo = {";
|
|
for (var i = 0; i < depth; ++i)
|
|
source += "a {";
|
|
for (i = 0; i < depth; ++i)
|
|
source += "}";
|
|
return source + "}; }";
|
|
}
|
|
|
|
function dottedOptionPath(depth) {
|
|
var source = "syntax = \"proto2\"; message M { optional int32 a = 1 [(.foo)";
|
|
for (var i = 0; i < depth; ++i)
|
|
source += ".a";
|
|
return source + " = 1]; }";
|
|
}
|
|
|
|
function packagePath(depth) {
|
|
var source = "syntax = \"proto3\"; package a";
|
|
for (var i = 1; i < depth; ++i)
|
|
source += ".a";
|
|
return source + ";";
|
|
}
|
|
|
|
var recursionLimit = protobuf.util.recursionLimit,
|
|
nestingLimit = protobuf.util.nestingLimit;
|
|
try {
|
|
protobuf.util.recursionLimit = 100;
|
|
protobuf.util.nestingLimit = 3;
|
|
test.doesNotThrow(function() {
|
|
protobuf.parse(nestedMessages(3));
|
|
}, "should parse message nesting up to the nesting limit");
|
|
test.throws(function() {
|
|
protobuf.parse(nestedMessages(4));
|
|
}, /max depth exceeded/, "should reject excessively nested messages");
|
|
protobuf.util.recursionLimit = 1;
|
|
test.doesNotThrow(function() {
|
|
protobuf.parse(nestedMessages(3));
|
|
}, "should not apply recursion limit to message declarations");
|
|
protobuf.util.recursionLimit = 100;
|
|
test.doesNotThrow(function() {
|
|
protobuf.parse(nestedGroups(2));
|
|
}, "should parse group nesting up to the nesting limit");
|
|
test.throws(function() {
|
|
protobuf.parse(nestedGroups(3));
|
|
}, /max depth exceeded/, "should reject excessively nested groups");
|
|
test.doesNotThrow(function() {
|
|
protobuf.parse(nestedExportMessages(3));
|
|
}, "should parse exported message nesting up to the nesting limit");
|
|
test.throws(function() {
|
|
protobuf.parse(nestedExportMessages(4));
|
|
}, /max depth exceeded/, "should reject excessively nested exported messages");
|
|
|
|
protobuf.util.recursionLimit = 3;
|
|
protobuf.util.nestingLimit = 100;
|
|
test.doesNotThrow(function() {
|
|
protobuf.parse(nestedOptionValue(3));
|
|
}, "should parse aggregate option nesting up to the recursion limit");
|
|
test.throws(function() {
|
|
protobuf.parse(nestedOptionValue(4));
|
|
}, /max depth exceeded/, "should reject excessively nested aggregate options");
|
|
test.doesNotThrow(function() {
|
|
protobuf.parse(dottedOptionPath(3));
|
|
}, "should parse dotted option paths up to the recursion limit");
|
|
test.throws(function() {
|
|
protobuf.parse(dottedOptionPath(4));
|
|
}, /max depth exceeded/, "should reject excessively nested dotted option paths");
|
|
test.doesNotThrow(function() {
|
|
protobuf.parse(packagePath(3));
|
|
}, "should parse package paths up to the recursion limit");
|
|
test.throws(function() {
|
|
protobuf.parse(packagePath(4));
|
|
}, /max depth exceeded/, "should reject excessively nested package paths");
|
|
} finally {
|
|
protobuf.util.recursionLimit = recursionLimit;
|
|
protobuf.util.nestingLimit = nestingLimit;
|
|
}
|
|
|
|
test.end();
|
|
});
|
|
|
|
tape.test("EOF / truncated input", function(test) {
|
|
function throwsIllegalNotTypeError(source, message) {
|
|
test.throws(function() {
|
|
try {
|
|
protobuf.parse(source);
|
|
} catch (err) {
|
|
test.notEqual(err && err.name, "TypeError", message + " should not throw TypeError");
|
|
throw err;
|
|
}
|
|
}, /illegal/, message);
|
|
}
|
|
|
|
throwsIllegalNotTypeError("message M { repeated int32 f = ", "should reject missing field id");
|
|
throwsIllegalNotTypeError("message M { repeated ", "should reject missing field type");
|
|
throwsIllegalNotTypeError("message M { repeated int32", "should reject missing field name");
|
|
throwsIllegalNotTypeError("message ", "should reject missing message type name");
|
|
throwsIllegalNotTypeError("message M { oneof ", "should reject missing oneof name");
|
|
throwsIllegalNotTypeError("message M { optional group ", "should reject missing group name");
|
|
throwsIllegalNotTypeError("message M { map<string, Foo> ", "should reject missing map field name");
|
|
throwsIllegalNotTypeError("enum ", "should reject missing enum name");
|
|
throwsIllegalNotTypeError("service ", "should reject missing service name");
|
|
throwsIllegalNotTypeError("extend ", "should reject missing extend reference");
|
|
throwsIllegalNotTypeError("package ", "should reject missing package name");
|
|
throwsIllegalNotTypeError("message M { reserved ", "should reject missing reserved range");
|
|
throwsIllegalNotTypeError("message M { optional Foo.", "should reject truncated dotted field type");
|
|
test.end();
|
|
});
|