Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Side by Side Diff: third_party/WebKit/LayoutTests/external/wpt/resources/testharnessreport.js

Issue 2642393002: Import wpt@40665266227e475bc4a56884247d8c09d78dfb6a (Closed)
Patch Set: rebaseline-cl Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /* global add_completion_callback */
2 /* global setup */
3
4 /*
5 * This file is intended for vendors to implement
6 * code needed to integrate testharness.js tests with their own test systems.
7 *
8 * The default implementation extracts metadata from the tests and validates
9 * it against the cached version that should be present in the test source
10 * file. If the cache is not found or is out of sync, source code suitable for
11 * caching the metadata is optionally generated.
12 *
13 * The cached metadata is present for extraction by test processing tools that
14 * are unable to execute javascript.
15 *
16 * Metadata is attached to tests via the properties parameter in the test
17 * constructor. See testharness.js for details.
18 *
19 * Typically test system integration will attach callbacks when each test has
20 * run, using add_result_callback(callback(test)), or when the whole test file
21 * has completed, using
22 * add_completion_callback(callback(tests, harness_status)).
23 *
24 * For more documentation about the callback functions and the
25 * parameters they are called with see testharness.js
26 */
27
28 var metadata_generator = {
29
30 currentMetadata: {},
31 cachedMetadata: false,
32 metadataProperties: ['help', 'assert', 'author'],
33
34 error: function(message) {
35 var messageElement = document.createElement('p');
36 messageElement.setAttribute('class', 'error');
37 this.appendText(messageElement, message);
38
39 var summary = document.getElementById('summary');
40 if (summary) {
41 summary.parentNode.insertBefore(messageElement, summary);
42 }
43 else {
44 document.body.appendChild(messageElement);
45 }
46 },
47
48 /**
49 * Ensure property value has contact information
50 */
51 validateContact: function(test, propertyName) {
52 var result = true;
53 var value = test.properties[propertyName];
54 var values = Array.isArray(value) ? value : [value];
55 for (var index = 0; index < values.length; index++) {
56 value = values[index];
57 var re = /(\S+)(\s*)<(.*)>(.*)/;
58 if (! re.test(value)) {
59 re = /(\S+)(\s+)(http[s]?:\/\/)(.*)/;
60 if (! re.test(value)) {
61 this.error('Metadata property "' + propertyName +
62 '" for test: "' + test.name +
63 '" must have name and contact information ' +
64 '("name <email>" or "name http(s)://")');
65 result = false;
66 }
67 }
68 }
69 return result;
70 },
71
72 /**
73 * Extract metadata from test object
74 */
75 extractFromTest: function(test) {
76 var testMetadata = {};
77 // filter out metadata from other properties in test
78 for (var metaIndex = 0; metaIndex < this.metadataProperties.length;
79 metaIndex++) {
80 var meta = this.metadataProperties[metaIndex];
81 if (test.properties.hasOwnProperty(meta)) {
82 if ('author' == meta) {
83 this.validateContact(test, meta);
84 }
85 testMetadata[meta] = test.properties[meta];
86 }
87 }
88 return testMetadata;
89 },
90
91 /**
92 * Compare cached metadata to extracted metadata
93 */
94 validateCache: function() {
95 for (var testName in this.currentMetadata) {
96 if (! this.cachedMetadata.hasOwnProperty(testName)) {
97 return false;
98 }
99 var testMetadata = this.currentMetadata[testName];
100 var cachedTestMetadata = this.cachedMetadata[testName];
101 delete this.cachedMetadata[testName];
102
103 for (var metaIndex = 0; metaIndex < this.metadataProperties.length;
104 metaIndex++) {
105 var meta = this.metadataProperties[metaIndex];
106 if (cachedTestMetadata.hasOwnProperty(meta) &&
107 testMetadata.hasOwnProperty(meta)) {
108 if (Array.isArray(cachedTestMetadata[meta])) {
109 if (! Array.isArray(testMetadata[meta])) {
110 return false;
111 }
112 if (cachedTestMetadata[meta].length ==
113 testMetadata[meta].length) {
114 for (var index = 0;
115 index < cachedTestMetadata[meta].length;
116 index++) {
117 if (cachedTestMetadata[meta][index] !=
118 testMetadata[meta][index]) {
119 return false;
120 }
121 }
122 }
123 else {
124 return false;
125 }
126 }
127 else {
128 if (Array.isArray(testMetadata[meta])) {
129 return false;
130 }
131 if (cachedTestMetadata[meta] != testMetadata[meta]) {
132 return false;
133 }
134 }
135 }
136 else if (cachedTestMetadata.hasOwnProperty(meta) ||
137 testMetadata.hasOwnProperty(meta)) {
138 return false;
139 }
140 }
141 }
142 for (var testName in this.cachedMetadata) {
143 return false;
144 }
145 return true;
146 },
147
148 appendText: function(elemement, text) {
149 elemement.appendChild(document.createTextNode(text));
150 },
151
152 jsonifyArray: function(arrayValue, indent) {
153 var output = '[';
154
155 if (1 == arrayValue.length) {
156 output += JSON.stringify(arrayValue[0]);
157 }
158 else {
159 for (var index = 0; index < arrayValue.length; index++) {
160 if (0 < index) {
161 output += ',\n ' + indent;
162 }
163 output += JSON.stringify(arrayValue[index]);
164 }
165 }
166 output += ']';
167 return output;
168 },
169
170 jsonifyObject: function(objectValue, indent) {
171 var output = '{';
172 var value;
173
174 var count = 0;
175 for (var property in objectValue) {
176 ++count;
177 if (Array.isArray(objectValue[property]) ||
178 ('object' == typeof(value))) {
179 ++count;
180 }
181 }
182 if (1 == count) {
183 for (var property in objectValue) {
184 output += ' "' + property + '": ' +
185 JSON.stringify(objectValue[property]) +
186 ' ';
187 }
188 }
189 else {
190 var first = true;
191 for (var property in objectValue) {
192 if (! first) {
193 output += ',';
194 }
195 first = false;
196 output += '\n ' + indent + '"' + property + '": ';
197 value = objectValue[property];
198 if (Array.isArray(value)) {
199 output += this.jsonifyArray(value, indent +
200 ' '.substr(0, 5 + property.length));
201 }
202 else if ('object' == typeof(value)) {
203 output += this.jsonifyObject(value, indent + ' ');
204 }
205 else {
206 output += JSON.stringify(value);
207 }
208 }
209 if (1 < output.length) {
210 output += '\n' + indent;
211 }
212 }
213 output += '}';
214 return output;
215 },
216
217 /**
218 * Generate javascript source code for captured metadata
219 * Metadata is in pretty-printed JSON format
220 */
221 generateSource: function() {
222 /* "\/" is used instead of a plain forward slash so that the contents
223 of testharnessreport.js can (for convenience) be copy-pasted into a
224 script tag without issue. Otherwise, the HTML parser would think that
225 the script ended in the middle of that string literal. */
226 var source =
227 '<script id="metadata_cache">/*\n' +
228 this.jsonifyObject(this.currentMetadata, '') + '\n' +
229 '*/<\/script>\n';
230 return source;
231 },
232
233 /**
234 * Add element containing metadata source code
235 */
236 addSourceElement: function(event) {
237 var sourceWrapper = document.createElement('div');
238 sourceWrapper.setAttribute('id', 'metadata_source');
239
240 var instructions = document.createElement('p');
241 if (this.cachedMetadata) {
242 this.appendText(instructions,
243 'Replace the existing <script id="metadata_cache"> element ' +
244 'in the test\'s <head> with the following:');
245 }
246 else {
247 this.appendText(instructions,
248 'Copy the following into the <head> element of the test ' +
249 'or the test\'s metadata sidecar file:');
250 }
251 sourceWrapper.appendChild(instructions);
252
253 var sourceElement = document.createElement('pre');
254 this.appendText(sourceElement, this.generateSource());
255
256 sourceWrapper.appendChild(sourceElement);
257
258 var messageElement = document.getElementById('metadata_issue');
259 messageElement.parentNode.insertBefore(sourceWrapper,
260 messageElement.nextSibling);
261 messageElement.parentNode.removeChild(messageElement);
262
263 (event.preventDefault) ? event.preventDefault() :
264 event.returnValue = false;
265 },
266
267 /**
268 * Extract the metadata cache from the cache element if present
269 */
270 getCachedMetadata: function() {
271 var cacheElement = document.getElementById('metadata_cache');
272
273 if (cacheElement) {
274 var cacheText = cacheElement.firstChild.nodeValue;
275 var openBrace = cacheText.indexOf('{');
276 var closeBrace = cacheText.lastIndexOf('}');
277 if ((-1 < openBrace) && (-1 < closeBrace)) {
278 cacheText = cacheText.slice(openBrace, closeBrace + 1);
279 try {
280 this.cachedMetadata = JSON.parse(cacheText);
281 }
282 catch (exc) {
283 this.cachedMetadata = 'Invalid JSON in Cached metadata. ';
284 }
285 }
286 else {
287 this.cachedMetadata = 'Metadata not found in cache element. ';
288 }
289 }
290 },
291
292 /**
293 * Main entry point, extract metadata from tests, compare to cached version
294 * if present.
295 * If cache not present or differs from extrated metadata, generate an error
296 */
297 process: function(tests) {
298 for (var index = 0; index < tests.length; index++) {
299 var test = tests[index];
300 if (this.currentMetadata.hasOwnProperty(test.name)) {
301 this.error('Duplicate test name: ' + test.name);
302 }
303 else {
304 this.currentMetadata[test.name] = this.extractFromTest(test);
305 }
306 }
307
308 this.getCachedMetadata();
309
310 var message = null;
311 var messageClass = 'warning';
312 var showSource = false;
313
314 if (0 === tests.length) {
315 if (this.cachedMetadata) {
316 message = 'Cached metadata present but no tests. ';
317 }
318 }
319 else if (1 === tests.length) {
320 if (this.cachedMetadata) {
321 message = 'Single test files should not have cached metadata. ';
322 }
323 else {
324 var testMetadata = this.currentMetadata[tests[0].name];
325 for (var meta in testMetadata) {
326 if (testMetadata.hasOwnProperty(meta)) {
327 message = 'Single tests should not have metadata. ' +
328 'Move metadata to <head>. ';
329 break;
330 }
331 }
332 }
333 }
334 else {
335 if (this.cachedMetadata) {
336 messageClass = 'error';
337 if ('string' == typeof(this.cachedMetadata)) {
338 message = this.cachedMetadata;
339 showSource = true;
340 }
341 else if (! this.validateCache()) {
342 message = 'Cached metadata out of sync. ';
343 showSource = true;
344 }
345 }
346 }
347
348 if (message) {
349 var messageElement = document.createElement('p');
350 messageElement.setAttribute('id', 'metadata_issue');
351 messageElement.setAttribute('class', messageClass);
352 this.appendText(messageElement, message);
353
354 if (showSource) {
355 var link = document.createElement('a');
356 this.appendText(link, 'Click for source code.');
357 link.setAttribute('href', '#');
358 link.setAttribute('onclick',
359 'metadata_generator.addSourceElement(event)');
360 messageElement.appendChild(link);
361 }
362
363 var summary = document.getElementById('summary');
364 if (summary) {
365 summary.parentNode.insertBefore(messageElement, summary);
366 }
367 else {
368 var log = document.getElementById('log');
369 if (log) {
370 log.appendChild(messageElement);
371 }
372 }
373 }
374 },
375
376 setup: function() {
377 add_completion_callback(
378 function (tests, harness_status) {
379 metadata_generator.process(tests, harness_status);
380 dump_test_results(tests, harness_status);
381 });
382 }
383 };
384
385 function dump_test_results(tests, status) {
386 var results_element = document.createElement("script");
387 results_element.type = "text/json";
388 results_element.id = "__testharness__results__";
389 var test_results = tests.map(function(x) {
390 return {name:x.name, status:x.status, message:x.message, stack:x.stack}
391 });
392 var data = {test:window.location.href,
393 tests:test_results,
394 status: status.status,
395 message: status.message,
396 stack: status.stack};
397 results_element.textContent = JSON.stringify(data);
398
399 // To avoid a HierarchyRequestError with XML documents, ensure that 'results _element'
400 // is inserted at a location that results in a valid document.
401 var parent = document.body
402 ? document.body // <body> is required in XHTML documents
403 : document.documentElement; // fallback for optional <body> in HTML5 , SVG, etc.
404
405 parent.appendChild(results_element);
406 }
407
408 metadata_generator.setup();
409
410 /* If the parent window has a testharness_properties object,
411 * we use this to provide the test settings. This is used by the
412 * default in-browser runner to configure the timeout and the
413 * rendering of results
414 */
415 try {
416 if (window.opener && "testharness_properties" in window.opener) {
417 /* If we pass the testharness_properties object as-is here without
418 * JSON stringifying and reparsing it, IE fails & emits the message
419 * "Could not complete the operation due to error 80700019".
420 */
421 setup(JSON.parse(JSON.stringify(window.opener.testharness_properties)));
422 }
423 } catch (e) {
424 }
425 // vim: set expandtab shiftwidth=4 tabstop=4:
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698