Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / test / base / js2gtest.js
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@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);