mochitestListingsUtils.js (9016B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // We expect this to be defined in the global scope by runtest.py. 6 /* global _TEST_PREFIX */ 7 8 // 9 // HTML GENERATION 10 // 11 /* global A, ABBR, ACRONYM, ADDRESS, APPLET, AREA, B, BASE, 12 BASEFONT, BDO, BIG, BLOCKQUOTE, BODY, BR, BUTTON, 13 CAPTION, CENTER, CITE, CODE, COL, COLGROUP, DD, 14 DEL, DFN, DIR, DIV, DL, DT, EM, FIELDSET, FONT, 15 FORM, FRAME, FRAMESET, H1, H2, H3, H4, H5, H6, 16 HEAD, HR, HTML, I, IFRAME, IMG, INPUT, INS, 17 ISINDEX, KBD, LABEL, LEGEND, LI, LINK, MAP, MENU, 18 META, NOFRAMES, NOSCRIPT, OBJECT, OL, OPTGROUP, 19 OPTION, P, PARAM, PRE, Q, S, SAMP, SCRIPT, 20 SELECT, SMALL, SPAN, STRIKE, STRONG, STYLE, SUB, 21 SUP, TABLE, TBODY, TD, TEXTAREA, TFOOT, TH, THEAD, 22 TITLE, TR, TT, U, UL, VAR */ 23 var tags = [ 24 "A", 25 "ABBR", 26 "ACRONYM", 27 "ADDRESS", 28 "APPLET", 29 "AREA", 30 "B", 31 "BASE", 32 "BASEFONT", 33 "BDO", 34 "BIG", 35 "BLOCKQUOTE", 36 "BODY", 37 "BR", 38 "BUTTON", 39 "CAPTION", 40 "CENTER", 41 "CITE", 42 "CODE", 43 "COL", 44 "COLGROUP", 45 "DD", 46 "DEL", 47 "DFN", 48 "DIR", 49 "DIV", 50 "DL", 51 "DT", 52 "EM", 53 "FIELDSET", 54 "FONT", 55 "FORM", 56 "FRAME", 57 "FRAMESET", 58 "H1", 59 "H2", 60 "H3", 61 "H4", 62 "H5", 63 "H6", 64 "HEAD", 65 "HR", 66 "HTML", 67 "I", 68 "IFRAME", 69 "IMG", 70 "INPUT", 71 "INS", 72 "ISINDEX", 73 "KBD", 74 "LABEL", 75 "LEGEND", 76 "LI", 77 "LINK", 78 "MAP", 79 "MENU", 80 "META", 81 "NOFRAMES", 82 "NOSCRIPT", 83 "OBJECT", 84 "OL", 85 "OPTGROUP", 86 "OPTION", 87 "P", 88 "PARAM", 89 "PRE", 90 "Q", 91 "S", 92 "SAMP", 93 "SCRIPT", 94 "SELECT", 95 "SMALL", 96 "SPAN", 97 "STRIKE", 98 "STRONG", 99 "STYLE", 100 "SUB", 101 "SUP", 102 "TABLE", 103 "TBODY", 104 "TD", 105 "TEXTAREA", 106 "TFOOT", 107 "TH", 108 "THEAD", 109 "TITLE", 110 "TR", 111 "TT", 112 "U", 113 "UL", 114 "VAR", 115 ]; 116 117 /** 118 * Below, we'll use makeTagFunc to create a function for each of the 119 * strings in 'tags'. This will allow us to use s-expression like syntax 120 * to create HTML. 121 */ 122 function makeTagFunc(tagName) { 123 return function (attrs /* rest... */) { 124 var startChildren = 0; 125 var response = ""; 126 127 // write the start tag and attributes 128 response += "<" + tagName; 129 // if attr is an object, write attributes 130 if (attrs && typeof attrs == "object") { 131 startChildren = 1; 132 133 for (let key in attrs) { 134 const value = attrs[key]; 135 var val = "" + value; 136 response += " " + key + '="' + val.replace('"', """) + '"'; 137 } 138 } 139 response += ">"; 140 141 // iterate through the rest of the args 142 for (var i = startChildren; i < arguments.length; i++) { 143 if (typeof arguments[i] == "function") { 144 response += arguments[i](); 145 } else { 146 response += arguments[i]; 147 } 148 } 149 150 // write the close tag 151 response += "</" + tagName + ">\n"; 152 return response; 153 }; 154 } 155 156 function makeTags() { 157 // map our global HTML generation functions 158 for (let tag of tags) { 159 this[tag] = makeTagFunc(tag.toLowerCase()); 160 } 161 } 162 163 /** 164 * Creates a generator that iterates over the contents of 165 * an nsIFile directory. 166 */ 167 function* dirIter(dir) { 168 var en = dir.directoryEntries; 169 while (en.hasMoreElements()) { 170 yield en.nextFile; 171 } 172 } 173 174 /** 175 * Builds an optionally nested object containing links to the 176 * files and directories within dir. 177 */ 178 function list(requestPath, directory, recurse) { 179 var count = 0; 180 var path = requestPath; 181 if (path.charAt(path.length - 1) != "/") { 182 path += "/"; 183 } 184 185 var dir = directory.QueryInterface(Ci.nsIFile); 186 var links = {}; 187 188 // The SimpleTest directory is hidden 189 let files = []; 190 for (let file of dirIter(dir)) { 191 if (file.exists() && !file.path.includes("SimpleTest")) { 192 files.push(file); 193 } 194 } 195 196 // Sort files by name, so that tests can be run in a pre-defined order inside 197 // a given directory (see bug 384823) 198 function leafNameComparator(first, second) { 199 if (first.leafName < second.leafName) { 200 return -1; 201 } 202 if (first.leafName > second.leafName) { 203 return 1; 204 } 205 return 0; 206 } 207 files.sort(leafNameComparator); 208 209 count = files.length; 210 for (let file of files) { 211 var key = path + file.leafName; 212 var childCount = 0; 213 if (file.isDirectory()) { 214 key += "/"; 215 } 216 if (recurse && file.isDirectory()) { 217 [links[key], childCount] = list(key, file, recurse); 218 count += childCount; 219 } else if (file.leafName.charAt(0) != ".") { 220 links[key] = { test: { url: key, expected: "pass" } }; 221 } 222 } 223 224 return [links, count]; 225 } 226 227 /** 228 * Heuristic function that determines whether a given path 229 * is a test case to be executed in the harness, or just 230 * a supporting file. 231 */ 232 function isTest(filename, pattern) { 233 if (pattern) { 234 return pattern.test(filename); 235 } 236 237 // File name is a URL style path to a test file, make sure that we check for 238 // tests that start with the appropriate prefix. 239 var testPrefix = typeof _TEST_PREFIX == "string" ? _TEST_PREFIX : "test_"; 240 var testPattern = new RegExp("^" + testPrefix); 241 242 var pathPieces = filename.split("/"); 243 244 return ( 245 testPattern.test(pathPieces[pathPieces.length - 1]) && 246 !filename.includes(".js") && 247 !filename.includes(".css") && 248 !/\^headers\^$/.test(filename) 249 ); 250 } 251 252 /** 253 * Transform nested hashtables of paths to nested HTML lists. 254 */ 255 function linksToListItems(links) { 256 var response = ""; 257 var children = ""; 258 for (let link in links) { 259 const value = links[link]; 260 var classVal = 261 !isTest(link) && !(value instanceof Object) 262 ? "non-test invisible" 263 : "test"; 264 if (value instanceof Object) { 265 children = UL({ class: "testdir" }, linksToListItems(value)); 266 } else { 267 children = ""; 268 } 269 270 var bug_title = link.match(/test_bug\S+/); 271 var bug_num = null; 272 if (bug_title != null) { 273 bug_num = bug_title[0].match(/\d+/); 274 } 275 276 if (bug_title == null || bug_num == null) { 277 response += LI({ class: classVal }, A({ href: link }, link), children); 278 } else { 279 var bug_url = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + bug_num; 280 response += LI( 281 { class: classVal }, 282 A({ href: link }, link), 283 " - ", 284 A({ href: bug_url }, "Bug " + bug_num), 285 children 286 ); 287 } 288 } 289 return response; 290 } 291 292 /** 293 * Transform nested hashtables of paths to a flat table rows. 294 */ 295 function linksToTableRows(links, recursionLevel) { 296 var response = ""; 297 for (let link in links) { 298 const value = links[link]; 299 var classVal = 300 !isTest(link) && value instanceof Object && "test" in value 301 ? "non-test invisible" 302 : ""; 303 304 var spacer = "padding-left: " + 10 * recursionLevel + "px"; 305 306 if (value instanceof Object && !("test" in value)) { 307 response += TR( 308 { class: "dir", id: "tr-" + link }, 309 TD({ colspan: "3" }, " "), 310 TD({ style: spacer }, A({ href: link }, link)) 311 ); 312 response += linksToTableRows(value, recursionLevel + 1); 313 } else { 314 var bug_title = link.match(/test_bug\S+/); 315 var bug_num = null; 316 if (bug_title != null) { 317 bug_num = bug_title[0].match(/\d+/); 318 } 319 if (bug_title == null || bug_num == null) { 320 response += TR( 321 { class: classVal, id: "tr-" + link }, 322 TD("0"), 323 TD("0"), 324 TD("0"), 325 TD({ style: spacer }, A({ href: link }, link)) 326 ); 327 } else { 328 var bug_url = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + bug_num; 329 response += TR( 330 { class: classVal, id: "tr-" + link }, 331 TD("0"), 332 TD("0"), 333 TD("0"), 334 TD( 335 { style: spacer }, 336 A({ href: link }, link), 337 " - ", 338 A({ href: bug_url }, "Bug " + bug_num) 339 ) 340 ); 341 } 342 } 343 } 344 return response; 345 } 346 347 function arrayOfTestFiles(linkArray, fileArray, testPattern) { 348 for (let link in linkArray) { 349 const value = linkArray[link]; 350 if (value instanceof Object && !("test" in value)) { 351 arrayOfTestFiles(value, fileArray, testPattern); 352 } else if (isTest(link, testPattern) && value instanceof Object) { 353 fileArray.push(value.test); 354 } 355 } 356 } 357 /** 358 * Produce a flat array of test file paths to be executed in the harness. 359 */ 360 function jsonArrayOfTestFiles(links) { 361 var testFiles = []; 362 arrayOfTestFiles(links, testFiles); 363 testFiles = testFiles.map(function (file) { 364 return '"' + file.url + '"'; 365 }); 366 367 return "[" + testFiles.join(",\n") + "]"; 368 } 369 370 /** 371 * Produce a normal directory listing. 372 */ 373 function regularListing(metadata, response) { 374 var [links] = list(metadata.path, metadata.getProperty("directory"), false); 375 response.write( 376 "<!DOCTYPE html>\n" + 377 HTML( 378 HEAD(TITLE("mochitest index ", metadata.path)), 379 BODY(BR(), A({ href: ".." }, "Up a level"), UL(linksToListItems(links))) 380 ) 381 ); 382 }