1 // Copyright (c) 2012 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 /** 6 * @fileoverview Generator script for creating gtest-style JavaScript 7 * tests for extensions, WebUI and unit tests. Generates C++ gtest wrappers 8 * which will invoke the appropriate JavaScript for each test. 9 * @author scr (a] chromium.org (Sheridan Rawlins) 10 * @see WebUI testing: http://goo.gl/ZWFXF 11 * @see gtest documentation: http://goo.gl/Ujj3H 12 * @see chrome/chrome_tests.gypi 13 * @see tools/gypv8sh.py 14 */ 15 16 // Arguments from rules in chrome_tests.gypi are passed in through 17 // python script gypv8sh.py. 18 if (arguments.length != 6) { 19 print('usage: ' + 20 arguments[0] + 21 ' path-to-testfile.js testfile.js path_to_deps.js output.cc test-type'); 22 quit(-1); 23 } 24 25 /** 26 * Full path to the test input file. 27 * @type {string} 28 */ 29 var jsFile = arguments[1]; 30 31 /** 32 * Relative path to the test input file appropriate for use in the 33 * C++ TestFixture's addLibrary method. 34 * @type {string} 35 */ 36 var jsFileBase = arguments[2]; 37 38 /** 39 * The cwd, as determined by the paths of |jsFile| and |jsFileBase|. 40 * This is usually relative to the root source directory and points to the 41 * directory where the GYP rule processing the js file lives. 42 */ 43 var jsDirBase = jsFileBase.replace(jsFile, ''); 44 45 /** 46 * Path to Closure library style deps.js file. 47 * @type {string?} 48 */ 49 var depsFile = arguments[3]; 50 51 /** 52 * Path to C++ file generation is outputting to. 53 * @type {string} 54 */ 55 var outputFile = arguments[4]; 56 57 /** 58 * Type of this test. 59 * @type {string} ('extension' | 'unit' | 'webui') 60 */ 61 var testType = arguments[5]; 62 if (testType != 'extension' && 63 testType != 'unit' && 64 testType != 'webui') { 65 print('Invalid test type: ' + testType); 66 quit(-1); 67 } 68 69 /** 70 * C++ gtest macro to use for TEST_F depending on |testType|. 71 * @type {string} ('TEST_F'|'IN_PROC_BROWSER_TEST_F') 72 */ 73 var testF; 74 75 /** 76 * Keeps track of whether a typedef has been generated for each test 77 * fixture. 78 * @type {Object.<string, string>} 79 */ 80 var typedeffedCppFixtures = {}; 81 82 /** 83 * Maintains a list of relative file paths to add to each gtest body 84 * for inclusion at runtime before running each JavaScript test. 85 * @type {Array.<string>} 86 */ 87 var genIncludes = []; 88 89 /** 90 * When true, add calls to set_preload_test_(fixture|name). This is needed when 91 * |testType| === 'webui' to send an injection message before the page loads, 92 * but is not required or supported by any other test type. 93 * @type {boolean} 94 */ 95 var addSetPreloadInfo; 96 97 /** 98 * Whether cc headers need to be generated. 99 * @type {boolean} 100 */ 101 var needGenHeader = true; 102 103 /** 104 * Helpful hint pointing back to the source js. 105 * @type {string} 106 */ 107 var argHint = '// ' + this['arguments'].join(' '); 108 109 110 /** 111 * Generates the header of the cc file to stdout. 112 * @param {string?} testFixture Name of test fixture. 113 */ 114 function maybeGenHeader(testFixture) { 115 if (!needGenHeader) 116 return; 117 needGenHeader = false; 118 print('// GENERATED FILE'); 119 print(argHint); 120 print('// PLEASE DO NOT HAND EDIT!'); 121 print(); 122 123 // Output some C++ headers based upon the |testType|. 124 // 125 // Currently supports: 126 // 'extension' - browser_tests harness, js2extension rule, 127 // ExtensionJSBrowserTest superclass. 128 // 'unit' - unit_tests harness, js2unit rule, V8UnitTest superclass. 129 // 'webui' - browser_tests harness, js2webui rule, WebUIBrowserTest 130 // superclass. 131 if (testType === 'extension') { 132 print('#include "chrome/test/base/extension_js_browser_test.h"'); 133 testing.Test.prototype.typedefCppFixture = 'ExtensionJSBrowserTest'; 134 addSetPreloadInfo = false; 135 testF = 'IN_PROC_BROWSER_TEST_F'; 136 } else if (testType === 'unit') { 137 print('#include "chrome/test/base/v8_unit_test.h"'); 138 testing.Test.prototype.typedefCppFixture = 'V8UnitTest'; 139 testF = 'TEST_F'; 140 addSetPreloadInfo = false; 141 } else { 142 print('#include "chrome/test/base/web_ui_browser_test.h"'); 143 testing.Test.prototype.typedefCppFixture = 'WebUIBrowserTest'; 144 testF = 'IN_PROC_BROWSER_TEST_F'; 145 addSetPreloadInfo = true; 146 } 147 print('#include "url/gurl.h"'); 148 print('#include "testing/gtest/include/gtest/gtest.h"'); 149 if (testFixture && this[testFixture].prototype.testGenCppIncludes) 150 this[testFixture].prototype.testGenCppIncludes(); 151 print(); 152 } 153 154 155 /** 156 * Convert the |includeFile| to paths appropriate for immediate 157 * inclusion (path) and runtime inclusion (base). 158 * @param {string} includeFile The file to include. 159 * @return {{path: string, base: string}} Object describing the paths 160 * for |includeFile|. |path| is relative to cwd; |base| is relative to 161 * source root. 162 */ 163 function includeFileToPaths(includeFile) { 164 if (includeFile.indexOf(jsDirBase) == 0) { 165 // The caller supplied a path relative to root source. 166 var relPath = includeFile.replace(jsDirBase, ''); 167 return { 168 path: relPath, 169 base: jsDirBase + relPath 170 }; 171 } 172 173 // The caller supplied a path relative to the input js file's directory (cwd). 174 return { 175 path: jsFile.replace(/[^\/\\]+$/, includeFile), 176 base: jsFileBase.replace(/[^\/\\]+$/, includeFile), 177 }; 178 } 179 180 181 /** 182 * Maps object names to the path to the file that provides them. 183 * Populated from the |depsFile| if any. 184 * @type {Object.<string, string>} 185 */ 186 var dependencyProvidesToPaths = {}; 187 188 /** 189 * Maps dependency path names to object names required by the file. 190 * Populated from the |depsFile| if any. 191 * @type {Object.<string, Array.<string>>} 192 */ 193 var dependencyPathsToRequires = {}; 194 195 if (depsFile) { 196 var goog = goog || {}; 197 /** 198 * Called by the javascript in the deps file to add modules and their 199 * dependencies. 200 * @param {string} path Relative path to the file. 201 * @param Array.<string> provides Objects provided by this file. 202 * @param Array.<string> requires Objects required by this file. 203 */ 204 goog.addDependency = function(path, provides, requires) { 205 provides.forEach(function(provide) { 206 dependencyProvidesToPaths[provide] = path; 207 }); 208 dependencyPathsToRequires[path] = requires; 209 }; 210 211 // Read and eval the deps file. It should only contain goog.addDependency 212 // calls. 213 eval(read(depsFile)); 214 } 215 216 /** 217 * Resolves a list of libraries to an ordered list of paths to load by the 218 * generated C++. The input should contain object names provided 219 * by the deps file. Dependencies will be resolved and included in the 220 * correct order, meaning that the returned array may contain more entries 221 * than the input. 222 * @param {Array.<string>} deps List of dependencies. 223 * @return {Array.<string>} List of paths to load. 224 */ 225 function resolveClosureModuleDeps(deps) { 226 if (!depsFile && deps.length > 0) { 227 print('Can\'t have closure dependencies without a deps file.'); 228 quit(-1); 229 } 230 var resultPaths = []; 231 var addedPaths = {}; 232 233 function addPath(path) { 234 addedPaths[path] = true; 235 resultPaths.push(path); 236 } 237 238 function resolveAndAppend(path) { 239 if (addedPaths[path]) { 240 return; 241 } 242 // Set before recursing to catch cycles. 243 addedPaths[path] = true; 244 dependencyPathsToRequires[path].forEach(function(require) { 245 var providingPath = dependencyProvidesToPaths[require]; 246 if (!providingPath) { 247 print('Unknown object', require, 'required by', path); 248 quit(-1); 249 } 250 resolveAndAppend(providingPath); 251 }); 252 resultPaths.push(path); 253 } 254 255 // Always add closure library's base.js if provided by deps. 256 var basePath = dependencyProvidesToPaths['goog']; 257 if (basePath) { 258 addPath(basePath); 259 } 260 261 deps.forEach(function(dep) { 262 var providingPath = dependencyProvidesToPaths[dep]; 263 if (providingPath) { 264 resolveAndAppend(providingPath); 265 } else { 266 print('Unknown dependency:', dep); 267 quit(-1); 268 } 269 }); 270 271 return resultPaths; 272 } 273 274 /** 275 * Output |code| verbatim. 276 * @param {string} code The code to output. 277 */ 278 function GEN(code) { 279 maybeGenHeader(null); 280 print(code); 281 } 282 283 /** 284 * Outputs |commentEncodedCode|, converting comment to enclosed C++ code. 285 * @param {function} commentEncodedCode A function in the following format (note 286 * the space in '/ *' and '* /' should be removed to form a comment delimiter): 287 * function() {/ *! my_cpp_code.DoSomething(); * / 288 * Code between / *! and * / will be extracted and written to stdout. 289 */ 290 function GEN_BLOCK(commentEncodedCode) { 291 var code = commentEncodedCode.toString(). 292 replace(/^[^\/]+\/\*!?/, ''). 293 replace(/\*\/[^\/]+$/, ''). 294 replace(/^\n|\n$/, ''). 295 replace(/\s+$/, ''); 296 GEN(code); 297 } 298 299 /** 300 * Generate includes for the current |jsFile| by including them 301 * immediately and at runtime. 302 * The paths are allowed to be: 303 * 1. relative to the root src directory (i.e. similar to #include's). 304 * 2. relative to the directory specified in the GYP rule for the file. 305 * @param {Array.<string>} includes Paths to JavaScript files to 306 * include immediately and at runtime. 307 */ 308 function GEN_INCLUDE(includes) { 309 for (var i = 0; i < includes.length; i++) { 310 var includePaths = includeFileToPaths(includes[i]); 311 var js = read(includePaths.path); 312 ('global', eval)(js); 313 genIncludes.push(includePaths.base); 314 } 315 } 316 317 /** 318 * Generate gtest-style TEST_F definitions for C++ with a body that 319 * will invoke the |testBody| for |testFixture|.|testFunction|. 320 * @param {string} testFixture The name of this test's fixture. 321 * @param {string} testFunction The name of this test's function. 322 * @param {Function} testBody The function body to execute for this test. 323 */ 324 function TEST_F(testFixture, testFunction, testBody) { 325 maybeGenHeader(testFixture); 326 var browsePreload = this[testFixture].prototype.browsePreload; 327 var browsePrintPreload = this[testFixture].prototype.browsePrintPreload; 328 var testGenPreamble = this[testFixture].prototype.testGenPreamble; 329 var testGenPostamble = this[testFixture].prototype.testGenPostamble; 330 var typedefCppFixture = this[testFixture].prototype.typedefCppFixture; 331 var isAsyncParam = testType === 'unit' ? '' : 332 this[testFixture].prototype.isAsync + ', '; 333 var testShouldFail = this[testFixture].prototype.testShouldFail; 334 var testPredicate = testShouldFail ? 'ASSERT_FALSE' : 'ASSERT_TRUE'; 335 var extraLibraries = genIncludes.concat( 336 this[testFixture].prototype.extraLibraries.map( 337 function(includeFile) { 338 return includeFileToPaths(includeFile).base; 339 }), 340 resolveClosureModuleDeps(this[testFixture].prototype.closureModuleDeps)); 341 342 if (typedefCppFixture && !(testFixture in typedeffedCppFixtures)) { 343 print('typedef ' + typedefCppFixture + ' ' + testFixture + ';'); 344 typedeffedCppFixtures[testFixture] = typedefCppFixture; 345 } 346 347 print(testF + '(' + testFixture + ', ' + testFunction + ') {'); 348 for (var i = 0; i < extraLibraries.length; i++) { 349 print(' AddLibrary(base::FilePath(FILE_PATH_LITERAL("' + 350 extraLibraries[i].replace(/\\/g, '/') + '")));'); 351 } 352 print(' AddLibrary(base::FilePath(FILE_PATH_LITERAL("' + 353 jsFileBase.replace(/\\/g, '/') + '")));'); 354 if (addSetPreloadInfo) { 355 print(' set_preload_test_fixture("' + testFixture + '");'); 356 print(' set_preload_test_name("' + testFunction + '");'); 357 } 358 if (testGenPreamble) 359 testGenPreamble(testFixture, testFunction); 360 if (browsePreload) 361 print(' BrowsePreload(GURL("' + browsePreload + '"));'); 362 if (browsePrintPreload) { 363 print(' BrowsePrintPreload(GURL(WebUITestDataPathToURL(\n' + 364 ' FILE_PATH_LITERAL("' + browsePrintPreload + '"))));'); 365 } 366 print(' ' + testPredicate + '(RunJavascriptTestF(' + isAsyncParam + 367 '"' + testFixture + '", ' + 368 '"' + testFunction + '"));'); 369 if (testGenPostamble) 370 testGenPostamble(testFixture, testFunction); 371 print('}'); 372 print(); 373 } 374 375 // Now that generation functions are defined, load in |jsFile|. 376 var js = read(jsFile); 377 eval(js); 378