Backed out 3 changesets (bug 1892041) for causing failures on async-module-does-not...
[gecko.git] / js / src / tests / test262-update.py
blobd45d639ebb9eef648dff64bbd13bf14cf2bc907b
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
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/.
8 import contextlib
9 import io
10 import os
11 import shutil
12 import sys
13 import tempfile
14 from functools import partial
15 from itertools import chain
16 from operator import itemgetter
18 # Skip all tests which use features not supported in SpiderMonkey.
19 UNSUPPORTED_FEATURES = set(
21 "tail-call-optimization",
22 "Intl.Locale-info", # Bug 1693576
23 "Intl.DurationFormat", # Bug 1648139
24 "Atomics.waitAsync", # Bug 1467846
25 "legacy-regexp", # Bug 1306461
26 "regexp-duplicate-named-groups", # Bug 1773135
27 "set-methods", # Bug 1805038
30 FEATURE_CHECK_NEEDED = {
31 "Atomics": "!this.hasOwnProperty('Atomics')",
32 "FinalizationRegistry": "!this.hasOwnProperty('FinalizationRegistry')",
33 "SharedArrayBuffer": "!this.hasOwnProperty('SharedArrayBuffer')",
34 "Temporal": "!this.hasOwnProperty('Temporal')",
35 "WeakRef": "!this.hasOwnProperty('WeakRef')",
36 "decorators": "!(this.hasOwnProperty('getBuildConfiguration')&&getBuildConfiguration('decorators'))", # Bug 1435869
37 "iterator-helpers": "!this.hasOwnProperty('Iterator')", # Bug 1568906
38 "Intl.Segmenter": "!Intl.Segmenter", # Bug 1423593
39 "resizable-arraybuffer": "!ArrayBuffer.prototype.resize", # Bug 1670026
40 "uint8array-base64": "!Uint8Array.fromBase64", # Bug 1862220
41 "json-parse-with-source": "!JSON.hasOwnProperty('isRawJSON')", # Bug 1658310
43 RELEASE_OR_BETA = set(
45 "symbols-as-weakmap-keys",
48 SHELL_OPTIONS = {
49 "import-assertions": "--enable-import-assertions",
50 "import-attributes": "--enable-import-attributes",
51 "ShadowRealm": "--enable-shadow-realms",
52 "iterator-helpers": "--enable-iterator-helpers",
53 "symbols-as-weakmap-keys": "--enable-symbols-as-weakmap-keys",
54 "resizable-arraybuffer": "--enable-arraybuffer-resizable",
55 "uint8array-base64": "--enable-uint8array-base64",
56 "json-parse-with-source": "--enable-json-parse-with-source",
60 @contextlib.contextmanager
61 def TemporaryDirectory():
62 tmpDir = tempfile.mkdtemp()
63 try:
64 yield tmpDir
65 finally:
66 shutil.rmtree(tmpDir)
69 def loadTest262Parser(test262Dir):
70 """
71 Loads the test262 test record parser.
72 """
73 import importlib.machinery
74 import importlib.util
76 packagingDir = os.path.join(test262Dir, "tools", "packaging")
77 moduleName = "parseTestRecord"
79 # Create a FileFinder to load Python source files.
80 loader_details = (
81 importlib.machinery.SourceFileLoader,
82 importlib.machinery.SOURCE_SUFFIXES,
84 finder = importlib.machinery.FileFinder(packagingDir, loader_details)
86 # Find the module spec.
87 spec = finder.find_spec(moduleName)
88 if spec is None:
89 raise RuntimeError("Can't find parseTestRecord module")
91 # Create and execute the module.
92 module = importlib.util.module_from_spec(spec)
93 spec.loader.exec_module(module)
95 # Return the executed module
96 return module
99 def tryParseTestFile(test262parser, source, testName):
101 Returns the result of test262parser.parseTestRecord() or None if a parser
102 error occured.
104 See <https://github.com/tc39/test262/blob/main/INTERPRETING.md> for an
105 overview of the returned test attributes.
107 try:
108 return test262parser.parseTestRecord(source, testName)
109 except Exception as err:
110 print("Error '%s' in file: %s" % (err, testName), file=sys.stderr)
111 print("Please report this error to the test262 GitHub repository!")
112 return None
115 def createRefTestEntry(options, skip, skipIf, error, isModule, isAsync):
117 Returns the |reftest| tuple (terms, comments) from the input arguments. Or a
118 tuple of empty strings if no reftest entry is required.
121 terms = []
122 comments = []
124 if options:
125 terms.extend(options)
127 if skip:
128 terms.append("skip")
129 comments.extend(skip)
131 if skipIf:
132 terms.append("skip-if(" + "||".join([cond for (cond, _) in skipIf]) + ")")
133 comments.extend([comment for (_, comment) in skipIf])
135 if error:
136 terms.append("error:" + error)
138 if isModule:
139 terms.append("module")
141 if isAsync:
142 terms.append("async")
144 return (" ".join(terms), ", ".join(comments))
147 def createRefTestLine(terms, comments):
149 Creates the |reftest| line using the given terms and comments.
152 refTest = terms
153 if comments:
154 refTest += " -- " + comments
155 return refTest
158 def createSource(testSource, refTest, prologue, epilogue):
160 Returns the post-processed source for |testSource|.
163 source = []
165 # Add the |reftest| line.
166 if refTest:
167 source.append(b"// |reftest| " + refTest.encode("utf-8"))
169 # Prepend any directives if present.
170 if prologue:
171 source.append(prologue.encode("utf-8"))
173 source.append(testSource)
175 # Append the test epilogue, i.e. the call to "reportCompare".
176 # TODO: Does this conflict with raw tests?
177 if epilogue:
178 source.append(epilogue.encode("utf-8"))
179 source.append(b"")
181 return b"\n".join(source)
184 def writeTestFile(test262OutDir, testFileName, source):
186 Writes the test source to |test262OutDir|.
189 with io.open(os.path.join(test262OutDir, testFileName), "wb") as output:
190 output.write(source)
193 def addSuffixToFileName(fileName, suffix):
194 (filePath, ext) = os.path.splitext(fileName)
195 return filePath + suffix + ext
198 def writeShellAndBrowserFiles(
199 test262OutDir, harnessDir, includesMap, localIncludesMap, relPath
202 Generate the shell.js and browser.js files for the test harness.
205 # Find all includes from parent directories.
206 def findParentIncludes():
207 parentIncludes = set()
208 current = relPath
209 while current:
210 (parent, child) = os.path.split(current)
211 if parent in includesMap:
212 parentIncludes.update(includesMap[parent])
213 current = parent
214 return parentIncludes
216 # Find all includes, skipping includes already present in parent directories.
217 def findIncludes():
218 parentIncludes = findParentIncludes()
219 for include in includesMap[relPath]:
220 if include not in parentIncludes:
221 yield include
223 def readIncludeFile(filePath):
224 with io.open(filePath, "rb") as includeFile:
225 return b"// file: %s\n%s" % (
226 os.path.basename(filePath).encode("utf-8"),
227 includeFile.read(),
230 localIncludes = localIncludesMap[relPath] if relPath in localIncludesMap else []
232 # Concatenate all includes files.
233 includeSource = b"\n".join(
234 map(
235 readIncludeFile,
236 chain(
237 # The requested include files.
238 map(partial(os.path.join, harnessDir), sorted(findIncludes())),
239 # And additional local include files.
240 map(partial(os.path.join, os.getcwd()), sorted(localIncludes)),
245 # Write the concatenated include sources to shell.js.
246 with io.open(os.path.join(test262OutDir, relPath, "shell.js"), "wb") as shellFile:
247 if includeSource:
248 shellFile.write(b"// GENERATED, DO NOT EDIT\n")
249 shellFile.write(includeSource)
251 # The browser.js file is always empty for test262 tests.
252 with io.open(
253 os.path.join(test262OutDir, relPath, "browser.js"), "wb"
254 ) as browserFile:
255 browserFile.write(b"")
258 def pathStartsWith(path, *args):
259 prefix = os.path.join(*args)
260 return os.path.commonprefix([path, prefix]) == prefix
263 def convertTestFile(test262parser, testSource, testName, includeSet, strictTests):
265 Convert a test262 test to a compatible jstests test file.
268 # The test record dictionary, its contents are explained in depth at
269 # <https://github.com/tc39/test262/blob/main/INTERPRETING.md>.
270 testRec = tryParseTestFile(test262parser, testSource.decode("utf-8"), testName)
272 # jsreftest meta data
273 refTestOptions = []
274 refTestSkip = []
275 refTestSkipIf = []
277 # Skip all files which contain YAML errors.
278 if testRec is None:
279 refTestSkip.append("has YAML errors")
280 testRec = dict()
282 # onlyStrict is set when the test must only be run in strict mode.
283 onlyStrict = "onlyStrict" in testRec
285 # noStrict is set when the test must not be run in strict mode.
286 noStrict = "noStrict" in testRec
288 # The "raw" attribute is used in the default test262 runner to prevent
289 # prepending additional content (use-strict directive, harness files)
290 # before the actual test source code.
291 raw = "raw" in testRec
293 # Negative tests have additional meta-data to specify the error type and
294 # when the error is issued (runtime error or early parse error). We're
295 # currently ignoring the error phase attribute.
296 # testRec["negative"] == {type=<error name>, phase=parse|resolution|runtime}
297 isNegative = "negative" in testRec
298 assert not isNegative or type(testRec["negative"]) == dict
299 errorType = testRec["negative"]["type"] if isNegative else None
301 # Async tests are marked with the "async" attribute.
302 isAsync = "async" in testRec
304 # Test262 tests cannot be both "negative" and "async". (In principle a
305 # negative async test is permitted when the error phase is not "parse" or
306 # the error type is not SyntaxError, but no such tests exist now.)
307 assert not (isNegative and isAsync), (
308 "Can't have both async and negative attributes: %s" % testName
311 # Only async tests may use the $DONE or asyncTest function. However,
312 # negative parse tests may "use" the $DONE (or asyncTest) function (of
313 # course they don't actually use it!) without specifying the "async"
314 # attribute. Otherwise, neither $DONE nor asyncTest must appear in the test.
316 # Some "harness" tests redefine $DONE, so skip this check when the test file
317 # is in the "harness" directory.
318 assert (
319 (b"$DONE" not in testSource and b"asyncTest" not in testSource)
320 or isAsync
321 or isNegative
322 or testName.split(os.path.sep)[0] == "harness"
323 ), ("Missing async attribute in: %s" % testName)
325 # When the "module" attribute is set, the source code is module code.
326 isModule = "module" in testRec
328 # CanBlockIsFalse is set when the test expects that the implementation
329 # cannot block on the main thread.
330 if "CanBlockIsFalse" in testRec:
331 refTestSkipIf.append(("xulRuntime.shell", "shell can block main thread"))
333 # CanBlockIsTrue is set when the test expects that the implementation
334 # can block on the main thread.
335 if "CanBlockIsTrue" in testRec:
336 refTestSkipIf.append(("!xulRuntime.shell", "browser cannot block main thread"))
338 # Skip tests with unsupported features.
339 if "features" in testRec:
340 unsupported = [f for f in testRec["features"] if f in UNSUPPORTED_FEATURES]
341 if unsupported:
342 refTestSkip.append("%s is not supported" % ",".join(unsupported))
343 else:
344 releaseOrBeta = [f for f in testRec["features"] if f in RELEASE_OR_BETA]
345 if releaseOrBeta:
346 refTestSkipIf.append(
348 "release_or_beta",
349 "%s is not released yet" % ",".join(releaseOrBeta),
353 featureCheckNeeded = [
354 f for f in testRec["features"] if f in FEATURE_CHECK_NEEDED
356 if featureCheckNeeded:
357 refTestSkipIf.append(
359 "||".join(
360 [FEATURE_CHECK_NEEDED[f] for f in featureCheckNeeded]
362 "%s is not enabled unconditionally"
363 % ",".join(featureCheckNeeded),
367 if (
368 "Atomics" in testRec["features"]
369 and "SharedArrayBuffer" in testRec["features"]
371 refTestSkipIf.append(
373 "(this.hasOwnProperty('getBuildConfiguration')"
374 "&&getBuildConfiguration('arm64-simulator'))",
375 "ARM64 Simulator cannot emulate atomics",
379 shellOptions = {
380 SHELL_OPTIONS[f] for f in testRec["features"] if f in SHELL_OPTIONS
382 if shellOptions:
383 refTestSkipIf.append(("!xulRuntime.shell", "requires shell-options"))
384 refTestOptions.extend(
385 ("shell-option({})".format(opt) for opt in sorted(shellOptions))
388 # Includes for every test file in a directory is collected in a single
389 # shell.js file per directory level. This is done to avoid adding all
390 # test harness files to the top level shell.js file.
391 if "includes" in testRec:
392 assert not raw, "Raw test with includes: %s" % testName
393 includeSet.update(testRec["includes"])
395 # Add reportCompare() after all positive, synchronous tests.
396 if not isNegative and not isAsync:
397 testEpilogue = "reportCompare(0, 0);"
398 else:
399 testEpilogue = ""
401 if raw:
402 refTestOptions.append("test262-raw")
404 (terms, comments) = createRefTestEntry(
405 refTestOptions, refTestSkip, refTestSkipIf, errorType, isModule, isAsync
407 if raw:
408 refTest = ""
409 externRefTest = (terms, comments)
410 else:
411 refTest = createRefTestLine(terms, comments)
412 externRefTest = None
414 # Don't write a strict-mode variant for raw or module files.
415 noStrictVariant = raw or isModule
416 assert not (noStrictVariant and (onlyStrict or noStrict)), (
417 "Unexpected onlyStrict or noStrict attribute: %s" % testName
420 # Write non-strict mode test.
421 if noStrictVariant or noStrict or not onlyStrict:
422 testPrologue = ""
423 nonStrictSource = createSource(testSource, refTest, testPrologue, testEpilogue)
424 testFileName = testName
425 yield (testFileName, nonStrictSource, externRefTest)
427 # Write strict mode test.
428 if not noStrictVariant and (onlyStrict or (not noStrict and strictTests)):
429 testPrologue = "'use strict';"
430 strictSource = createSource(testSource, refTest, testPrologue, testEpilogue)
431 testFileName = testName
432 if not noStrict:
433 testFileName = addSuffixToFileName(testFileName, "-strict")
434 yield (testFileName, strictSource, externRefTest)
437 def convertFixtureFile(fixtureSource, fixtureName):
439 Convert a test262 fixture file to a compatible jstests test file.
442 # jsreftest meta data
443 refTestOptions = []
444 refTestSkip = ["not a test file"]
445 refTestSkipIf = []
446 errorType = None
447 isModule = False
448 isAsync = False
450 (terms, comments) = createRefTestEntry(
451 refTestOptions, refTestSkip, refTestSkipIf, errorType, isModule, isAsync
453 refTest = createRefTestLine(terms, comments)
455 source = createSource(fixtureSource, refTest, "", "")
456 externRefTest = None
457 yield (fixtureName, source, externRefTest)
460 def process_test262(test262Dir, test262OutDir, strictTests, externManifests):
462 Process all test262 files and converts them into jstests compatible tests.
465 harnessDir = os.path.join(test262Dir, "harness")
466 testDir = os.path.join(test262Dir, "test")
467 test262parser = loadTest262Parser(test262Dir)
469 # Map of test262 subdirectories to the set of include files required for
470 # tests in that subdirectory. The includes for all tests in a subdirectory
471 # are merged into a single shell.js.
472 # map<dirname, set<includeFiles>>
473 includesMap = {}
475 # Additional local includes keyed by test262 directory names. The include
476 # files in this map must be located in the js/src/tests directory.
477 # map<dirname, list<includeFiles>>
478 localIncludesMap = {}
480 # The root directory contains required harness files and test262-host.js.
481 includesMap[""] = set(["sta.js", "assert.js"])
482 localIncludesMap[""] = ["test262-host.js"]
484 # Also add files known to be used by many tests to the root shell.js file.
485 includesMap[""].update(["propertyHelper.js", "compareArray.js"])
487 # Write the root shell.js file.
488 writeShellAndBrowserFiles(
489 test262OutDir, harnessDir, includesMap, localIncludesMap, ""
492 # Additional explicit includes inserted at well-chosen locations to reduce
493 # code duplication in shell.js files.
494 explicitIncludes = {}
495 explicitIncludes[os.path.join("built-ins", "Atomics")] = [
496 "testAtomics.js",
497 "testTypedArray.js",
499 explicitIncludes[os.path.join("built-ins", "DataView")] = [
500 "byteConversionValues.js"
502 explicitIncludes[os.path.join("built-ins", "Promise")] = ["promiseHelper.js"]
503 explicitIncludes[os.path.join("built-ins", "Temporal")] = ["temporalHelpers.js"]
504 explicitIncludes[os.path.join("built-ins", "TypedArray")] = [
505 "byteConversionValues.js",
506 "detachArrayBuffer.js",
507 "nans.js",
509 explicitIncludes[os.path.join("built-ins", "TypedArrays")] = [
510 "detachArrayBuffer.js"
512 explicitIncludes[os.path.join("built-ins", "Temporal")] = ["temporalHelpers.js"]
514 # Process all test directories recursively.
515 for dirPath, dirNames, fileNames in os.walk(testDir):
516 relPath = os.path.relpath(dirPath, testDir)
517 if relPath == ".":
518 continue
520 # Skip creating a "prs" directory if it already exists
521 if relPath not in ("prs", "local") and not os.path.exists(
522 os.path.join(test262OutDir, relPath)
524 os.makedirs(os.path.join(test262OutDir, relPath))
526 includeSet = set()
527 includesMap[relPath] = includeSet
529 if relPath in explicitIncludes:
530 includeSet.update(explicitIncludes[relPath])
532 # Convert each test file.
533 for fileName in fileNames:
534 filePath = os.path.join(dirPath, fileName)
535 testName = os.path.relpath(filePath, testDir)
537 # Copy non-test files as is.
538 (_, fileExt) = os.path.splitext(fileName)
539 if fileExt != ".js":
540 shutil.copyfile(filePath, os.path.join(test262OutDir, testName))
541 continue
543 # Files ending with "_FIXTURE.js" are fixture files:
544 # https://github.com/tc39/test262/blob/main/INTERPRETING.md#modules
545 isFixtureFile = fileName.endswith("_FIXTURE.js")
547 # Read the original test source and preprocess it for the jstests harness.
548 with io.open(filePath, "rb") as testFile:
549 testSource = testFile.read()
551 if isFixtureFile:
552 convert = convertFixtureFile(testSource, testName)
553 else:
554 convert = convertTestFile(
555 test262parser, testSource, testName, includeSet, strictTests
558 for newFileName, newSource, externRefTest in convert:
559 writeTestFile(test262OutDir, newFileName, newSource)
561 if externRefTest is not None:
562 externManifests.append(
564 "name": newFileName,
565 "reftest": externRefTest,
569 # Add shell.js and browers.js files for the current directory.
570 writeShellAndBrowserFiles(
571 test262OutDir, harnessDir, includesMap, localIncludesMap, relPath
575 def fetch_local_changes(inDir, outDir, srcDir, strictTests):
577 Fetch the changes from a local clone of Test262.
579 1. Get the list of file changes made by the current branch used on Test262 (srcDir).
580 2. Copy only the (A)dded, (C)opied, (M)odified, and (R)enamed files to inDir.
581 3. inDir is treated like a Test262 checkout, where files will be converted.
582 4. Fetches the current branch name to set the outDir.
583 5. Processed files will be added to `<outDir>/local/<branchName>`.
585 import subprocess
587 # TODO: fail if it's in the default branch? or require a branch name?
588 # Checks for unstaged or non committed files. A clean branch provides a clean status.
589 status = subprocess.check_output(
590 ("git -C %s status --porcelain" % srcDir).split(" ")
593 if status.strip():
594 raise RuntimeError(
595 "Please commit files and cleanup the local test262 folder before importing files.\n"
596 "Current status: \n%s" % status
599 # Captures the branch name to be used on the output
600 branchName = subprocess.check_output(
601 ("git -C %s rev-parse --abbrev-ref HEAD" % srcDir).split(" ")
602 ).split("\n")[0]
604 # Fetches the file names to import
605 files = subprocess.check_output(
606 ("git -C %s diff main --diff-filter=ACMR --name-only" % srcDir).split(" ")
609 # Fetches the deleted files to print an output log. This can be used to
610 # set up the skip list, if necessary.
611 deletedFiles = subprocess.check_output(
612 ("git -C %s diff main --diff-filter=D --name-only" % srcDir).split(" ")
615 # Fetches the modified files as well for logging to support maintenance
616 # in the skip list.
617 modifiedFiles = subprocess.check_output(
618 ("git -C %s diff main --diff-filter=M --name-only" % srcDir).split(" ")
621 # Fetches the renamed files for the same reason, this avoids duplicate
622 # tests if running the new local folder and the general imported Test262
623 # files.
624 renamedFiles = subprocess.check_output(
625 ("git -C %s diff main --diff-filter=R --summary" % srcDir).split(" ")
628 # Print some friendly output
629 print("From the branch %s in %s \n" % (branchName, srcDir))
630 print("Files being copied to the local folder: \n%s" % files)
631 if deletedFiles:
632 print(
633 "Deleted files (use this list to update the skip list): \n%s" % deletedFiles
635 if modifiedFiles:
636 print(
637 "Modified files (use this list to update the skip list): \n%s"
638 % modifiedFiles
640 if renamedFiles:
641 print("Renamed files (already added with the new names): \n%s" % renamedFiles)
643 for f in files.splitlines():
644 # Capture the subdirectories names to recreate the file tree
645 # TODO: join the file tree with -- instead of multiple subfolders?
646 fileTree = os.path.join(inDir, os.path.dirname(f))
647 if not os.path.exists(fileTree):
648 os.makedirs(fileTree)
650 shutil.copyfile(
651 os.path.join(srcDir, f), os.path.join(fileTree, os.path.basename(f))
654 # Extras from Test262. Copy the current support folders - including the
655 # harness - for a proper conversion process
656 shutil.copytree(os.path.join(srcDir, "tools"), os.path.join(inDir, "tools"))
657 shutil.copytree(os.path.join(srcDir, "harness"), os.path.join(inDir, "harness"))
659 # Reset any older directory in the output using the same branch name
660 outDir = os.path.join(outDir, "local", branchName)
661 if os.path.isdir(outDir):
662 shutil.rmtree(outDir)
663 os.makedirs(outDir)
665 process_test262(inDir, outDir, strictTests, [])
668 def fetch_pr_files(inDir, outDir, prNumber, strictTests):
669 import requests
671 prTestsOutDir = os.path.join(outDir, "prs", prNumber)
672 if os.path.isdir(prTestsOutDir):
673 print("Removing folder %s" % prTestsOutDir)
674 shutil.rmtree(prTestsOutDir)
675 os.makedirs(prTestsOutDir)
677 # Reuses current Test262 clone's harness and tools folders only, the clone's test/
678 # folder can be discarded from here
679 shutil.rmtree(os.path.join(inDir, "test"))
681 prRequest = requests.get(
682 "https://api.github.com/repos/tc39/test262/pulls/%s" % prNumber
684 prRequest.raise_for_status()
686 pr = prRequest.json()
688 if pr["state"] != "open":
689 # Closed PR, remove respective files from folder
690 return print("PR %s is closed" % prNumber)
692 url = "https://api.github.com/repos/tc39/test262/pulls/%s/files" % prNumber
693 hasNext = True
695 while hasNext:
696 files = requests.get(url)
697 files.raise_for_status()
699 for item in files.json():
700 if not item["filename"].startswith("test/"):
701 continue
703 filename = item["filename"]
704 fileStatus = item["status"]
706 print("%s %s" % (fileStatus, filename))
708 # Do not add deleted files
709 if fileStatus == "removed":
710 continue
712 contents = requests.get(item["raw_url"])
713 contents.raise_for_status()
715 fileText = contents.text
717 filePathDirs = os.path.join(inDir, *filename.split("/")[:-1])
719 if not os.path.isdir(filePathDirs):
720 os.makedirs(filePathDirs)
722 with io.open(
723 os.path.join(inDir, *filename.split("/")), "wb"
724 ) as output_file:
725 output_file.write(fileText.encode("utf8"))
727 hasNext = False
729 # Check if the pull request changes are split over multiple pages.
730 if "link" in files.headers:
731 link = files.headers["link"]
733 # The links are comma separated and the entries within a link are separated by a
734 # semicolon. For example the first two links entries for PR 3199:
736 # https://api.github.com/repos/tc39/test262/pulls/3199/files
737 # """
738 # <https://api.github.com/repositories/16147933/pulls/3199/files?page=2>; rel="next",
739 # <https://api.github.com/repositories/16147933/pulls/3199/files?page=14>; rel="last"
740 # """
742 # https://api.github.com/repositories/16147933/pulls/3199/files?page=2
743 # """
744 # <https://api.github.com/repositories/16147933/pulls/3199/files?page=1>; rel="prev",
745 # <https://api.github.com/repositories/16147933/pulls/3199/files?page=3>; rel="next",
746 # <https://api.github.com/repositories/16147933/pulls/3199/files?page=14>; rel="last",
747 # <https://api.github.com/repositories/16147933/pulls/3199/files?page=1>; rel="first"
748 # """
750 for pages in link.split(", "):
751 (pageUrl, rel) = pages.split("; ")
753 assert pageUrl[0] == "<"
754 assert pageUrl[-1] == ">"
756 # Remove the angle brackets around the URL.
757 pageUrl = pageUrl[1:-1]
759 # Make sure we only request data from github and not some other place.
760 assert pageUrl.startswith("https://api.github.com/")
762 # Ensure the relative URL marker has the expected format.
763 assert (
764 rel == 'rel="prev"'
765 or rel == 'rel="next"'
766 or rel == 'rel="first"'
767 or rel == 'rel="last"'
770 # We only need the URL for the next page.
771 if rel == 'rel="next"':
772 url = pageUrl
773 hasNext = True
775 process_test262(inDir, prTestsOutDir, strictTests, [])
778 def general_update(inDir, outDir, strictTests):
779 import subprocess
781 restoreLocalTestsDir = False
782 restorePrsTestsDir = False
783 localTestsOutDir = os.path.join(outDir, "local")
784 prsTestsOutDir = os.path.join(outDir, "prs")
786 # Stash test262/local and test262/prs. Currently the Test262 repo does not have any
787 # top-level subdirectories named "local" or "prs".
788 # This prevents these folders from being removed during the update process.
789 if os.path.isdir(localTestsOutDir):
790 shutil.move(localTestsOutDir, inDir)
791 restoreLocalTestsDir = True
793 if os.path.isdir(prsTestsOutDir):
794 shutil.move(prsTestsOutDir, inDir)
795 restorePrsTestsDir = True
797 # Create the output directory from scratch.
798 if os.path.isdir(outDir):
799 shutil.rmtree(outDir)
800 os.makedirs(outDir)
802 # Copy license file.
803 shutil.copyfile(os.path.join(inDir, "LICENSE"), os.path.join(outDir, "LICENSE"))
805 # Create the git info file.
806 with io.open(os.path.join(outDir, "GIT-INFO"), "w", encoding="utf-8") as info:
807 subprocess.check_call(["git", "-C", inDir, "log", "-1"], stdout=info)
809 # Copy the test files.
810 externManifests = []
811 process_test262(inDir, outDir, strictTests, externManifests)
813 # Create the external reftest manifest file.
814 with io.open(os.path.join(outDir, "jstests.list"), "wb") as manifestFile:
815 manifestFile.write(b"# GENERATED, DO NOT EDIT\n\n")
816 for externManifest in sorted(externManifests, key=itemgetter("name")):
817 (terms, comments) = externManifest["reftest"]
818 if terms:
819 entry = "%s script %s%s\n" % (
820 terms,
821 externManifest["name"],
822 (" # %s" % comments) if comments else "",
824 manifestFile.write(entry.encode("utf-8"))
826 # Move test262/local back.
827 if restoreLocalTestsDir:
828 shutil.move(os.path.join(inDir, "local"), outDir)
830 # Restore test262/prs if necessary after a general Test262 update.
831 if restorePrsTestsDir:
832 shutil.move(os.path.join(inDir, "prs"), outDir)
835 def update_test262(args):
836 import subprocess
838 url = args.url
839 branch = args.branch
840 revision = args.revision
841 outDir = args.out
842 prNumber = args.pull
843 srcDir = args.local
845 if not os.path.isabs(outDir):
846 outDir = os.path.join(os.getcwd(), outDir)
848 strictTests = args.strict
850 # Download the requested branch in a temporary directory.
851 with TemporaryDirectory() as inDir:
852 # If it's a local import, skip the git clone parts.
853 if srcDir:
854 return fetch_local_changes(inDir, outDir, srcDir, strictTests)
856 if revision == "HEAD":
857 subprocess.check_call(
858 ["git", "clone", "--depth=1", "--branch=%s" % branch, url, inDir]
860 else:
861 subprocess.check_call(
862 ["git", "clone", "--single-branch", "--branch=%s" % branch, url, inDir]
864 subprocess.check_call(["git", "-C", inDir, "reset", "--hard", revision])
866 # If a PR number is provided, fetches only the new and modified files
867 # from that PR. It also creates a new folder for that PR or replaces if
868 # it already exists, without updating the regular Test262 tests.
869 if prNumber:
870 return fetch_pr_files(inDir, outDir, prNumber, strictTests)
872 # Without a PR or a local import, follows through a regular copy.
873 general_update(inDir, outDir, strictTests)
876 if __name__ == "__main__":
877 import argparse
879 # This script must be run from js/src/tests to work correctly.
880 if "/".join(os.path.normpath(os.getcwd()).split(os.sep)[-3:]) != "js/src/tests":
881 raise RuntimeError("%s must be run from js/src/tests" % sys.argv[0])
883 parser = argparse.ArgumentParser(description="Update the test262 test suite.")
884 parser.add_argument(
885 "--url",
886 default="https://github.com/tc39/test262.git",
887 help="URL to git repository (default: %(default)s)",
889 parser.add_argument(
890 "--branch", default="main", help="Git branch (default: %(default)s)"
892 parser.add_argument(
893 "--revision", default="HEAD", help="Git revision (default: %(default)s)"
895 parser.add_argument(
896 "--out",
897 default="test262",
898 help="Output directory. Any existing directory will be removed!"
899 "(default: %(default)s)",
901 parser.add_argument(
902 "--pull", help="Import contents from a Pull Request specified by its number"
904 parser.add_argument(
905 "--local",
906 help="Import new and modified contents from a local folder, a new folder "
907 "will be created on local/branch_name",
909 parser.add_argument(
910 "--strict",
911 default=False,
912 action="store_true",
913 help="Generate additional strict mode tests. Not enabled by default.",
915 parser.set_defaults(func=update_test262)
916 args = parser.parse_args()
917 args.func(args)