1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# 4# This Source Code Form is subject to the terms of the Mozilla Public 5# License, v. 2.0. If a copy of the MPL was not distributed with this 6# file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 8from __future__ import print_function 9 10import contextlib 11import io 12import os 13import tempfile 14import shutil 15import sys 16 17from functools import partial 18from itertools import chain 19from operator import itemgetter 20 21# Skip all tests which use features not supported in SpiderMonkey. 22UNSUPPORTED_FEATURES = set( 23 [ 24 "tail-call-optimization", 25 "Intl.DateTimeFormat-quarter", 26 "Intl.Segmenter", 27 "Atomics.waitAsync", 28 "legacy-regexp", 29 "import-assertions", 30 ] 31) 32FEATURE_CHECK_NEEDED = { 33 "Atomics": "!this.hasOwnProperty('Atomics')", 34 "FinalizationRegistry": "!this.hasOwnProperty('FinalizationRegistry')", 35 "SharedArrayBuffer": "!this.hasOwnProperty('SharedArrayBuffer')", 36 "WeakRef": "!this.hasOwnProperty('WeakRef')", 37} 38RELEASE_OR_BETA = set([]) 39SHELL_OPTIONS = { 40 "top-level-await": "--enable-top-level-await", 41} 42 43 44@contextlib.contextmanager 45def TemporaryDirectory(): 46 tmpDir = tempfile.mkdtemp() 47 try: 48 yield tmpDir 49 finally: 50 shutil.rmtree(tmpDir) 51 52 53def loadTest262Parser(test262Dir): 54 """ 55 Loads the test262 test record parser. 56 """ 57 import imp 58 59 fileObj = None 60 try: 61 moduleName = "parseTestRecord" 62 packagingDir = os.path.join(test262Dir, "tools", "packaging") 63 (fileObj, pathName, description) = imp.find_module(moduleName, [packagingDir]) 64 return imp.load_module(moduleName, fileObj, pathName, description) 65 finally: 66 if fileObj: 67 fileObj.close() 68 69 70def tryParseTestFile(test262parser, source, testName): 71 """ 72 Returns the result of test262parser.parseTestRecord() or None if a parser 73 error occured. 74 75 See <https://github.com/tc39/test262/blob/main/INTERPRETING.md> for an 76 overview of the returned test attributes. 77 """ 78 try: 79 return test262parser.parseTestRecord(source, testName) 80 except Exception as err: 81 print("Error '%s' in file: %s" % (err, testName), file=sys.stderr) 82 print("Please report this error to the test262 GitHub repository!") 83 return None 84 85 86def createRefTestEntry(options, skip, skipIf, error, isModule, isAsync): 87 """ 88 Returns the |reftest| tuple (terms, comments) from the input arguments. Or a 89 tuple of empty strings if no reftest entry is required. 90 """ 91 92 terms = [] 93 comments = [] 94 95 if options: 96 terms.extend(options) 97 98 if skip: 99 terms.append("skip") 100 comments.extend(skip) 101 102 if skipIf: 103 terms.append("skip-if(" + "||".join([cond for (cond, _) in skipIf]) + ")") 104 comments.extend([comment for (_, comment) in skipIf]) 105 106 if error: 107 terms.append("error:" + error) 108 109 if isModule: 110 terms.append("module") 111 112 if isAsync: 113 terms.append("async") 114 115 return (" ".join(terms), ", ".join(comments)) 116 117 118def createRefTestLine(terms, comments): 119 """ 120 Creates the |reftest| line using the given terms and comments. 121 """ 122 123 refTest = terms 124 if comments: 125 refTest += " -- " + comments 126 return refTest 127 128 129def createSource(testSource, refTest, prologue, epilogue): 130 """ 131 Returns the post-processed source for |testSource|. 132 """ 133 134 source = [] 135 136 # Add the |reftest| line. 137 if refTest: 138 source.append(b"// |reftest| " + refTest.encode("utf-8")) 139 140 # Prepend any directives if present. 141 if prologue: 142 source.append(prologue.encode("utf-8")) 143 144 source.append(testSource) 145 146 # Append the test epilogue, i.e. the call to "reportCompare". 147 # TODO: Does this conflict with raw tests? 148 if epilogue: 149 source.append(epilogue.encode("utf-8")) 150 source.append(b"") 151 152 return b"\n".join(source) 153 154 155def writeTestFile(test262OutDir, testFileName, source): 156 """ 157 Writes the test source to |test262OutDir|. 158 """ 159 160 with io.open(os.path.join(test262OutDir, testFileName), "wb") as output: 161 output.write(source) 162 163 164def addSuffixToFileName(fileName, suffix): 165 (filePath, ext) = os.path.splitext(fileName) 166 return filePath + suffix + ext 167 168 169def writeShellAndBrowserFiles( 170 test262OutDir, harnessDir, includesMap, localIncludesMap, relPath 171): 172 """ 173 Generate the shell.js and browser.js files for the test harness. 174 """ 175 176 # Find all includes from parent directories. 177 def findParentIncludes(): 178 parentIncludes = set() 179 current = relPath 180 while current: 181 (parent, child) = os.path.split(current) 182 if parent in includesMap: 183 parentIncludes.update(includesMap[parent]) 184 current = parent 185 return parentIncludes 186 187 # Find all includes, skipping includes already present in parent directories. 188 def findIncludes(): 189 parentIncludes = findParentIncludes() 190 for include in includesMap[relPath]: 191 if include not in parentIncludes: 192 yield include 193 194 def readIncludeFile(filePath): 195 with io.open(filePath, "rb") as includeFile: 196 return b"// file: %s\n%s" % ( 197 os.path.basename(filePath).encode("utf-8"), 198 includeFile.read(), 199 ) 200 201 localIncludes = localIncludesMap[relPath] if relPath in localIncludesMap else [] 202 203 # Concatenate all includes files. 204 includeSource = b"\n".join( 205 map( 206 readIncludeFile, 207 chain( 208 # The requested include files. 209 map(partial(os.path.join, harnessDir), sorted(findIncludes())), 210 # And additional local include files. 211 map(partial(os.path.join, os.getcwd()), sorted(localIncludes)), 212 ), 213 ) 214 ) 215 216 # Write the concatenated include sources to shell.js. 217 with io.open(os.path.join(test262OutDir, relPath, "shell.js"), "wb") as shellFile: 218 if includeSource: 219 shellFile.write(b"// GENERATED, DO NOT EDIT\n") 220 shellFile.write(includeSource) 221 222 # The browser.js file is always empty for test262 tests. 223 with io.open( 224 os.path.join(test262OutDir, relPath, "browser.js"), "wb" 225 ) as browserFile: 226 browserFile.write(b"") 227 228 229def pathStartsWith(path, *args): 230 prefix = os.path.join(*args) 231 return os.path.commonprefix([path, prefix]) == prefix 232 233 234def convertTestFile(test262parser, testSource, testName, includeSet, strictTests): 235 """ 236 Convert a test262 test to a compatible jstests test file. 237 """ 238 239 # The test record dictionary, its contents are explained in depth at 240 # <https://github.com/tc39/test262/blob/main/INTERPRETING.md>. 241 testRec = tryParseTestFile(test262parser, testSource.decode("utf-8"), testName) 242 243 # jsreftest meta data 244 refTestOptions = [] 245 refTestSkip = [] 246 refTestSkipIf = [] 247 248 # Skip all files which contain YAML errors. 249 if testRec is None: 250 refTestSkip.append("has YAML errors") 251 testRec = dict() 252 253 # onlyStrict is set when the test must only be run in strict mode. 254 onlyStrict = "onlyStrict" in testRec 255 256 # noStrict is set when the test must not be run in strict mode. 257 noStrict = "noStrict" in testRec 258 259 # The "raw" attribute is used in the default test262 runner to prevent 260 # prepending additional content (use-strict directive, harness files) 261 # before the actual test source code. 262 raw = "raw" in testRec 263 264 # Negative tests have additional meta-data to specify the error type and 265 # when the error is issued (runtime error or early parse error). We're 266 # currently ignoring the error phase attribute. 267 # testRec["negative"] == {type=<error name>, phase=parse|resolution|runtime} 268 isNegative = "negative" in testRec 269 assert not isNegative or type(testRec["negative"]) == dict 270 errorType = testRec["negative"]["type"] if isNegative else None 271 272 # Async tests are marked with the "async" attribute. 273 isAsync = "async" in testRec 274 275 # Test262 tests cannot be both "negative" and "async". (In principle a 276 # negative async test is permitted when the error phase is not "parse" or 277 # the error type is not SyntaxError, but no such tests exist now.) 278 assert not (isNegative and isAsync), ( 279 "Can't have both async and negative attributes: %s" % testName 280 ) 281 282 # Only async tests may use the $DONE function. However, negative parse 283 # tests may "use" the $DONE function (of course they don't actually use it!) 284 # without specifying the "async" attribute. Otherwise, $DONE must not 285 # appear in the test. 286 assert b"$DONE" not in testSource or isAsync or isNegative, ( 287 "Missing async attribute in: %s" % testName 288 ) 289 290 # When the "module" attribute is set, the source code is module code. 291 isModule = "module" in testRec 292 293 # CanBlockIsFalse is set when the test expects that the implementation 294 # cannot block on the main thread. 295 if "CanBlockIsFalse" in testRec: 296 refTestSkipIf.append(("xulRuntime.shell", "shell can block main thread")) 297 298 # CanBlockIsTrue is set when the test expects that the implementation 299 # can block on the main thread. 300 if "CanBlockIsTrue" in testRec: 301 refTestSkipIf.append(("!xulRuntime.shell", "browser cannot block main thread")) 302 303 # Skip tests with unsupported features. 304 if "features" in testRec: 305 unsupported = [f for f in testRec["features"] if f in UNSUPPORTED_FEATURES] 306 if unsupported: 307 refTestSkip.append("%s is not supported" % ",".join(unsupported)) 308 else: 309 releaseOrBeta = [f for f in testRec["features"] if f in RELEASE_OR_BETA] 310 if releaseOrBeta: 311 refTestSkipIf.append( 312 ( 313 "release_or_beta", 314 "%s is not released yet" % ",".join(releaseOrBeta), 315 ) 316 ) 317 318 featureCheckNeeded = [ 319 f for f in testRec["features"] if f in FEATURE_CHECK_NEEDED 320 ] 321 if featureCheckNeeded: 322 refTestSkipIf.append( 323 ( 324 "||".join( 325 [FEATURE_CHECK_NEEDED[f] for f in featureCheckNeeded] 326 ), 327 "%s is not enabled unconditionally" 328 % ",".join(featureCheckNeeded), 329 ) 330 ) 331 332 if ( 333 "Atomics" in testRec["features"] 334 and "SharedArrayBuffer" in testRec["features"] 335 ): 336 refTestSkipIf.append( 337 ( 338 "(this.hasOwnProperty('getBuildConfiguration')" 339 "&&getBuildConfiguration()['arm64-simulator'])", 340 "ARM64 Simulator cannot emulate atomics", 341 ) 342 ) 343 344 shellOptions = { 345 SHELL_OPTIONS[f] for f in testRec["features"] if f in SHELL_OPTIONS 346 } 347 if shellOptions: 348 refTestSkipIf.append(("!xulRuntime.shell", "requires shell-options")) 349 refTestOptions.extend( 350 ("shell-option({})".format(opt) for opt in sorted(shellOptions)) 351 ) 352 353 # Includes for every test file in a directory is collected in a single 354 # shell.js file per directory level. This is done to avoid adding all 355 # test harness files to the top level shell.js file. 356 if "includes" in testRec: 357 assert not raw, "Raw test with includes: %s" % testName 358 includeSet.update(testRec["includes"]) 359 360 # Add reportCompare() after all positive, synchronous tests. 361 if not isNegative and not isAsync: 362 testEpilogue = "reportCompare(0, 0);" 363 else: 364 testEpilogue = "" 365 366 (terms, comments) = createRefTestEntry( 367 refTestOptions, refTestSkip, refTestSkipIf, errorType, isModule, isAsync 368 ) 369 if raw: 370 refTest = "" 371 externRefTest = (terms, comments) 372 else: 373 refTest = createRefTestLine(terms, comments) 374 externRefTest = None 375 376 # Don't write a strict-mode variant for raw or module files. 377 noStrictVariant = raw or isModule 378 assert not (noStrictVariant and (onlyStrict or noStrict)), ( 379 "Unexpected onlyStrict or noStrict attribute: %s" % testName 380 ) 381 382 # Write non-strict mode test. 383 if noStrictVariant or noStrict or not onlyStrict: 384 testPrologue = "" 385 nonStrictSource = createSource(testSource, refTest, testPrologue, testEpilogue) 386 testFileName = testName 387 yield (testFileName, nonStrictSource, externRefTest) 388 389 # Write strict mode test. 390 if not noStrictVariant and (onlyStrict or (not noStrict and strictTests)): 391 testPrologue = "'use strict';" 392 strictSource = createSource(testSource, refTest, testPrologue, testEpilogue) 393 testFileName = testName 394 if not noStrict: 395 testFileName = addSuffixToFileName(testFileName, "-strict") 396 yield (testFileName, strictSource, externRefTest) 397 398 399def convertFixtureFile(fixtureSource, fixtureName): 400 """ 401 Convert a test262 fixture file to a compatible jstests test file. 402 """ 403 404 # jsreftest meta data 405 refTestOptions = [] 406 refTestSkip = ["not a test file"] 407 refTestSkipIf = [] 408 errorType = None 409 isModule = False 410 isAsync = False 411 412 (terms, comments) = createRefTestEntry( 413 refTestOptions, refTestSkip, refTestSkipIf, errorType, isModule, isAsync 414 ) 415 refTest = createRefTestLine(terms, comments) 416 417 source = createSource(fixtureSource, refTest, "", "") 418 externRefTest = None 419 yield (fixtureName, source, externRefTest) 420 421 422def process_test262(test262Dir, test262OutDir, strictTests, externManifests): 423 """ 424 Process all test262 files and converts them into jstests compatible tests. 425 """ 426 427 harnessDir = os.path.join(test262Dir, "harness") 428 testDir = os.path.join(test262Dir, "test") 429 test262parser = loadTest262Parser(test262Dir) 430 431 # Map of test262 subdirectories to the set of include files required for 432 # tests in that subdirectory. The includes for all tests in a subdirectory 433 # are merged into a single shell.js. 434 # map<dirname, set<includeFiles>> 435 includesMap = {} 436 437 # Additional local includes keyed by test262 directory names. The include 438 # files in this map must be located in the js/src/tests directory. 439 # map<dirname, list<includeFiles>> 440 localIncludesMap = {} 441 442 # The root directory contains required harness files and test262-host.js. 443 includesMap[""] = set(["sta.js", "assert.js"]) 444 localIncludesMap[""] = ["test262-host.js"] 445 446 # Also add files known to be used by many tests to the root shell.js file. 447 includesMap[""].update(["propertyHelper.js", "compareArray.js"]) 448 449 # Write the root shell.js file. 450 writeShellAndBrowserFiles( 451 test262OutDir, harnessDir, includesMap, localIncludesMap, "" 452 ) 453 454 # Additional explicit includes inserted at well-chosen locations to reduce 455 # code duplication in shell.js files. 456 explicitIncludes = {} 457 explicitIncludes[os.path.join("built-ins", "Atomics")] = [ 458 "testAtomics.js", 459 "testTypedArray.js", 460 ] 461 explicitIncludes[os.path.join("built-ins", "DataView")] = [ 462 "byteConversionValues.js" 463 ] 464 explicitIncludes[os.path.join("built-ins", "Promise")] = ["promiseHelper.js"] 465 explicitIncludes[os.path.join("built-ins", "TypedArray")] = [ 466 "byteConversionValues.js", 467 "detachArrayBuffer.js", 468 "nans.js", 469 ] 470 explicitIncludes[os.path.join("built-ins", "TypedArrays")] = [ 471 "detachArrayBuffer.js" 472 ] 473 474 # Process all test directories recursively. 475 for (dirPath, dirNames, fileNames) in os.walk(testDir): 476 relPath = os.path.relpath(dirPath, testDir) 477 if relPath == ".": 478 continue 479 480 # Skip creating a "prs" directory if it already exists 481 if relPath not in ("prs", "local") and not os.path.exists( 482 os.path.join(test262OutDir, relPath) 483 ): 484 os.makedirs(os.path.join(test262OutDir, relPath)) 485 486 includeSet = set() 487 includesMap[relPath] = includeSet 488 489 if relPath in explicitIncludes: 490 includeSet.update(explicitIncludes[relPath]) 491 492 # Convert each test file. 493 for fileName in fileNames: 494 filePath = os.path.join(dirPath, fileName) 495 testName = os.path.relpath(filePath, testDir) 496 497 # Copy non-test files as is. 498 (_, fileExt) = os.path.splitext(fileName) 499 if fileExt != ".js": 500 shutil.copyfile(filePath, os.path.join(test262OutDir, testName)) 501 continue 502 503 # Files ending with "_FIXTURE.js" are fixture files: 504 # https://github.com/tc39/test262/blob/main/INTERPRETING.md#modules 505 isFixtureFile = fileName.endswith("_FIXTURE.js") 506 507 # Read the original test source and preprocess it for the jstests harness. 508 with io.open(filePath, "rb") as testFile: 509 testSource = testFile.read() 510 511 if isFixtureFile: 512 convert = convertFixtureFile(testSource, testName) 513 else: 514 convert = convertTestFile( 515 test262parser, testSource, testName, includeSet, strictTests 516 ) 517 518 for (newFileName, newSource, externRefTest) in convert: 519 writeTestFile(test262OutDir, newFileName, newSource) 520 521 if externRefTest is not None: 522 externManifests.append( 523 { 524 "name": newFileName, 525 "reftest": externRefTest, 526 } 527 ) 528 529 # Add shell.js and browers.js files for the current directory. 530 writeShellAndBrowserFiles( 531 test262OutDir, harnessDir, includesMap, localIncludesMap, relPath 532 ) 533 534 535def fetch_local_changes(inDir, outDir, srcDir, strictTests): 536 """ 537 Fetch the changes from a local clone of Test262. 538 539 1. Get the list of file changes made by the current branch used on Test262 (srcDir). 540 2. Copy only the (A)dded, (C)opied, (M)odified, and (R)enamed files to inDir. 541 3. inDir is treated like a Test262 checkout, where files will be converted. 542 4. Fetches the current branch name to set the outDir. 543 5. Processed files will be added to `<outDir>/local/<branchName>`. 544 """ 545 import subprocess 546 547 # TODO: fail if it's in the default branch? or require a branch name? 548 549 # Checks for unstaged or non committed files. A clean branch provides a clean status. 550 status = subprocess.check_output( 551 ("git -C %s status --porcelain" % srcDir).split(" ") 552 ) 553 554 if status.strip(): 555 raise RuntimeError( 556 "Please commit files and cleanup the local test262 folder before importing files.\n" 557 "Current status: \n%s" % status 558 ) 559 560 # Captures the branch name to be used on the output 561 branchName = subprocess.check_output( 562 ("git -C %s rev-parse --abbrev-ref HEAD" % srcDir).split(" ") 563 ).split("\n")[0] 564 565 # Fetches the file names to import 566 files = subprocess.check_output( 567 ("git -C %s diff main --diff-filter=ACMR --name-only" % srcDir).split(" ") 568 ) 569 570 # Fetches the deleted files to print an output log. This can be used to 571 # set up the skip list, if necessary. 572 deletedFiles = subprocess.check_output( 573 ("git -C %s diff main --diff-filter=D --name-only" % srcDir).split(" ") 574 ) 575 576 # Fetches the modified files as well for logging to support maintenance 577 # in the skip list. 578 modifiedFiles = subprocess.check_output( 579 ("git -C %s diff main --diff-filter=M --name-only" % srcDir).split(" ") 580 ) 581 582 # Fetches the renamed files for the same reason, this avoids duplicate 583 # tests if running the new local folder and the general imported Test262 584 # files. 585 renamedFiles = subprocess.check_output( 586 ("git -C %s diff main --diff-filter=R --summary" % srcDir).split(" ") 587 ) 588 589 # Print some friendly output 590 print("From the branch %s in %s \n" % (branchName, srcDir)) 591 print("Files being copied to the local folder: \n%s" % files) 592 if deletedFiles: 593 print( 594 "Deleted files (use this list to update the skip list): \n%s" % deletedFiles 595 ) 596 if modifiedFiles: 597 print( 598 "Modified files (use this list to update the skip list): \n%s" 599 % modifiedFiles 600 ) 601 if renamedFiles: 602 print("Renamed files (already added with the new names): \n%s" % renamedFiles) 603 604 for f in files.splitlines(): 605 # Capture the subdirectories names to recreate the file tree 606 # TODO: join the file tree with -- instead of multiple subfolders? 607 fileTree = os.path.join(inDir, os.path.dirname(f)) 608 if not os.path.exists(fileTree): 609 os.makedirs(fileTree) 610 611 shutil.copyfile( 612 os.path.join(srcDir, f), os.path.join(fileTree, os.path.basename(f)) 613 ) 614 615 # Extras from Test262. Copy the current support folders - including the 616 # harness - for a proper conversion process 617 shutil.copytree(os.path.join(srcDir, "tools"), os.path.join(inDir, "tools")) 618 shutil.copytree(os.path.join(srcDir, "harness"), os.path.join(inDir, "harness")) 619 620 # Reset any older directory in the output using the same branch name 621 outDir = os.path.join(outDir, "local", branchName) 622 if os.path.isdir(outDir): 623 shutil.rmtree(outDir) 624 os.makedirs(outDir) 625 626 process_test262(inDir, outDir, strictTests, []) 627 628 629def fetch_pr_files(inDir, outDir, prNumber, strictTests): 630 import requests 631 632 prTestsOutDir = os.path.join(outDir, "prs", prNumber) 633 if os.path.isdir(prTestsOutDir): 634 print("Removing folder %s" % prTestsOutDir) 635 shutil.rmtree(prTestsOutDir) 636 os.makedirs(prTestsOutDir) 637 638 # Reuses current Test262 clone's harness and tools folders only, the clone's test/ 639 # folder can be discarded from here 640 shutil.rmtree(os.path.join(inDir, "test")) 641 642 prRequest = requests.get( 643 "https://api.github.com/repos/tc39/test262/pulls/%s" % prNumber 644 ) 645 prRequest.raise_for_status() 646 647 pr = prRequest.json() 648 649 if pr["state"] != "open": 650 # Closed PR, remove respective files from folder 651 return print("PR %s is closed" % prNumber) 652 653 files = requests.get( 654 "https://api.github.com/repos/tc39/test262/pulls/%s/files" % prNumber 655 ) 656 files.raise_for_status() 657 658 for item in files.json(): 659 if not item["filename"].startswith("test/"): 660 continue 661 662 filename = item["filename"] 663 fileStatus = item["status"] 664 665 print("%s %s" % (fileStatus, filename)) 666 667 # Do not add deleted files 668 if fileStatus == "removed": 669 continue 670 671 contents = requests.get(item["raw_url"]) 672 contents.raise_for_status() 673 674 fileText = contents.text 675 676 filePathDirs = os.path.join(inDir, *filename.split("/")[:-1]) 677 678 if not os.path.isdir(filePathDirs): 679 os.makedirs(filePathDirs) 680 681 with io.open(os.path.join(inDir, *filename.split("/")), "wb") as output_file: 682 output_file.write(fileText.encode("utf8")) 683 684 process_test262(inDir, prTestsOutDir, strictTests, []) 685 686 687def general_update(inDir, outDir, strictTests): 688 import subprocess 689 690 restoreLocalTestsDir = False 691 restorePrsTestsDir = False 692 localTestsOutDir = os.path.join(outDir, "local") 693 prsTestsOutDir = os.path.join(outDir, "prs") 694 695 # Stash test262/local and test262/prs. Currently the Test262 repo does not have any 696 # top-level subdirectories named "local" or "prs". 697 # This prevents these folders from being removed during the update process. 698 if os.path.isdir(localTestsOutDir): 699 shutil.move(localTestsOutDir, inDir) 700 restoreLocalTestsDir = True 701 702 if os.path.isdir(prsTestsOutDir): 703 shutil.move(prsTestsOutDir, inDir) 704 restorePrsTestsDir = True 705 706 # Create the output directory from scratch. 707 if os.path.isdir(outDir): 708 shutil.rmtree(outDir) 709 os.makedirs(outDir) 710 711 # Copy license file. 712 shutil.copyfile(os.path.join(inDir, "LICENSE"), os.path.join(outDir, "LICENSE")) 713 714 # Create the git info file. 715 with io.open(os.path.join(outDir, "GIT-INFO"), "w", encoding="utf-8") as info: 716 subprocess.check_call(["git", "-C", inDir, "log", "-1"], stdout=info) 717 718 # Copy the test files. 719 externManifests = [] 720 process_test262(inDir, outDir, strictTests, externManifests) 721 722 # Create the external reftest manifest file. 723 with io.open(os.path.join(outDir, "jstests.list"), "wb") as manifestFile: 724 manifestFile.write(b"# GENERATED, DO NOT EDIT\n\n") 725 for externManifest in sorted(externManifests, key=itemgetter("name")): 726 (terms, comments) = externManifest["reftest"] 727 if terms: 728 entry = "%s script %s%s\n" % ( 729 terms, 730 externManifest["name"], 731 (" # %s" % comments) if comments else "", 732 ) 733 manifestFile.write(entry.encode("utf-8")) 734 735 # Move test262/local back. 736 if restoreLocalTestsDir: 737 shutil.move(os.path.join(inDir, "local"), outDir) 738 739 # Restore test262/prs if necessary after a general Test262 update. 740 if restorePrsTestsDir: 741 shutil.move(os.path.join(inDir, "prs"), outDir) 742 743 744def update_test262(args): 745 import subprocess 746 747 url = args.url 748 branch = args.branch 749 revision = args.revision 750 outDir = args.out 751 prNumber = args.pull 752 srcDir = args.local 753 754 if not os.path.isabs(outDir): 755 outDir = os.path.join(os.getcwd(), outDir) 756 757 strictTests = args.strict 758 759 # Download the requested branch in a temporary directory. 760 with TemporaryDirectory() as inDir: 761 # If it's a local import, skip the git clone parts. 762 if srcDir: 763 return fetch_local_changes(inDir, outDir, srcDir, strictTests) 764 765 if revision == "HEAD": 766 subprocess.check_call( 767 ["git", "clone", "--depth=1", "--branch=%s" % branch, url, inDir] 768 ) 769 else: 770 subprocess.check_call( 771 ["git", "clone", "--single-branch", "--branch=%s" % branch, url, inDir] 772 ) 773 subprocess.check_call(["git", "-C", inDir, "reset", "--hard", revision]) 774 775 # If a PR number is provided, fetches only the new and modified files 776 # from that PR. It also creates a new folder for that PR or replaces if 777 # it already exists, without updating the regular Test262 tests. 778 if prNumber: 779 return fetch_pr_files(inDir, outDir, prNumber, strictTests) 780 781 # Without a PR or a local import, follows through a regular copy. 782 general_update(inDir, outDir, strictTests) 783 784 785if __name__ == "__main__": 786 import argparse 787 788 # This script must be run from js/src/tests to work correctly. 789 if "/".join(os.path.normpath(os.getcwd()).split(os.sep)[-3:]) != "js/src/tests": 790 raise RuntimeError("%s must be run from js/src/tests" % sys.argv[0]) 791 792 parser = argparse.ArgumentParser(description="Update the test262 test suite.") 793 parser.add_argument( 794 "--url", 795 default="git://github.com/tc39/test262.git", 796 help="URL to git repository (default: %(default)s)", 797 ) 798 parser.add_argument( 799 "--branch", default="main", help="Git branch (default: %(default)s)" 800 ) 801 parser.add_argument( 802 "--revision", default="HEAD", help="Git revision (default: %(default)s)" 803 ) 804 parser.add_argument( 805 "--out", 806 default="test262", 807 help="Output directory. Any existing directory will be removed!" 808 "(default: %(default)s)", 809 ) 810 parser.add_argument( 811 "--pull", help="Import contents from a Pull Request specified by its number" 812 ) 813 parser.add_argument( 814 "--local", 815 help="Import new and modified contents from a local folder, a new folder " 816 "will be created on local/branch_name", 817 ) 818 parser.add_argument( 819 "--strict", 820 default=False, 821 action="store_true", 822 help="Generate additional strict mode tests. Not enabled by default.", 823 ) 824 parser.set_defaults(func=update_test262) 825 args = parser.parse_args() 826 args.func(args) 827