// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
define([
"console",
"file",
"gin/test/expect",
"mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom",
"mojo/public/js/buffer",
"mojo/public/js/codec",
"mojo/public/js/connection",
"mojo/public/js/connector",
"mojo/public/js/core",
"mojo/public/js/test/validation_test_input_parser",
"mojo/public/js/router",
"mojo/public/js/validator",
], function(console,
file,
expect,
testInterface,
buffer,
codec,
connection,
connector,
core,
parser,
router,
validator) {
var noError = validator.validationError.NONE;
function checkTestMessageParser() {
function TestMessageParserFailure(message, input) {
this.message = message;
this.input = input;
}
TestMessageParserFailure.prototype.toString = function() {
return 'Error: ' + this.message + ' for "' + this.input + '"';
}
function checkData(data, expectedData, input) {
if (data.byteLength != expectedData.byteLength) {
var s = "message length (" + data.byteLength + ") doesn't match " +
"expected length: " + expectedData.byteLength;
throw new TestMessageParserFailure(s, input);
}
for (var i = 0; i < data.byteLength; i++) {
if (data.getUint8(i) != expectedData.getUint8(i)) {
var s = 'message data mismatch at byte offset ' + i;
throw new TestMessageParserFailure(s, input);
}
}
}
function testFloatItems() {
var input = '[f]+.3e9 [d]-10.03';
var msg = parser.parseTestMessage(input);
var expectedData = new buffer.Buffer(12);
expectedData.setFloat32(0, +.3e9);
expectedData.setFloat64(4, -10.03);
checkData(msg.buffer, expectedData, input);
}
function testUnsignedIntegerItems() {
var input = '[u1]0x10// hello world !! \n\r \t [u2]65535 \n' +
'[u4]65536 [u8]0xFFFFFFFFFFFFF 0 0Xff';
var msg = parser.parseTestMessage(input);
var expectedData = new buffer.Buffer(17);
expectedData.setUint8(0, 0x10);
expectedData.setUint16(1, 65535);
expectedData.setUint32(3, 65536);
expectedData.setUint64(7, 0xFFFFFFFFFFFFF);
expectedData.setUint8(15, 0);
expectedData.setUint8(16, 0xff);
checkData(msg.buffer, expectedData, input);
}
function testSignedIntegerItems() {
var input = '[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40';
var msg = parser.parseTestMessage(input);
var expectedData = new buffer.Buffer(15);
expectedData.setInt64(0, -0x800);
expectedData.setInt8(8, -128);
expectedData.setInt16(9, 0);
expectedData.setInt32(11, -40);
checkData(msg.buffer, expectedData, input);
}
function testByteItems() {
var input = '[b]00001011 [b]10000000 // hello world\n [b]00000000';
var msg = parser.parseTestMessage(input);
var expectedData = new buffer.Buffer(3);
expectedData.setUint8(0, 11);
expectedData.setUint8(1, 128);
expectedData.setUint8(2, 0);
checkData(msg.buffer, expectedData, input);
}
function testAnchors() {
var input = '[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar';
var msg = parser.parseTestMessage(input);
var expectedData = new buffer.Buffer(14);
expectedData.setUint32(0, 14);
expectedData.setUint8(4, 0);
expectedData.setUint64(5, 9);
expectedData.setUint8(13, 0);
checkData(msg.buffer, expectedData, input);
}
function testHandles() {
var input = '// This message has handles! \n[handles]50 [u8]2';
var msg = parser.parseTestMessage(input);
var expectedData = new buffer.Buffer(8);
expectedData.setUint64(0, 2);
if (msg.handleCount != 50) {
var s = 'wrong handle count (' + msg.handleCount + ')';
throw new TestMessageParserFailure(s, input);
}
checkData(msg.buffer, expectedData, input);
}
function testEmptyInput() {
var msg = parser.parseTestMessage('');
if (msg.buffer.byteLength != 0)
throw new TestMessageParserFailure('expected empty message', '');
}
function testBlankInput() {
var input = ' \t // hello world \n\r \t// the answer is 42 ';
var msg = parser.parseTestMessage(input);
if (msg.buffer.byteLength != 0)
throw new TestMessageParserFailure('expected empty message', input);
}
function testInvalidInput() {
function parserShouldFail(input) {
try {
parser.parseTestMessage(input);
} catch (e) {
if (e instanceof parser.InputError)
return;
throw new TestMessageParserFailure(
'unexpected exception ' + e.toString(), input);
}
throw new TestMessageParserFailure("didn't detect invalid input", file);
}
['/ hello world',
'[u1]x',
'[u2]-1000',
'[u1]0x100',
'[s2]-0x8001',
'[b]1',
'[b]1111111k',
'[dist4]unmatched',
'[anchr]hello [dist8]hello',
'[dist4]a [dist4]a [anchr]a',
// '[dist4]a [anchr]a [dist4]a [anchr]a',
'0 [handles]50'
].forEach(parserShouldFail);
}
try {
testFloatItems();
testUnsignedIntegerItems();
testSignedIntegerItems();
testByteItems();
testInvalidInput();
testEmptyInput();
testBlankInput();
testHandles();
testAnchors();
} catch (e) {
return e.toString();
}
return null;
}
function getMessageTestFiles(prefix) {
var sourceRoot = file.getSourceRootDirectory();
expect(sourceRoot).not.toBeNull();
var testDir = sourceRoot +
"/mojo/public/interfaces/bindings/tests/data/validation/";
var testFiles = file.getFilesInDirectory(testDir);
expect(testFiles).not.toBeNull();
expect(testFiles.length).toBeGreaterThan(0);
// The matching ".data" pathnames with the extension removed.
return testFiles.filter(function(s) {
return s.substr(-5) == ".data" && s.indexOf(prefix) == 0;
}).map(function(s) {
return testDir + s.slice(0, -5);
});
}
function readTestMessage(filename) {
var contents = file.readFileToString(filename + ".data");
expect(contents).not.toBeNull();
return parser.parseTestMessage(contents);
}
function readTestExpected(filename) {
var contents = file.readFileToString(filename + ".expected");
expect(contents).not.toBeNull();
return contents.trim();
}
function checkValidationResult(testFile, err) {
var actualResult = (err === noError) ? "PASS" : err;
var expectedResult = readTestExpected(testFile);
if (actualResult != expectedResult)
console.log("[Test message validation failed: " + testFile + " ]");
expect(actualResult).toEqual(expectedResult);
}
function testMessageValidation(prefix, filters) {
var testFiles = getMessageTestFiles(prefix);
expect(testFiles.length).toBeGreaterThan(0);
for (var i = 0; i < testFiles.length; i++) {
// TODO(hansmuller) Temporarily skipping array pointer overflow tests
// because JS numbers are limited to 53 bits.
// TODO(yzshen) Skipping struct versioning tests (tests with "mthd11"
// in the name) because the feature is not supported in JS yet.
// TODO(yzshen) Skipping enum validation tests (tests with "enum" in the
// name) because the feature is not supported in JS yet. crbug.com/581390
// TODO(rudominer): Temporarily skipping 'no-such-method',
// 'invalid_request_flags', and 'invalid_response_flags' until additional
// logic in *RequestValidator and *ResponseValidator is ported from
// cpp to js.
if (testFiles[i].indexOf("overflow") != -1 ||
testFiles[i].indexOf("mthd11") != -1 ||
testFiles[i].indexOf("enum") != -1 ||
testFiles[i].indexOf("no_such_method") != -1 ||
testFiles[i].indexOf("invalid_request_flags") != -1 ||
testFiles[i].indexOf("invalid_response_flags") != -1) {
console.log("[Skipping " + testFiles[i] + "]");
continue;
}
var testMessage = readTestMessage(testFiles[i]);
var handles = new Array(testMessage.handleCount);
var message = new codec.Message(testMessage.buffer, handles);
var messageValidator = new validator.Validator(message);
var err = messageValidator.validateMessageHeader();
for (var j = 0; err === noError && j < filters.length; ++j)
err = filters[j](messageValidator);
checkValidationResult(testFiles[i], err);
}
}
function testConformanceMessageValidation() {
testMessageValidation("conformance_", [
testInterface.ConformanceTestInterface.validateRequest]);
}
function testBoundsCheckMessageValidation() {
testMessageValidation("boundscheck_", [
testInterface.BoundsCheckTestInterface.validateRequest]);
}
function testResponseConformanceMessageValidation() {
testMessageValidation("resp_conformance_", [
testInterface.ConformanceTestInterface.validateResponse]);
}
function testResponseBoundsCheckMessageValidation() {
testMessageValidation("resp_boundscheck_", [
testInterface.BoundsCheckTestInterface.validateResponse]);
}
function testIntegratedMessageValidation(testFilesPattern,
localFactory,
remoteFactory) {
var testFiles = getMessageTestFiles(testFilesPattern);
expect(testFiles.length).toBeGreaterThan(0);
var testMessagePipe = core.createMessagePipe();
expect(testMessagePipe.result).toBe(core.RESULT_OK);
var testConnection = new connection.TestConnection(
testMessagePipe.handle1, localFactory, remoteFactory);
for (var i = 0; i < testFiles.length; i++) {
var testMessage = readTestMessage(testFiles[i]);
var handles = new Array(testMessage.handleCount);
var writeMessageValue = core.writeMessage(
testMessagePipe.handle0,
new Uint8Array(testMessage.buffer.arrayBuffer),
new Array(testMessage.handleCount),
core.WRITE_MESSAGE_FLAG_NONE);
expect(writeMessageValue).toBe(core.RESULT_OK);
var validationError = noError;
testConnection.router_.validationErrorHandler = function(err) {
validationError = err;
}
testConnection.router_.connector_.waitForNextMessage();
checkValidationResult(testFiles[i], validationError);
}
testConnection.close();
expect(core.close(testMessagePipe.handle0)).toBe(core.RESULT_OK);
}
function testIntegratedMessageHeaderValidation() {
testIntegratedMessageValidation(
"integration_msghdr",
testInterface.IntegrationTestInterface.stubClass,
undefined);
testIntegratedMessageValidation(
"integration_msghdr",
undefined,
testInterface.IntegrationTestInterface.proxyClass);
}
function testIntegratedRequestMessageValidation() {
testIntegratedMessageValidation(
"integration_intf_rqst",
testInterface.IntegrationTestInterface.stubClass,
undefined);
}
function testIntegratedResponseMessageValidation() {
testIntegratedMessageValidation(
"integration_intf_resp",
undefined,
testInterface.IntegrationTestInterface.proxyClass);
}
expect(checkTestMessageParser()).toBeNull();
testConformanceMessageValidation();
testBoundsCheckMessageValidation();
testResponseConformanceMessageValidation();
testResponseBoundsCheckMessageValidation();
testIntegratedMessageHeaderValidation();
testIntegratedResponseMessageValidation();
testIntegratedRequestMessageValidation();
this.result = "PASS";
});