// 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"; });