test262-export.py (24188B)
1 #!/usr/bin/env python3 2 # 3 # This Source Code Form is subject to the terms of the Mozilla Public 4 # License, v. 2.0. If a copy of the MPL was not distributed with this 5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7 import math 8 import os 9 import re 10 import shutil 11 import sys 12 import traceback 13 from datetime import date 14 from typing import Any, Optional 15 16 import yaml 17 18 # Skip all common files used to support tests for jstests 19 # These files are listed in the README.txt 20 SUPPORT_FILES = set([ 21 "browser.js", 22 "shell.js", 23 "template.js", 24 "user.js", 25 "js-test-driver-begin.js", 26 "js-test-driver-end.js", 27 ]) 28 29 30 # Run once per subdirectory 31 def findAndCopyIncludes(dirPath: str, baseDir: str, includeDir: str) -> "list[str]": 32 relPath = os.path.relpath(dirPath, baseDir) 33 includes: list[str] = ["sm/non262.js"] 34 os.makedirs(os.path.join(includeDir, "sm"), exist_ok=True) 35 36 # Recurse down all folders in the relative path until 37 # we reach the base directory of shell.js include files. 38 # Each directory will have a shell.js file to copy. 39 while relPath: 40 # find the shell.js 41 shellFile = os.path.join(baseDir, relPath, "shell.js") 42 43 # if the file isn't excluded, exists, and is not empty, include in includes 44 if ( 45 not any(relPath == f"non262/{path}" for path in UNSUPPORTED_PATHS) 46 and os.path.exists(shellFile) 47 and os.path.getsize(shellFile) > 0 48 ): 49 with open(shellFile, "rb") as f: 50 testSource = f.read() 51 52 if b"// SKIP test262 export" not in testSource: 53 # create new shell.js file name 54 includeFileName = "sm/" + relPath.replace("/", "-") + "-shell.js" 55 includes.append(includeFileName) 56 57 includesPath = os.path.join(includeDir, includeFileName) 58 shutil.copyfile(shellFile, includesPath) 59 60 relPath = os.path.split(relPath)[0] 61 62 return includes 63 64 65 UNSUPPORTED_CODE: "list[bytes]" = [ 66 b"// SKIP test262 export", 67 b"inTimeZone(", 68 b"getTimeZone(", 69 b"setTimeZone(", 70 b"getAvailableLocalesOf(", 71 b"uneval(", 72 b"Debugger", 73 b"SpecialPowers", 74 b"evalcx(", 75 b"evaluate(", 76 b"drainJobQueue(", 77 b"getPromiseResult(", 78 b"assertEventuallyEq(", 79 b"assertEventuallyThrows(", 80 b"settlePromiseNow(", 81 b"setPromiseRejectionTrackerCallback", 82 b"displayName(", 83 b"InternalError", 84 b"toSource(", 85 b"toSource.call(", 86 b"isRope(", 87 b"isSameCompartment(", 88 b"isCCW", 89 b"nukeCCW", 90 b"representativeStringArray(", 91 b"largeArrayBufferSupported(", 92 b"helperThreadCount(", 93 b"serialize(", 94 b"deserialize(", 95 b"clone_object_check", 96 b"grayRoot(", 97 b"blackRoot(", 98 b"gczeal", 99 b"getSelfHostedValue(", 100 b"oomTest(", 101 b"assertLineAndColumn(", 102 b"wrapWithProto(", 103 b"Reflect.parse(", 104 b"relazifyFunctions(", 105 b"ignoreUnhandledRejections", 106 b".lineNumber", 107 b"expectExitCode", 108 b"loadRelativeToScript", 109 b"XorShiftGenerator", 110 ] 111 112 113 def skipTest(source: bytes) -> Optional[bytes]: 114 if b"This Source Code Form is subject to the terms of the Mozilla Public" in source: 115 return b"MPL license" 116 for c in UNSUPPORTED_CODE: 117 if c in source: 118 return c 119 120 return None 121 122 123 MODELINE_PATTERN = re.compile(rb"/(/|\*) -\*- .* -\*-( \*/)?[\r\n]+") 124 125 UNSUPPORTED_FEATURES = [ 126 "async-iterator-helpers", 127 "Iterator.range", 128 ] 129 130 UNSUPPORTED_PATHS = [ 131 "Intl", 132 "Temporal/Intl", 133 "reflect-parse", 134 "extensions/empty.txt", 135 "extensions/file-mapped-arraybuffers.txt", 136 "module/bug1693261-async.mjs", 137 "module/bug1693261-c1.mjs", 138 "module/bug1693261-c2.mjs", 139 "module/bug1693261-x.mjs", 140 ] 141 142 143 def convertTestFile(source: bytes, includes: "list[str]") -> Optional[bytes]: 144 """ 145 Convert a jstest test to a compatible Test262 test file. 146 """ 147 148 source = MODELINE_PATTERN.sub(b"", source) 149 150 # Extract the reftest data from the source 151 source, reftest = parseHeader(source) 152 153 # Add copyright, if needed. 154 copyright, source = insertCopyrightLines(source) 155 156 # Extract the frontmatter data from the source 157 frontmatter, source = extractMeta(source) 158 159 source, addincludes = translateHelpers(source) 160 includes = includes + addincludes 161 162 meta = computeMeta(source, reftest, frontmatter, includes) 163 164 if "features" in meta and any(f in UNSUPPORTED_FEATURES for f in meta["features"]): 165 return None 166 167 source = insertMeta(source, meta) 168 169 source = convertReportCompare(source) 170 171 return copyright + source 172 173 174 ## parseHeader 175 176 177 class ReftestEntry: 178 def __init__( 179 self, 180 features: "list[str]", 181 error: Optional[str], 182 module: bool, 183 info: Optional[str], 184 ): 185 self.features: list[str] = features 186 self.error: Optional[str] = error 187 self.module: bool = module 188 self.info: Optional[str] = info 189 190 191 def featureFromReftest(reftest: str) -> Optional[str]: 192 if reftest == "Iterator": 193 return "iterator-helpers" 194 if reftest == "AsyncIterator": 195 return "async-iterator-helpers" 196 if reftest == "Temporal": 197 return "Temporal" 198 if reftest in ("Intl", "addIntlExtras"): 199 return None 200 raise Exception(f"Unexpected feature {reftest}") 201 202 203 def fetchReftestEntries(reftest: str) -> ReftestEntry: 204 """ 205 Collects and stores the entries from the reftest header. 206 """ 207 208 # TODO: fails, slow, skip, random, random-if 209 210 features: list[str] = [] 211 error: Optional[str] = None 212 comments: Optional[str] = None 213 module: bool = False 214 215 # should capture conditions to skip 216 matchesSkip = re.search(r"skip-if\((.*)\)", reftest) 217 if matchesSkip: 218 matches = matchesSkip.group(1).split("||") 219 for match in matches: 220 # captures a features list 221 dependsOnProp = re.search( 222 r"!this.hasOwnProperty\([\'\"](.*?)[\'\"]\)", match 223 ) 224 if dependsOnProp: 225 if feature := featureFromReftest(dependsOnProp.group(1)): 226 features.append(feature) 227 else: 228 print("# Can't parse the following skip-if rule: %s" % match) 229 230 # should capture the expected error 231 matchesError = re.search(r"error:\s*(\w*)", reftest) 232 if matchesError: 233 # The metadata from the reftests won't say if it's a runtime or an 234 # early error. This specification is required for the frontmatter tags. 235 error = matchesError.group(1) 236 237 # just tells if it's a module 238 matchesModule = re.search(r"\bmodule\b", reftest) 239 if matchesModule: 240 module = True 241 242 # captures any comments 243 matchesComments = re.search(r" -- (.*)", reftest) 244 if matchesComments: 245 comments = matchesComments.group(1) 246 247 return ReftestEntry(features=features, error=error, module=module, info=comments) 248 249 250 def parseHeader(source: bytes) -> "tuple[bytes, Optional[ReftestEntry]]": 251 """ 252 Parse the source to return it with the extracted the header 253 """ 254 from lib.manifest import TEST_HEADER_PATTERN_INLINE 255 256 # Bail early if we do not start with a single comment. 257 if not source.startswith(b"//"): 258 return (source, None) 259 260 # Extract the token. 261 part, _, rest = source.partition(b"\n") 262 part = part.decode("utf-8") 263 matches = TEST_HEADER_PATTERN_INLINE.match(part) 264 265 if matches and matches.group(0): 266 reftest = matches.group(0) 267 268 # Remove the found header from the source; 269 # Fetch and return the reftest entries 270 return (rest, fetchReftestEntries(reftest)) 271 272 return (source, None) 273 274 275 ## insertCopyrightLines 276 277 278 LICENSE_PATTERN = re.compile( 279 rb"// Copyright( \([C]\))? (\w+) .+\. {1,2}All rights reserved\.[\r\n]{1,2}" 280 + rb"(" 281 + rb"// This code is governed by the( BSD)? license found in the LICENSE file\." 282 + rb"|" 283 + rb"// See LICENSE for details." 284 + rb"|" 285 + rb"// Use of this source code is governed by a BSD-style license that can be[\r\n]{1,2}" 286 + rb"// found in the LICENSE file\." 287 + rb"|" 288 + rb"// See LICENSE or https://github\.com/tc39/test262/blob/HEAD/LICENSE" 289 + rb")[\r\n]{1,2}", 290 re.IGNORECASE, 291 ) 292 293 PD_PATTERN1 = re.compile( 294 rb"/\*[\r\n]{1,2}" 295 + rb" \* Any copyright is dedicated to the Public Domain\.[\r\n]{1,2}" 296 + rb" \* (http://creativecommons\.org/licenses/publicdomain/|https://creativecommons\.org/publicdomain/zero/1\.0/)[\r\n]{1,2}" 297 + rb"( \* Contributors?:" 298 + rb"(( [^\r\n]*[\r\n]{1,2})|" 299 + rb"([\r\n]{1,2}( \* [^\r\n]*[\r\n]{1,2})+)))?" 300 + rb" \*/[\r\n]{1,2}", 301 re.IGNORECASE, 302 ) 303 304 PD_PATTERN2 = re.compile( 305 rb"// Any copyright is dedicated to the Public Domain\.[\r\n]{1,2}" 306 + rb"// (http://creativecommons\.org/licenses/publicdomain/|https://creativecommons\.org/publicdomain/zero/1\.0/)[\r\n]{1,2}" 307 + rb"(// Contributors?: [^\r\n]*[\r\n]{1,2})?", 308 re.IGNORECASE, 309 ) 310 311 PD_PATTERN3 = re.compile( 312 rb"/\* Any copyright is dedicated to the Public Domain\.[\r\n]{1,2}" 313 + rb" \* (http://creativecommons\.org/licenses/publicdomain/|https://creativecommons\.org/publicdomain/zero/1\.0/) \*/[\r\n]{1,2}", 314 re.IGNORECASE, 315 ) 316 317 318 BSD_TEMPLATE = ( 319 b"""\ 320 // Copyright (C) %d Mozilla Corporation. All rights reserved. 321 // This code is governed by the BSD license found in the LICENSE file. 322 323 """ 324 % date.today().year 325 ) 326 327 PD_TEMPLATE = b"""\ 328 /* 329 * Any copyright is dedicated to the Public Domain. 330 * http://creativecommons.org/licenses/publicdomain/ 331 */ 332 333 """ 334 335 336 def insertCopyrightLines(source: bytes) -> "tuple[bytes, bytes]": 337 """ 338 Insert the copyright lines into the file. 339 """ 340 if match := LICENSE_PATTERN.search(source): 341 start, end = match.span() 342 return source[start:end], source[:start] + source[end:] 343 344 if ( 345 match := PD_PATTERN1.search(source) 346 or PD_PATTERN2.search(source) 347 or PD_PATTERN3.search(source) 348 ): 349 start, end = match.span() 350 return PD_TEMPLATE, source[:start] + source[end:] 351 352 return BSD_TEMPLATE, source 353 354 355 ## extractMeta 356 357 FRONTMATTER_WRAPPER_PATTERN = re.compile( 358 rb"/\*\---\n([\s]*)((?:\s|\S)*)[\n\s*]---\*/[\r\n]{1,2}", flags=re.DOTALL 359 ) 360 361 362 def extractMeta(source: bytes) -> "tuple[dict[str, Any], bytes]": 363 """ 364 Capture the frontmatter metadata as yaml if it exists. 365 Returns a new dict if it doesn't. 366 """ 367 368 match = FRONTMATTER_WRAPPER_PATTERN.search(source) 369 if not match: 370 return {}, source 371 372 indent, frontmatter_lines = match.groups() 373 374 unindented = re.sub(b"^%s" % indent, b"", frontmatter_lines) 375 376 yamlresult = yaml.safe_load(unindented) 377 if isinstance(yamlresult, str): 378 result = {"info": yamlresult} 379 else: 380 result = yamlresult 381 start, end = match.span() 382 return result, source[:start] + source[end:] 383 384 385 ## computeMeta 386 387 388 def translateHelpers(source: bytes) -> "tuple[bytes, list[str]]": 389 """ 390 Translate SpiderMonkey helper methods that have standard variants in test262. 391 This also returns a list of includes that are needed to use these variants in test262, if any. 392 """ 393 394 includes: list[str] = [] 395 source, n = re.subn(rb"\bassertDeepEq\b", b"assert.deepEqual", source) 396 if n: 397 includes.append("deepEqual.js") 398 399 source, n = re.subn(rb"\bassertEqArray\b", b"assert.compareArray", source) 400 if n: 401 includes.append("compareArray.js") 402 403 source = re.sub(rb"\bdetachArrayBuffer\b", b"$262.detachArrayBuffer", source) 404 source = re.sub(rb"\bnewGlobal\b", b"createNewGlobal", source) 405 source = re.sub(rb"\bassertEq\b", b"assert.sameValue", source) 406 407 return (source, includes) 408 409 410 def mergeMeta( 411 reftest: "Optional[ReftestEntry]", 412 frontmatter: "dict[str, Any]", 413 includes: "list[str]", 414 ) -> "dict[str, Any]": 415 """ 416 Merge the metadata from reftest and an existing frontmatter and populate 417 required frontmatter fields properly. 418 """ 419 420 # Merge the meta from reftest to the frontmatter 421 422 # Add the shell specific includes 423 if includes: 424 frontmatter["includes"] = frontmatter.get("includes", []) + list(includes) 425 426 flags: list[str] = frontmatter.get("flags", []) 427 if "noStrict" not in flags and "onlyStrict" not in flags: 428 frontmatter.setdefault("flags", []).append("noStrict") 429 430 if not reftest: 431 return frontmatter 432 433 frontmatter.setdefault("features", []).extend(reftest.features) 434 435 # Only add the module flag if the value from reftest is truish 436 if reftest.module: 437 frontmatter.setdefault("flags", []).append("module") 438 if "noStrict" in frontmatter["flags"]: 439 frontmatter["flags"].remove("noStrict") 440 if "onlyStrict" in frontmatter["flags"]: 441 frontmatter["flags"].remove("onlyStrict") 442 443 # Add any comments to the info tag 444 if reftest.info: 445 info = reftest.info 446 # Open some space in an existing info text 447 if "info" in frontmatter and frontmatter["info"]: 448 frontmatter["info"] += "\n\n%s" % info 449 else: 450 frontmatter["info"] = info 451 452 # Set the negative flags 453 if reftest.error: 454 error = reftest.error 455 if "negative" not in frontmatter: 456 frontmatter["negative"] = { 457 # This code is assuming error tags are parse errors, but they 458 # might be runtime errors as well. 459 # From this point, this code can also print a warning asking to 460 # specify the error phase in the generated code or fill the 461 # phase with an empty string. 462 "phase": "parse", 463 "type": error, 464 } 465 # Print a warning if the errors don't match 466 elif frontmatter["negative"].get("type") != error: 467 print( 468 "Warning: The reftest error doesn't match the existing " 469 + "frontmatter error. %s != %s" 470 % (error, frontmatter["negative"]["type"]) 471 ) 472 473 return frontmatter 474 475 476 def cleanupMeta(meta: "dict[str, Any]") -> "dict[str, Any]": 477 """ 478 Clean up all the frontmatter meta tags. This is not a lint tool, just a 479 simple cleanup to remove trailing spaces and duplicate entries from lists. 480 """ 481 482 # Trim values on each string tag 483 for tag in ("description", "esid", "es5id", "es6id", "info", "author"): 484 if tag in meta: 485 if not meta[tag]: 486 del meta[tag] 487 else: 488 meta[tag] = meta[tag].strip() 489 if not len(meta[tag]): 490 del meta[tag] 491 492 # Populate required tags 493 for tag in ("description", "esid"): 494 meta.setdefault(tag, "pending") 495 496 # Remove duplicate entries on each list tag 497 for tag in ("features", "flags", "includes"): 498 if tag in meta: 499 if not meta[tag]: 500 del meta[tag] 501 else: 502 # We need the list back for the yaml dump 503 meta[tag] = sorted(set(meta[tag]), reverse=tag == "includes") 504 if not len(meta[tag]): 505 del meta[tag] 506 507 if "negative" in meta: 508 # If the negative tag exists, phase needs to be present and set 509 if meta["negative"].get("phase") not in ["parse", "resolution", "runtime"]: 510 print( 511 "Warning: the negative.phase is not properly set.\n" 512 + "Ref https://github.com/tc39/test262/blob/main/INTERPRETING.md#negative" 513 ) 514 # If the negative tag exists, type is required 515 if "type" not in meta["negative"]: 516 print( 517 "Warning: the negative.type is not set.\n" 518 + "Ref https://github.com/tc39/test262/blob/main/INTERPRETING.md#negative" 519 ) 520 521 return meta 522 523 524 def insertMeta(source: bytes, frontmatter: "dict[str, Any]") -> bytes: 525 """ 526 Insert the formatted frontmatter into the file, use the current existing 527 space if any 528 """ 529 lines: list[bytes] = [] 530 531 lines.append(b"/*---") 532 533 for key, value in frontmatter.items(): 534 if key in ("description", "info"): 535 lines.append(b"%s: |" % key.encode("ascii")) 536 lines.append( 537 yaml.dump( 538 value, 539 encoding="utf8", 540 default_style="|", 541 default_flow_style=False, 542 allow_unicode=True, 543 ) 544 .strip() 545 .replace(b"|-\n", b"") 546 ) 547 elif key in ["flags", "includes", "features"]: 548 lines.append( 549 b"%s: " % key.encode("ascii") 550 + yaml.dump( 551 value, encoding="utf8", default_flow_style=True, width=math.inf 552 ).strip() 553 ) 554 else: 555 lines.append( 556 yaml.dump( 557 {key: value}, encoding="utf8", default_flow_style=False 558 ).strip() 559 ) 560 561 lines.append(b"---*/\n") 562 source = b"\n".join(lines) + source 563 564 if frontmatter.get("negative", {}).get("phase", "") == "parse": 565 source += b"$DONOTEVALUATE();\n" 566 567 return source 568 569 570 def computeMeta( 571 source: bytes, 572 reftest: "Optional[ReftestEntry]", 573 frontmatter: "dict[str, Any]", 574 includes: "list[str]", 575 ) -> "dict[str, Any]": 576 """ 577 Captures the reftest meta and a pre-existing meta if any and merge them 578 into a single dict. 579 """ 580 581 if source.startswith((b'"use strict"', b"'use strict'")): 582 frontmatter.setdefault("flags", []).append("onlyStrict") 583 584 if b"createIsHTMLDDA" in source: 585 frontmatter.setdefault("features", []).append("IsHTMLDDA") 586 587 # Merge the reftest and frontmatter 588 merged = mergeMeta(reftest, frontmatter, includes) 589 590 # Cleanup the metadata 591 return cleanupMeta(merged) 592 593 594 ## convertReportCompare 595 596 597 def convertReportCompare(source: bytes) -> bytes: 598 """ 599 Captures all the reportCompare and convert them accordingly. 600 601 Cases with reportCompare calls where the arguments are the same and one of 602 0, true, or null, will be discarded as they are not necessary for Test262. 603 604 Otherwise, reportCompare will be replaced with assert.sameValue, as the 605 equivalent in Test262 606 """ 607 608 def replaceFn(matchobj: "re.Match[bytes]") -> bytes: 609 actual: bytes = matchobj.group(4) 610 expected: bytes = matchobj.group(5) 611 612 if actual == expected and actual in [b"0", b"true", b"null"]: 613 return b"" 614 615 return matchobj.group() 616 617 newSource = re.sub( 618 rb".*(if \(typeof reportCompare ===? (\"|')function(\"|')\)\s*)?reportCompare\s*\(\s*(\w*)\s*,\s*(\w*)\s*(,\s*\S*)?\s*\)\s*;*\s*", 619 replaceFn, 620 source, 621 ) 622 623 return re.sub(rb"\breportCompare\b", b"assert.sameValue", newSource) 624 625 626 def exportTest262( 627 outDir: str, providedSrcs: "list[str]", includeShell: bool, baseDir: str 628 ): 629 # Create the output directory from scratch. 630 print(f"Generating output in {os.path.abspath(outDir)}") 631 if os.path.isdir(outDir): 632 shutil.rmtree(outDir) 633 634 # only make the includes directory if requested 635 includeDir = os.path.join(outDir, "harness-includes") 636 if includeShell: 637 os.makedirs(includeDir) 638 639 skipped = 0 640 skippedDirs = 0 641 642 # Go through each source path 643 for providedSrc in providedSrcs: 644 src = os.path.abspath(providedSrc) 645 if not os.path.isdir(src): 646 print(f"Did not find directory {src}") 647 # the basename of the path will be used in case multiple "src" arguments 648 # are passed in to create an output directory for each "src". 649 basename = os.path.basename(src) 650 651 # Process all test directories recursively. 652 for dirPath, dirNames, fileNames in os.walk(src): 653 # we need to make and get the unique set of includes for this filepath 654 includes = [] 655 if includeShell: 656 includes = findAndCopyIncludes(dirPath, baseDir, includeDir) 657 658 relPath = os.path.relpath(dirPath, src) 659 fullRelPath = os.path.join(basename, relPath) 660 661 if relPath in UNSUPPORTED_PATHS: 662 print("SKIPPED unsupported path %s" % relPath) 663 # Prevent recursing into subdirectories 664 del dirNames[:] 665 skippedDirs += 1 666 continue 667 668 # Make new test subdirectory to seperate from includes 669 currentOutDir = os.path.join(outDir, "tests", fullRelPath) 670 671 # This also creates the own outDir folder 672 if not os.path.exists(currentOutDir): 673 os.makedirs(currentOutDir) 674 675 for fileName in fileNames: 676 # Skip browser.js files 677 if fileName in {"browser.js", "shell.js"}: 678 continue 679 680 if fileName.endswith("~"): 681 continue 682 683 filePath = os.path.join(dirPath, fileName) 684 testName = os.path.join( 685 fullRelPath, fileName 686 ) # captures folder(s)+filename 687 688 # This is hacky, but we're unlikely to add anything new 689 # that needs to be skipped this way. 690 if relPath + "/" + fileName in UNSUPPORTED_PATHS: 691 print("SKIPPED %s" % testName) 692 skipped += 1 693 continue 694 695 # Copy non-test files as is. 696 if "_FIXTURE" in fileName or os.path.splitext(fileName)[1] != ".js": 697 shutil.copyfile(filePath, os.path.join(currentOutDir, fileName)) 698 print("C %s" % testName) 699 continue 700 701 # Read the original test source and preprocess it for Test262 702 with open(filePath, "rb") as testFile: 703 testSource = testFile.read() 704 705 if not testSource: 706 print("SKIPPED %s" % testName) 707 skipped += 1 708 continue 709 710 skip = skipTest(testSource) 711 if skip is not None: 712 print( 713 f"SKIPPED {testName} because file contains {skip.decode('ascii')}" 714 ) 715 skipped += 1 716 continue 717 718 try: 719 newSource = convertTestFile(testSource, includes) 720 if newSource is None: 721 print(f"SKIPPED {testName} due to disabled features") 722 skipped += 1 723 continue 724 except Exception as e: 725 print(f"SKIPPED {testName} due to error {e}") 726 traceback.print_exc(file=sys.stdout) 727 skipped += 1 728 continue 729 730 with open(os.path.join(currentOutDir, fileName), "wb") as output: 731 output.write(newSource) 732 733 print("SAVED %s" % testName) 734 735 print(f"Skipped {skipped} tests and {skippedDirs} test directories") 736 737 738 if __name__ == "__main__": 739 import argparse 740 741 # This script must be run from js/src/tests to work correctly. 742 if "/".join(os.path.normpath(os.getcwd()).split(os.sep)[-3:]) != "js/src/tests": 743 raise RuntimeError("%s must be run from js/src/tests" % sys.argv[0]) 744 745 parser = argparse.ArgumentParser( 746 description="Export tests to match Test262 file compliance." 747 ) 748 parser.add_argument( 749 "--out", 750 default="test262/export", 751 help="Output directory. Any existing directory will be removed! " 752 "(default: %(default)s)", 753 ) 754 parser.add_argument( 755 "--exportshellincludes", 756 action="store_true", 757 help="Optionally export shell.js files as includes in exported tests. " 758 "Only use for testing, do not use for exporting to test262 (test262 tests " 759 "should have as few dependencies as possible).", 760 ) 761 parser.add_argument( 762 "src", nargs="+", help="Source folder with test files to export" 763 ) 764 args = parser.parse_args() 765 exportTest262( 766 os.path.abspath(args.out), args.src, args.exportshellincludes, os.getcwd() 767 )