1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 define([ 6 "console", 7 "file", 8 "gin/test/expect", 9 "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom", 10 "mojo/public/js/buffer", 11 "mojo/public/js/codec", 12 "mojo/public/js/connection", 13 "mojo/public/js/connector", 14 "mojo/public/js/core", 15 "mojo/public/js/test/validation_test_input_parser", 16 "mojo/public/js/router", 17 "mojo/public/js/validator", 18 ], function(console, 19 file, 20 expect, 21 testInterface, 22 buffer, 23 codec, 24 connection, 25 connector, 26 core, 27 parser, 28 router, 29 validator) { 30 31 var noError = validator.validationError.NONE; 32 33 function checkTestMessageParser() { 34 function TestMessageParserFailure(message, input) { 35 this.message = message; 36 this.input = input; 37 } 38 39 TestMessageParserFailure.prototype.toString = function() { 40 return 'Error: ' + this.message + ' for "' + this.input + '"'; 41 } 42 43 function checkData(data, expectedData, input) { 44 if (data.byteLength != expectedData.byteLength) { 45 var s = "message length (" + data.byteLength + ") doesn't match " + 46 "expected length: " + expectedData.byteLength; 47 throw new TestMessageParserFailure(s, input); 48 } 49 50 for (var i = 0; i < data.byteLength; i++) { 51 if (data.getUint8(i) != expectedData.getUint8(i)) { 52 var s = 'message data mismatch at byte offset ' + i; 53 throw new TestMessageParserFailure(s, input); 54 } 55 } 56 } 57 58 function testFloatItems() { 59 var input = '[f]+.3e9 [d]-10.03'; 60 var msg = parser.parseTestMessage(input); 61 var expectedData = new buffer.Buffer(12); 62 expectedData.setFloat32(0, +.3e9); 63 expectedData.setFloat64(4, -10.03); 64 checkData(msg.buffer, expectedData, input); 65 } 66 67 function testUnsignedIntegerItems() { 68 var input = '[u1]0x10// hello world !! \n\r \t [u2]65535 \n' + 69 '[u4]65536 [u8]0xFFFFFFFFFFFFF 0 0Xff'; 70 var msg = parser.parseTestMessage(input); 71 var expectedData = new buffer.Buffer(17); 72 expectedData.setUint8(0, 0x10); 73 expectedData.setUint16(1, 65535); 74 expectedData.setUint32(3, 65536); 75 expectedData.setUint64(7, 0xFFFFFFFFFFFFF); 76 expectedData.setUint8(15, 0); 77 expectedData.setUint8(16, 0xff); 78 checkData(msg.buffer, expectedData, input); 79 } 80 81 function testSignedIntegerItems() { 82 var input = '[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40'; 83 var msg = parser.parseTestMessage(input); 84 var expectedData = new buffer.Buffer(15); 85 expectedData.setInt64(0, -0x800); 86 expectedData.setInt8(8, -128); 87 expectedData.setInt16(9, 0); 88 expectedData.setInt32(11, -40); 89 checkData(msg.buffer, expectedData, input); 90 } 91 92 function testByteItems() { 93 var input = '[b]00001011 [b]10000000 // hello world\n [b]00000000'; 94 var msg = parser.parseTestMessage(input); 95 var expectedData = new buffer.Buffer(3); 96 expectedData.setUint8(0, 11); 97 expectedData.setUint8(1, 128); 98 expectedData.setUint8(2, 0); 99 checkData(msg.buffer, expectedData, input); 100 } 101 102 function testAnchors() { 103 var input = '[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar'; 104 var msg = parser.parseTestMessage(input); 105 var expectedData = new buffer.Buffer(14); 106 expectedData.setUint32(0, 14); 107 expectedData.setUint8(4, 0); 108 expectedData.setUint64(5, 9); 109 expectedData.setUint8(13, 0); 110 checkData(msg.buffer, expectedData, input); 111 } 112 113 function testHandles() { 114 var input = '// This message has handles! \n[handles]50 [u8]2'; 115 var msg = parser.parseTestMessage(input); 116 var expectedData = new buffer.Buffer(8); 117 expectedData.setUint64(0, 2); 118 119 if (msg.handleCount != 50) { 120 var s = 'wrong handle count (' + msg.handleCount + ')'; 121 throw new TestMessageParserFailure(s, input); 122 } 123 checkData(msg.buffer, expectedData, input); 124 } 125 126 function testEmptyInput() { 127 var msg = parser.parseTestMessage(''); 128 if (msg.buffer.byteLength != 0) 129 throw new TestMessageParserFailure('expected empty message', ''); 130 } 131 132 function testBlankInput() { 133 var input = ' \t // hello world \n\r \t// the answer is 42 '; 134 var msg = parser.parseTestMessage(input); 135 if (msg.buffer.byteLength != 0) 136 throw new TestMessageParserFailure('expected empty message', input); 137 } 138 139 function testInvalidInput() { 140 function parserShouldFail(input) { 141 try { 142 parser.parseTestMessage(input); 143 } catch (e) { 144 if (e instanceof parser.InputError) 145 return; 146 throw new TestMessageParserFailure( 147 'unexpected exception ' + e.toString(), input); 148 } 149 throw new TestMessageParserFailure("didn't detect invalid input", file); 150 } 151 152 ['/ hello world', 153 '[u1]x', 154 '[u2]-1000', 155 '[u1]0x100', 156 '[s2]-0x8001', 157 '[b]1', 158 '[b]1111111k', 159 '[dist4]unmatched', 160 '[anchr]hello [dist8]hello', 161 '[dist4]a [dist4]a [anchr]a', 162 // '[dist4]a [anchr]a [dist4]a [anchr]a', 163 '0 [handles]50' 164 ].forEach(parserShouldFail); 165 } 166 167 try { 168 testFloatItems(); 169 testUnsignedIntegerItems(); 170 testSignedIntegerItems(); 171 testByteItems(); 172 testInvalidInput(); 173 testEmptyInput(); 174 testBlankInput(); 175 testHandles(); 176 testAnchors(); 177 } catch (e) { 178 return e.toString(); 179 } 180 return null; 181 } 182 183 function getMessageTestFiles(prefix) { 184 var sourceRoot = file.getSourceRootDirectory(); 185 expect(sourceRoot).not.toBeNull(); 186 187 var testDir = sourceRoot + 188 "/mojo/public/interfaces/bindings/tests/data/validation/"; 189 var testFiles = file.getFilesInDirectory(testDir); 190 expect(testFiles).not.toBeNull(); 191 expect(testFiles.length).toBeGreaterThan(0); 192 193 // The matching ".data" pathnames with the extension removed. 194 return testFiles.filter(function(s) { 195 return s.substr(-5) == ".data" && s.indexOf(prefix) == 0; 196 }).map(function(s) { 197 return testDir + s.slice(0, -5); 198 }); 199 } 200 201 function readTestMessage(filename) { 202 var contents = file.readFileToString(filename + ".data"); 203 expect(contents).not.toBeNull(); 204 return parser.parseTestMessage(contents); 205 } 206 207 function readTestExpected(filename) { 208 var contents = file.readFileToString(filename + ".expected"); 209 expect(contents).not.toBeNull(); 210 return contents.trim(); 211 } 212 213 function checkValidationResult(testFile, err) { 214 var actualResult = (err === noError) ? "PASS" : err; 215 var expectedResult = readTestExpected(testFile); 216 if (actualResult != expectedResult) 217 console.log("[Test message validation failed: " + testFile + " ]"); 218 expect(actualResult).toEqual(expectedResult); 219 } 220 221 function testMessageValidation(prefix, filters) { 222 var testFiles = getMessageTestFiles(prefix); 223 expect(testFiles.length).toBeGreaterThan(0); 224 225 for (var i = 0; i < testFiles.length; i++) { 226 // TODO(hansmuller) Temporarily skipping array pointer overflow tests 227 // because JS numbers are limited to 53 bits. 228 // TODO(yzshen) Skipping struct versioning tests (tests with "mthd11" 229 // in the name) because the feature is not supported in JS yet. 230 // TODO(yzshen) Skipping enum validation tests (tests with "enum" in the 231 // name) because the feature is not supported in JS yet. crbug.com/581390 232 // TODO(rudominer): Temporarily skipping 'no-such-method', 233 // 'invalid_request_flags', and 'invalid_response_flags' until additional 234 // logic in *RequestValidator and *ResponseValidator is ported from 235 // cpp to js. 236 if (testFiles[i].indexOf("overflow") != -1 || 237 testFiles[i].indexOf("mthd11") != -1 || 238 testFiles[i].indexOf("enum") != -1 || 239 testFiles[i].indexOf("no_such_method") != -1 || 240 testFiles[i].indexOf("invalid_request_flags") != -1 || 241 testFiles[i].indexOf("invalid_response_flags") != -1) { 242 console.log("[Skipping " + testFiles[i] + "]"); 243 continue; 244 } 245 246 var testMessage = readTestMessage(testFiles[i]); 247 var handles = new Array(testMessage.handleCount); 248 var message = new codec.Message(testMessage.buffer, handles); 249 var messageValidator = new validator.Validator(message); 250 251 var err = messageValidator.validateMessageHeader(); 252 for (var j = 0; err === noError && j < filters.length; ++j) 253 err = filters[j](messageValidator); 254 255 checkValidationResult(testFiles[i], err); 256 } 257 } 258 259 function testConformanceMessageValidation() { 260 testMessageValidation("conformance_", [ 261 testInterface.ConformanceTestInterface.validateRequest]); 262 } 263 264 function testBoundsCheckMessageValidation() { 265 testMessageValidation("boundscheck_", [ 266 testInterface.BoundsCheckTestInterface.validateRequest]); 267 } 268 269 function testResponseConformanceMessageValidation() { 270 testMessageValidation("resp_conformance_", [ 271 testInterface.ConformanceTestInterface.validateResponse]); 272 } 273 274 function testResponseBoundsCheckMessageValidation() { 275 testMessageValidation("resp_boundscheck_", [ 276 testInterface.BoundsCheckTestInterface.validateResponse]); 277 } 278 279 function testIntegratedMessageValidation(testFilesPattern, 280 localFactory, 281 remoteFactory) { 282 var testFiles = getMessageTestFiles(testFilesPattern); 283 expect(testFiles.length).toBeGreaterThan(0); 284 285 var testMessagePipe = core.createMessagePipe(); 286 expect(testMessagePipe.result).toBe(core.RESULT_OK); 287 var testConnection = new connection.TestConnection( 288 testMessagePipe.handle1, localFactory, remoteFactory); 289 290 for (var i = 0; i < testFiles.length; i++) { 291 var testMessage = readTestMessage(testFiles[i]); 292 var handles = new Array(testMessage.handleCount); 293 294 var writeMessageValue = core.writeMessage( 295 testMessagePipe.handle0, 296 new Uint8Array(testMessage.buffer.arrayBuffer), 297 new Array(testMessage.handleCount), 298 core.WRITE_MESSAGE_FLAG_NONE); 299 expect(writeMessageValue).toBe(core.RESULT_OK); 300 301 var validationError = noError; 302 testConnection.router_.validationErrorHandler = function(err) { 303 validationError = err; 304 } 305 306 testConnection.router_.connector_.waitForNextMessage(); 307 checkValidationResult(testFiles[i], validationError); 308 } 309 310 testConnection.close(); 311 expect(core.close(testMessagePipe.handle0)).toBe(core.RESULT_OK); 312 } 313 314 function testIntegratedMessageHeaderValidation() { 315 testIntegratedMessageValidation( 316 "integration_msghdr", 317 testInterface.IntegrationTestInterface.stubClass, 318 undefined); 319 testIntegratedMessageValidation( 320 "integration_msghdr", 321 undefined, 322 testInterface.IntegrationTestInterface.proxyClass); 323 } 324 325 function testIntegratedRequestMessageValidation() { 326 testIntegratedMessageValidation( 327 "integration_intf_rqst", 328 testInterface.IntegrationTestInterface.stubClass, 329 undefined); 330 } 331 332 function testIntegratedResponseMessageValidation() { 333 testIntegratedMessageValidation( 334 "integration_intf_resp", 335 undefined, 336 testInterface.IntegrationTestInterface.proxyClass); 337 } 338 339 expect(checkTestMessageParser()).toBeNull(); 340 testConformanceMessageValidation(); 341 testBoundsCheckMessageValidation(); 342 testResponseConformanceMessageValidation(); 343 testResponseBoundsCheckMessageValidation(); 344 testIntegratedMessageHeaderValidation(); 345 testIntegratedResponseMessageValidation(); 346 testIntegratedRequestMessageValidation(); 347 348 this.result = "PASS"; 349 }); 350