2 * This file is intended for vendors to implement
3 * code needed to integrate testharness.js tests with their own test systems.
5 * The default implementation extracts metadata from the tests and validates
6 * it against the cached version that should be present in the test source
7 * file. If the cache is not found or is out of sync, source code suitable for
8 * caching the metadata is optionally generated.
10 * The cached metadata is present for extraction by test processing tools that
11 * are unable to execute javascript.
13 * Metadata is attached to tests via the properties parameter in the test
14 * constructor. See testharness.js for details.
16 * Typically test system integration will attach callbacks when each test has
17 * run, using add_result_callback(callback(test)), or when the whole test file
18 * has completed, using
19 * add_completion_callback(callback(tests, harness_status)).
21 * For more documentation about the callback functions and the
22 * parameters they are called with see testharness.js
27 var metadata_generator = {
30 cachedMetadata: false,
31 metadataProperties: ['help', 'assert', 'author'],
33 error: function(message) {
34 var messageElement = document.createElement('p');
35 messageElement.setAttribute('class', 'error');
36 this.appendText(messageElement, message);
38 var summary = document.getElementById('summary');
40 summary.parentNode.insertBefore(messageElement, summary);
43 document.body.appendChild(messageElement);
48 * Ensure property value has contact information
50 validateContact: function(test, propertyName) {
52 var value = test.properties[propertyName];
53 var values = Array.isArray(value) ? value : [value];
54 for (var index = 0; index < values.length; index++) {
55 value = values[index];
56 var re = /(\S+)(\s*)<(.*)>(.*)/;
57 if (! re.test(value)) {
58 re = /(\S+)(\s+)(http[s]?:\/\/)(.*)/
59 if (! re.test(value)) {
60 this.error('Metadata property "' + propertyName +
61 '" for test: "' + test.name +
62 '" must have name and contact information ' +
63 '("name <email>" or "name http(s)://")');
72 * Extract metadata from test object
74 extractFromTest: function(test) {
75 var testMetadata = {};
76 // filter out metadata from other properties in test
77 for (var metaIndex = 0; metaIndex < this.metadataProperties.length;
79 var meta = this.metadataProperties[metaIndex];
80 if (test.properties.hasOwnProperty(meta)) {
81 if ('author' == meta) {
82 this.validateContact(test, meta);
84 testMetadata[meta] = test.properties[meta];
91 * Compare cached metadata to extracted metadata
93 validateCache: function() {
94 for (var testName in this.currentMetadata) {
95 if (! this.cachedMetadata.hasOwnProperty(testName)) {
98 var testMetadata = this.currentMetadata[testName];
99 var cachedTestMetadata = this.cachedMetadata[testName];
100 delete this.cachedMetadata[testName];
102 for (var metaIndex = 0; metaIndex < this.metadataProperties.length;
104 var meta = this.metadataProperties[metaIndex];
105 if (cachedTestMetadata.hasOwnProperty(meta) &&
106 testMetadata.hasOwnProperty(meta)) {
107 if (Array.isArray(cachedTestMetadata[meta])) {
108 if (! Array.isArray(testMetadata[meta])) {
111 if (cachedTestMetadata[meta].length ==
112 testMetadata[meta].length) {
114 index < cachedTestMetadata[meta].length;
116 if (cachedTestMetadata[meta][index] !=
117 testMetadata[meta][index]) {
127 if (Array.isArray(testMetadata[meta])) {
130 if (cachedTestMetadata[meta] != testMetadata[meta]) {
135 else if (cachedTestMetadata.hasOwnProperty(meta) ||
136 testMetadata.hasOwnProperty(meta)) {
141 for (var testName in this.cachedMetadata) {
147 appendText: function(elemement, text) {
148 elemement.appendChild(document.createTextNode(text));
151 jsonifyArray: function(arrayValue, indent) {
154 if (1 == arrayValue.length) {
155 output += JSON.stringify(arrayValue[0]);
158 for (var index = 0; index < arrayValue.length; index++) {
160 output += ',\n ' + indent;
162 output += JSON.stringify(arrayValue[index]);
169 jsonifyObject: function(objectValue, indent) {
173 for (var property in objectValue) {
175 if (Array.isArray(objectValue[property]) ||
176 ('object' == typeof(value))) {
181 for (var property in objectValue) {
182 output += ' "' + property + '": '
183 + JSON.stringify(objectValue[property])
189 for (var property in objectValue) {
194 output += '\n ' + indent + '"' + property + '": ';
195 var value = objectValue[property];
196 if (Array.isArray(value)) {
197 output += this.jsonifyArray(value, indent +
198 ' '.substr(0, 5 + property.length));
200 else if ('object' == typeof(value)) {
201 output += this.jsonifyObject(value, indent + ' ');
204 output += JSON.stringify(value);
207 if (1 < output.length) {
208 output += '\n' + indent;
216 * Generate javascript source code for captured metadata
217 * Metadata is in pretty-printed JSON format
219 generateSource: function() {
221 '<script id="metadata_cache">/*\n' +
222 this.jsonifyObject(this.currentMetadata, '') + '\n' +
228 * Add element containing metadata source code
230 addSourceElement: function(event) {
231 var sourceWrapper = document.createElement('div');
232 sourceWrapper.setAttribute('id', 'metadata_source');
234 var instructions = document.createElement('p');
235 if (this.cachedMetadata) {
236 this.appendText(instructions,
237 'Replace the existing <script id="metadata_cache"> element ' +
238 'in the test\'s <head> with the following:');
241 this.appendText(instructions,
242 'Copy the following into the <head> element of the test ' +
243 'or the test\'s metadata sidecar file:');
245 sourceWrapper.appendChild(instructions);
247 var sourceElement = document.createElement('pre');
248 this.appendText(sourceElement, this.generateSource());
250 sourceWrapper.appendChild(sourceElement);
252 var messageElement = document.getElementById('metadata_issue');
253 messageElement.parentNode.insertBefore(sourceWrapper,
254 messageElement.nextSibling);
255 messageElement.parentNode.removeChild(messageElement);
257 (event.preventDefault) ? event.preventDefault() :
258 event.returnValue = false;
262 * Extract the metadata cache from the cache element if present
264 getCachedMetadata: function() {
265 var cacheElement = document.getElementById('metadata_cache');
268 var cacheText = cacheElement.firstChild.nodeValue;
269 var openBrace = cacheText.indexOf('{');
270 var closeBrace = cacheText.lastIndexOf('}');
271 if ((-1 < openBrace) && (-1 < closeBrace)) {
272 cacheText = cacheText.slice(openBrace, closeBrace + 1);
274 this.cachedMetadata = JSON.parse(cacheText);
277 this.cachedMetadata = 'Invalid JSON in Cached metadata. ';
281 this.cachedMetadata = 'Metadata not found in cache element. ';
287 * Main entry point, extract metadata from tests, compare to cached version
289 * If cache not present or differs from extrated metadata, generate an error
291 process: function(tests, harness_status) {
292 for (var index = 0; index < tests.length; index++) {
293 var test = tests[index];
294 if (this.currentMetadata.hasOwnProperty(test.name)) {
295 this.error('Duplicate test name: ' + test.name);
298 this.currentMetadata[test.name] = this.extractFromTest(test);
302 this.getCachedMetadata();
305 var messageClass = 'warning';
306 var showSource = false;
308 if (0 == tests.length) {
309 if (this.cachedMetadata) {
310 message = 'Cached metadata present but no tests. ';
313 else if (1 == tests.length) {
314 if (this.cachedMetadata) {
315 message = 'Single test files should not have cached metadata. ';
318 var testMetadata = this.currentMetadata[tests[0].name];
319 var hasMetadata = false;
320 for (var meta in testMetadata) {
321 hasMetadata |= testMetadata.hasOwnProperty(meta);
324 message = 'Single tests should not have metadata. ' +
325 'Move metadata to <head>. ';
330 if (this.cachedMetadata) {
331 messageClass = 'error';
332 if ('string' == typeof(this.cachedMetadata)) {
333 message = this.cachedMetadata;
336 else if (! this.validateCache()) {
337 message = 'Cached metadata out of sync. ';
344 var messageElement = document.createElement('p');
345 messageElement.setAttribute('id', 'metadata_issue');
346 messageElement.setAttribute('class', messageClass);
347 this.appendText(messageElement, message);
350 var link = document.createElement('a');
351 this.appendText(link, 'Click for source code.');
352 link.setAttribute('href', '#');
353 link.setAttribute('onclick',
354 'metadata_generator.addSourceElement(event)');
355 messageElement.appendChild(link);
358 var summary = document.getElementById('summary');
360 summary.parentNode.insertBefore(messageElement, summary);
363 var log = document.getElementById('log');
365 log.appendChild(messageElement);
372 add_completion_callback(
373 function (tests, harness_status) {
374 metadata_generator.process(tests, harness_status)
379 metadata_generator.setup();
380 // vim: set expandtab shiftwidth=4 tabstop=4: