Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / test / base / js2gtest.js
blob8d0f7e734bb1eeb2a92629c2d24246998e86e7de
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.
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@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  */
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);
25 /**
26  * Full path to the test input file.
27  * @type {string}
28  */
29 var jsFile = arguments[1];
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];
38 /**
39  * Path to Closure library style deps.js file.
40  * @type {string?}
41  */
42 var depsFile = arguments[3];
44 /**
45  * Path to C++ file generation is outputting to.
46  * @type {string}
47  */
48 var outputFile = arguments[4];
50 /**
51  * Type of this test.
52  * @type {string} ('extension' | 'unit' | 'webui')
53  */
54 var testType = arguments[5];
55 if (testType != 'extension' &&
56     testType != 'unit' &&
57     testType != 'webui') {
58   print('Invalid test type: ' + testType);
59   quit(-1);
62 /**
63  * C++ gtest macro to use for TEST_F depending on |testType|.
64  * @type {string} ('TEST_F'|'IN_PROC_BROWSER_TEST_F')
65  */
66 var testF;
68 /**
69  * Keeps track of whether a typedef has been generated for each test
70  * fixture.
71  * @type {Object<string>}
72  */
73 var typedeffedCppFixtures = {};
75 /**
76  * Maintains a list of relative file paths to add to each gtest body
77  * for inclusion at runtime before running each JavaScript test.
78  * @type {Array<string>}
79  */
80 var genIncludes = [];
82 /**
83  * When true, add calls to set_preload_test_(fixture|name). This is needed when
84  * |testType| === 'webui' to send an injection message before the page loads,
85  * but is not required or supported by any other test type.
86  * @type {boolean}
87  */
88 var addSetPreloadInfo;
90 /**
91  * Whether cc headers need to be generated.
92  * @type {boolean}
93  */
94 var needGenHeader = true;
96 /**
97  * Helpful hint pointing back to the source js.
98  * @type {string}
99  */
100 var argHint = '// ' + this['arguments'].join(' ');
104  * Generates the header of the cc file to stdout.
105  * @param {string?} testFixture Name of test fixture.
106  */
107 function maybeGenHeader(testFixture) {
108   if (!needGenHeader)
109     return;
110   needGenHeader = false;
111   print('// GENERATED FILE');
112   print(argHint);
113   print('// PLEASE DO NOT HAND EDIT!');
114   print();
116   // Output some C++ headers based upon the |testType|.
117   //
118   // Currently supports:
119   // 'extension' - browser_tests harness, js2extension rule,
120   //               ExtensionJSBrowserTest superclass.
121   // 'unit' - unit_tests harness, js2unit rule, V8UnitTest superclass.
122   // 'webui' - browser_tests harness, js2webui rule, WebUIBrowserTest
123   // superclass.
124   if (testType === 'extension') {
125     print('#include "chrome/test/base/extension_js_browser_test.h"');
126     testing.Test.prototype.typedefCppFixture = 'ExtensionJSBrowserTest';
127     addSetPreloadInfo = false;
128     testF = 'IN_PROC_BROWSER_TEST_F';
129   } else if (testType === 'unit') {
130     print('#include "chrome/test/base/v8_unit_test.h"');
131     testing.Test.prototype.typedefCppFixture = 'V8UnitTest';
132     testF = 'TEST_F';
133     addSetPreloadInfo = false;
134   } else {
135     print('#include "chrome/test/base/web_ui_browser_test.h"');
136     testing.Test.prototype.typedefCppFixture = 'WebUIBrowserTest';
137     testF = 'IN_PROC_BROWSER_TEST_F';
138     addSetPreloadInfo = true;
139   }
140   print('#include "url/gurl.h"');
141   print('#include "testing/gtest/include/gtest/gtest.h"');
142   // Add includes specified by test fixture.
143   if (testFixture) {
144     if (this[testFixture].prototype.testGenCppIncludes)
145       this[testFixture].prototype.testGenCppIncludes();
146     if (this[testFixture].prototype.commandLineSwitches)
147       print('#include "base/command_line.h"');
148   }
149   print();
154  * @type {Array<{path: string, base: string>}
155  */
156 var pathStack = [];
160  * Convert the |includeFile| to paths appropriate for immediate
161  * inclusion (path) and runtime inclusion (base).
162  * @param {string} includeFile The file to include.
163  * @return {{path: string, base: string}} Object describing the paths
164  *     for |includeFile|. |path| is relative to cwd; |base| is relative to
165  *     source root.
166  */
167 function includeFileToPaths(includeFile) {
168   paths = pathStack[pathStack.length - 1];
169   return {
170     path: paths.path.replace(/[^\/\\]+$/, includeFile),
171     base: paths.base.replace(/[^\/\\]+$/, includeFile),
172   };
177  * Maps object names to the path to the file that provides them.
178  * Populated from the |depsFile| if any.
179  * @type {Object<string>}
180  */
181 var dependencyProvidesToPaths = {};
184  * Maps dependency path names to object names required by the file.
185  * Populated from the |depsFile| if any.
186  * @type {Object<Array<string>>}
187  */
188 var dependencyPathsToRequires = {};
190 if (depsFile) {
191   var goog = goog || {};
192   /**
193    * Called by the javascript in the deps file to add modules and their
194    * dependencies.
195    * @param {string} path Relative path to the file.
196    * @param Array<string> provides Objects provided by this file.
197    * @param Array<string> requires Objects required by this file.
198    */
199   goog.addDependency = function(path, provides, requires) {
200     provides.forEach(function(provide) {
201       dependencyProvidesToPaths[provide] = path;
202     });
203     dependencyPathsToRequires[path] = requires;
204   };
206   // Read and eval the deps file.  It should only contain goog.addDependency
207   // calls.
208   eval(read(depsFile));
212  * Resolves a list of libraries to an ordered list of paths to load by the
213  * generated C++.  The input should contain object names provided
214  * by the deps file.  Dependencies will be resolved and included in the
215  * correct order, meaning that the returned array may contain more entries
216  * than the input.
217  * @param {Array<string>} deps List of dependencies.
218  * @return {Array<string>} List of paths to load.
219  */
220 function resolveClosureModuleDeps(deps) {
221   if (!depsFile && deps.length > 0) {
222     print('Can\'t have closure dependencies without a deps file.');
223     quit(-1);
224   }
225   var resultPaths = [];
226   var addedPaths = {};
228   function addPath(path) {
229     addedPaths[path] = true;
230     resultPaths.push(path);
231   }
233   function resolveAndAppend(path) {
234     if (addedPaths[path]) {
235       return;
236     }
237     // Set before recursing to catch cycles.
238     addedPaths[path] = true;
239     dependencyPathsToRequires[path].forEach(function(require) {
240       var providingPath = dependencyProvidesToPaths[require];
241       if (!providingPath) {
242         print('Unknown object', require, 'required by', path);
243         quit(-1);
244       }
245       resolveAndAppend(providingPath);
246     });
247     resultPaths.push(path);
248   }
250   // Always add closure library's base.js if provided by deps.
251   var basePath = dependencyProvidesToPaths['goog'];
252   if (basePath) {
253     addPath(basePath);
254   }
256   deps.forEach(function(dep) {
257     var providingPath = dependencyProvidesToPaths[dep];
258     if (providingPath) {
259       resolveAndAppend(providingPath);
260     } else {
261       print('Unknown dependency:', dep);
262       quit(-1);
263     }
264   });
266   return resultPaths;
270  * Output |code| verbatim.
271  * @param {string} code The code to output.
272  */
273 function GEN(code) {
274   maybeGenHeader(null);
275   print(code);
279  * Outputs |commentEncodedCode|, converting comment to enclosed C++ code.
280  * @param {function} commentEncodedCode A function in the following format (note
281  * the space in '/ *' and '* /' should be removed to form a comment delimiter):
282  *    function() {/ *! my_cpp_code.DoSomething(); * /
283  *    Code between / *! and * / will be extracted and written to stdout.
284  */
285 function GEN_BLOCK(commentEncodedCode) {
286   var code = commentEncodedCode.toString().
287       replace(/^[^\/]+\/\*!?/, '').
288       replace(/\*\/[^\/]+$/, '').
289       replace(/^\n|\n$/, '').
290       replace(/\s+$/, '');
291   GEN(code);
295  * Generate includes for the current |jsFile| by including them
296  * immediately and at runtime.
297  * The paths must be relative to the directory of the current file.
298  * @param {Array<string>} includes Paths to JavaScript files to
299  *     include immediately and at runtime.
300  */
301 function GEN_INCLUDE(includes) {
302   for (var i = 0; i < includes.length; i++) {
303     var includePaths = includeFileToPaths(includes[i]);
304     var js = read(includePaths.path);
305     pathStack.push(includePaths);
306     ('global', eval)(js);
307     pathStack.pop();
308     genIncludes.push(includePaths.base);
309   }
313  * Generate gtest-style TEST_F definitions for C++ with a body that
314  * will invoke the |testBody| for |testFixture|.|testFunction|.
315  * @param {string} testFixture The name of this test's fixture.
316  * @param {string} testFunction The name of this test's function.
317  * @param {Function} testBody The function body to execute for this test.
318  */
319 function TEST_F(testFixture, testFunction, testBody) {
320   maybeGenHeader(testFixture);
321   var browsePreload = this[testFixture].prototype.browsePreload;
322   var browsePrintPreload = this[testFixture].prototype.browsePrintPreload;
323   var testGenPreamble = this[testFixture].prototype.testGenPreamble;
324   var testGenPostamble = this[testFixture].prototype.testGenPostamble;
325   var typedefCppFixture = this[testFixture].prototype.typedefCppFixture;
326   var isAsyncParam = testType === 'unit' ? '' :
327       this[testFixture].prototype.isAsync + ', ';
328   var testShouldFail = this[testFixture].prototype.testShouldFail;
329   var testPredicate = testShouldFail ? 'ASSERT_FALSE' : 'ASSERT_TRUE';
330   var extraLibraries = genIncludes.concat(
331       this[testFixture].prototype.extraLibraries.map(
332           function(includeFile) {
333             return includeFileToPaths(includeFile).base;
334           }),
335       resolveClosureModuleDeps(this[testFixture].prototype.closureModuleDeps));
337   if (typedefCppFixture && !(testFixture in typedeffedCppFixtures)) {
338     var switches = this[testFixture].prototype.commandLineSwitches;
339     if (!switches || !switches.length || typedefCppFixture == 'V8UnitTest') {
340       print('typedef ' + typedefCppFixture + ' ' + testFixture + ';');
341     } else {
342       // Make the testFixture a class inheriting from the base fixture.
343       print('class ' + testFixture + ' : public ' + typedefCppFixture + ' {');
344       print(' private:');
345       // Override SetUpCommandLine and add each switch.
346       print('  void');
347       print('  SetUpCommandLine(base::CommandLine* command_line) override {');
348       for (var i = 0; i < switches.length; i++) {
349         print('    command_line->AppendSwitchASCII(');
350         print('        "' + switches[i].switchName + '",');
351         print('        "' + (switches[i].switchValue || '') + '");');
352       }
353       print('  }');
354       print('};');
355     }
356     typedeffedCppFixtures[testFixture] = typedefCppFixture;
357   }
359   print(testF + '(' + testFixture + ', ' + testFunction + ') {');
360   for (var i = 0; i < extraLibraries.length; i++) {
361     print('  AddLibrary(base::FilePath(FILE_PATH_LITERAL("' +
362         extraLibraries[i].replace(/\\/g, '/') + '")));');
363   }
364   print('  AddLibrary(base::FilePath(FILE_PATH_LITERAL("' +
365       jsFileBase.replace(/\\/g, '/') + '")));');
366   if (addSetPreloadInfo) {
367     print('  set_preload_test_fixture("' + testFixture + '");');
368     print('  set_preload_test_name("' + testFunction + '");');
369   }
370   if (testGenPreamble)
371     testGenPreamble(testFixture, testFunction);
372   if (browsePreload)
373     print('  BrowsePreload(GURL("' + browsePreload + '"));');
374   if (browsePrintPreload) {
375     print('  BrowsePrintPreload(GURL(WebUITestDataPathToURL(\n' +
376           '      FILE_PATH_LITERAL("' + browsePrintPreload + '"))));');
377   }
378   print('  ' + testPredicate + '(RunJavascriptTestF(' + isAsyncParam +
379         '"' + testFixture + '", ' +
380         '"' + testFunction + '"));');
381   if (testGenPostamble)
382     testGenPostamble(testFixture, testFunction);
383   print('}');
384   print();
387 // Now that generation functions are defined, load in |jsFile|.
388 var js = read(jsFile);
389 pathStack.push({path: jsFile, base: jsFileBase});
390 eval(js);
391 pathStack.pop();