OLD | NEW |
| (Empty) |
1 // Copyright (c) 2009 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 // This is a test harness for running javascript tests in the browser. | |
6 // The only identifier exposed by this harness is WebGLTestHarnessModule. | |
7 // | |
8 // To use it make an HTML page with an iframe. Then call the harness like this | |
9 // | |
10 // function reportResults(type, msg, success) { | |
11 // ... | |
12 // return true; | |
13 // } | |
14 // | |
15 // var fileListURL = '00_test_list.txt'; | |
16 // var testHarness = new WebGLTestHarnessModule.TestHarness( | |
17 // iframe, | |
18 // fileListURL, | |
19 // reportResults); | |
20 // | |
21 // The harness will load the fileListURL and parse it for the URLs, one URL | |
22 // per line. URLs should be on the same domain and at the same folder level | |
23 // or below the main html file. If any URL ends in .txt it will be parsed | |
24 // as well so you can nest .txt files. URLs inside a .txt file should be | |
25 // relative to that text file. | |
26 // | |
27 // During startup, for each page found the reportFunction will be called with | |
28 // WebGLTestHarnessModule.TestHarness.reportType.ADD_PAGE and msg will be | |
29 // the URL of the test. | |
30 // | |
31 // Each test is required to call testHarness.reportResults. This is most easily | |
32 // accomplished by storing that value on the main window with | |
33 // | |
34 // window.webglTestHarness = testHarness | |
35 // | |
36 // and then adding these to functions to your tests. | |
37 // | |
38 // function reportTestResultsToHarness(success, msg) { | |
39 // if (window.parent.webglTestHarness) { | |
40 // window.parent.webglTestHarness.reportResults(success, msg); | |
41 // } | |
42 // } | |
43 // | |
44 // function notifyFinishedToHarness() { | |
45 // if (window.parent.webglTestHarness) { | |
46 // window.parent.webglTestHarness.notifyFinished(); | |
47 // } | |
48 // } | |
49 // | |
50 // This way your tests will still run without the harness and you can use | |
51 // any testing framework you want. | |
52 // | |
53 // Each test should call reportTestResultsToHarness with true for success if it | |
54 // succeeded and false if it fail followed and any message it wants to | |
55 // associate with the test. If your testing framework supports checking for | |
56 // timeout you can call it with success equal to undefined in that case. | |
57 // | |
58 // To run the tests, call testHarness.runTests(); | |
59 // | |
60 // For each test run, before the page is loaded the reportFunction will be | |
61 // called with WebGLTestHarnessModule.TestHarness.reportType.START_PAGE and msg | |
62 // will be the URL of the test. You may return false if you want the test to be | |
63 // skipped. | |
64 // | |
65 // For each test completed the reportFunction will be called with | |
66 // with WebGLTestHarnessModule.TestHarness.reportType.TEST_RESULT, | |
67 // success = true on success, false on failure, undefined on timeout | |
68 // and msg is any message the test choose to pass on. | |
69 // | |
70 // When all the tests on the page have finished your page must call | |
71 // notifyFinishedToHarness. If notifyFinishedToHarness is not called | |
72 // the harness will assume the test timed out. | |
73 // | |
74 // When all the tests on a page have finished OR the page as timed out the | |
75 // reportFunction will be called with | |
76 // WebGLTestHarnessModule.TestHarness.reportType.FINISH_PAGE | |
77 // where success = true if the page has completed or undefined if the page timed | |
78 // out. | |
79 // | |
80 // Finally, when all the tests have completed the reportFunction will be called | |
81 // with WebGLTestHarnessModule.TestHarness.reportType.FINISHED_ALL_TESTS. | |
82 // | |
83 | |
84 WebGLTestHarnessModule = function() { | |
85 | |
86 /** | |
87 * Wrapped logging function. | |
88 */ | |
89 var log = function(msg) { | |
90 if (window.console && window.console.log) { | |
91 window.console.log(msg); | |
92 } | |
93 }; | |
94 | |
95 /** | |
96 * Loads text from an external file. This function is synchronous. | |
97 * @param {string} url The url of the external file. | |
98 * @param {!function(bool, string): void} callback that is sent a bool for | |
99 * success and the string. | |
100 */ | |
101 var loadTextFileAsynchronous = function(url, callback) { | |
102 log ("loading: " + url); | |
103 var error = 'loadTextFileSynchronous failed to load url "' + url + '"'; | |
104 var request; | |
105 if (window.XMLHttpRequest) { | |
106 request = new XMLHttpRequest(); | |
107 if (request.overrideMimeType) { | |
108 request.overrideMimeType('text/plain'); | |
109 } | |
110 } else { | |
111 throw 'XMLHttpRequest is disabled'; | |
112 } | |
113 try { | |
114 request.open('GET', url, true); | |
115 request.onreadystatechange = function() { | |
116 if (request.readyState == 4) { | |
117 var text = ''; | |
118 // HTTP reports success with a 200 status. The file protocol reports | |
119 // success with zero. HTTP does not use zero as a status code (they | |
120 // start at 100). | |
121 // https://developer.mozilla.org/En/Using_XMLHttpRequest | |
122 var success = request.status == 200 || request.status == 0; | |
123 if (success) { | |
124 text = request.responseText; | |
125 } | |
126 log("loaded: " + url); | |
127 callback(success, text); | |
128 } | |
129 }; | |
130 request.send(null); | |
131 } catch (e) { | |
132 log("failed to load: " + url); | |
133 callback(false, ''); | |
134 } | |
135 }; | |
136 | |
137 var getFileList = function(url, callback) { | |
138 var files = []; | |
139 | |
140 var getFileListImpl = function(url, callback) { | |
141 var files = []; | |
142 if (url.substr(url.length - 4) == '.txt') { | |
143 loadTextFileAsynchronous(url, function() { | |
144 return function(success, text) { | |
145 if (!success) { | |
146 callback(false, ''); | |
147 return; | |
148 } | |
149 var lines = text.split('\n'); | |
150 var prefix = ''; | |
151 var lastSlash = url.lastIndexOf('/'); | |
152 if (lastSlash >= 0) { | |
153 prefix = url.substr(0, lastSlash + 1); | |
154 } | |
155 var fail = false; | |
156 var count = 1; | |
157 var index = 0; | |
158 for (var ii = 0; ii < lines.length; ++ii) { | |
159 var str = lines[ii].replace(/^\s\s*/, '').replace(/\s\s*$/, ''); | |
160 if (str.length > 4 && | |
161 str[0] != '#' && | |
162 str[0] != ";" && | |
163 str.substr(0, 2) != "//") { | |
164 new_url = prefix + str; | |
165 ++count; | |
166 getFileListImpl(new_url, function(index) { | |
167 return function(success, new_files) { | |
168 log("got files: " + new_files.length); | |
169 if (success) { | |
170 files[index] = new_files; | |
171 } | |
172 finish(success); | |
173 }; | |
174 }(index++)); | |
175 } | |
176 } | |
177 finish(true); | |
178 | |
179 function finish(success) { | |
180 if (!success) { | |
181 fail = true; | |
182 } | |
183 --count; | |
184 log("count: " + count); | |
185 if (!count) { | |
186 callback(!fail, files); | |
187 } | |
188 } | |
189 } | |
190 }()); | |
191 | |
192 } else { | |
193 files.push(url); | |
194 callback(true, files); | |
195 } | |
196 }; | |
197 | |
198 getFileListImpl(url, function(success, files) { | |
199 // flatten | |
200 var flat = []; | |
201 flatten(files); | |
202 function flatten(files) { | |
203 for (var ii = 0; ii < files.length; ++ii) { | |
204 var value = files[ii]; | |
205 if (typeof(value) == "string") { | |
206 flat.push(value); | |
207 } else { | |
208 flatten(value); | |
209 } | |
210 } | |
211 } | |
212 callback(success, flat); | |
213 }); | |
214 }; | |
215 | |
216 var TestFile = function(url) { | |
217 this.url = url; | |
218 }; | |
219 | |
220 var TestHarness = function(iframe, filelistUrl, reportFunc) { | |
221 this.window = window; | |
222 this.iframe = iframe; | |
223 this.reportFunc = reportFunc; | |
224 this.timeoutDelay = 20000; | |
225 this.files = []; | |
226 | |
227 var that = this; | |
228 getFileList(filelistUrl, function() { | |
229 return function(success, files) { | |
230 that.addFiles_(success, files); | |
231 }; | |
232 }()); | |
233 | |
234 }; | |
235 | |
236 TestHarness.reportType = { | |
237 ADD_PAGE: 1, | |
238 READY: 2, | |
239 START_PAGE: 3, | |
240 TEST_RESULT: 4, | |
241 FINISH_PAGE: 5, | |
242 FINISHED_ALL_TESTS: 6 | |
243 }; | |
244 | |
245 TestHarness.prototype.addFiles_ = function(success, files) { | |
246 if (!success) { | |
247 this.reportFunc( | |
248 TestHarness.reportType.FINISHED_ALL_TESTS, | |
249 'Unable to load tests. Are you running locally?\n' + | |
250 'You need to run from a server or configure your\n' + | |
251 'browser to allow access to local files (not recommended).\n\n' + | |
252 'Note: An easy way to run from a server:\n\n' + | |
253 '\tcd path_to_tests\n' + | |
254 '\tpython -m SimpleHTTPServer\n\n' + | |
255 'then point your browser to ' + | |
256 '<a href="http://localhost:8000/webgl-conformance-tests.html">' + | |
257 'http://localhost:8000/webgl-conformance-tests.html</a>', | |
258 false) | |
259 return; | |
260 } | |
261 log("total files: " + files.length); | |
262 for (var ii = 0; ii < files.length; ++ii) { | |
263 log("" + ii + ": " + files[ii]); | |
264 this.files.push(new TestFile(files[ii])); | |
265 this.reportFunc(TestHarness.reportType.ADD_PAGE, files[ii], undefined); | |
266 } | |
267 this.reportFunc(TestHarness.reportType.READY, undefined, undefined); | |
268 this.nextFileIndex = files.length; | |
269 this.lastFileIndex = files.length; | |
270 } | |
271 | |
272 TestHarness.prototype.runTests = function(opt_start, opt_count) { | |
273 var count = opt_count || this.files.length; | |
274 this.nextFileIndex = opt_start || 0; | |
275 this.lastFileIndex = this.nextFileIndex + count; | |
276 this.startNextFile(); | |
277 }; | |
278 | |
279 TestHarness.prototype.setTimeout = function() { | |
280 var that = this; | |
281 this.timeoutId = this.window.setTimeout(function() { | |
282 that.timeout(); | |
283 }, this.timeoutDelay); | |
284 }; | |
285 | |
286 TestHarness.prototype.clearTimeout = function() { | |
287 this.window.clearTimeout(this.timeoutId); | |
288 }; | |
289 | |
290 TestHarness.prototype.startNextFile = function() { | |
291 if (this.nextFileIndex >= this.lastFileIndex) { | |
292 log("done"); | |
293 this.reportFunc(TestHarness.reportType.FINISHED_ALL_TESTS, | |
294 '', true); | |
295 } else { | |
296 this.currentFile = this.files[this.nextFileIndex++]; | |
297 log("loading: " + this.currentFile.url); | |
298 if (this.reportFunc(TestHarness.reportType.START_PAGE, | |
299 this.currentFile.url, undefined)) { | |
300 this.iframe.src = this.currentFile.url; | |
301 this.setTimeout(); | |
302 } else { | |
303 this.reportResults(false, "skipped"); | |
304 this.notifyFinished(); | |
305 } | |
306 } | |
307 }; | |
308 | |
309 TestHarness.prototype.reportResults = function (success, msg) { | |
310 this.clearTimeout(); | |
311 log(success ? "PASS" : "FAIL", msg); | |
312 this.reportFunc(TestHarness.reportType.TEST_RESULT, msg, success); | |
313 // For each result we get, reset the timeout | |
314 this.setTimeout(); | |
315 }; | |
316 | |
317 TestHarness.prototype.notifyFinished = function () { | |
318 this.clearTimeout(); | |
319 var url = this.currentFile ? this.currentFile.url : 'unknown'; | |
320 log(url + ": finished"); | |
321 this.reportFunc(TestHarness.reportType.FINISH_PAGE, url, true); | |
322 this.startNextFile(); | |
323 }; | |
324 | |
325 TestHarness.prototype.timeout = function() { | |
326 this.clearTimeout(); | |
327 var url = this.currentFile ? this.currentFile.url : 'unknown'; | |
328 log(url + ": timeout"); | |
329 this.reportFunc(TestHarness.reportType.FINISH_PAGE, url, undefined); | |
330 this.startNextFile(); | |
331 }; | |
332 | |
333 TestHarness.prototype.setTimeoutDelay = function(x) { | |
334 this.timeoutDelay = x; | |
335 }; | |
336 | |
337 return { | |
338 'TestHarness': TestHarness | |
339 }; | |
340 | |
341 }(); | |
342 | |
343 | |
344 | |
OLD | NEW |