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/.
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 "set-methods", # Bug 1805038
27 "explicit-resource-management", # Bug 1569081
29 "source-phase-imports",
30 "source-phase-imports-module-source",
35 FEATURE_CHECK_NEEDED
= {
36 "Atomics": "!this.hasOwnProperty('Atomics')",
37 "FinalizationRegistry": "!this.hasOwnProperty('FinalizationRegistry')",
38 "SharedArrayBuffer": "!this.hasOwnProperty('SharedArrayBuffer')",
39 "Temporal": "!this.hasOwnProperty('Temporal')",
40 "WeakRef": "!this.hasOwnProperty('WeakRef')",
41 "decorators": "!(this.hasOwnProperty('getBuildConfiguration')&&getBuildConfiguration('decorators'))", # Bug 1435869
42 "iterator-helpers": "!this.hasOwnProperty('Iterator')", # Bug 1568906
43 "Intl.Segmenter": "!Intl.Segmenter", # Bug 1423593
44 "resizable-arraybuffer": "!ArrayBuffer.prototype.resize", # Bug 1670026
45 "uint8array-base64": "!Uint8Array.fromBase64", # Bug 1862220
46 "json-parse-with-source": "!JSON.hasOwnProperty('isRawJSON')", # Bug 1658310
47 "Float16Array": "!this.hasOwnProperty('Float16Array')",
48 "RegExp.escape": "!RegExp.escape",
50 RELEASE_OR_BETA
= set(
53 "symbols-as-weakmap-keys",
57 "import-assertions": "--enable-import-assertions",
58 "import-attributes": "--enable-import-attributes",
59 "ShadowRealm": "--enable-shadow-realms",
60 "iterator-helpers": "--enable-iterator-helpers",
61 "symbols-as-weakmap-keys": "--enable-symbols-as-weakmap-keys",
62 "resizable-arraybuffer": "--enable-arraybuffer-resizable",
63 "uint8array-base64": "--enable-uint8array-base64",
64 "json-parse-with-source": "--enable-json-parse-with-source",
65 "Float16Array": "--enable-float16array",
66 "regexp-duplicate-named-groups": "--enable-regexp-duplicate-named-groups",
67 "RegExp.escape": "--enable-regexp-escape",
68 "regexp-modifiers": "--enable-regexp-modifiers",
71 INCLUDE_FEATURE_DETECTED_OPTIONAL_SHELL_OPTIONS
= {
72 "testTypedArray.js": "Float16Array",
76 @contextlib.contextmanager
77 def TemporaryDirectory():
78 tmpDir
= tempfile
.mkdtemp()
85 def loadTest262Parser(test262Dir
):
87 Loads the test262 test record parser.
89 import importlib
.machinery
92 packagingDir
= os
.path
.join(test262Dir
, "tools", "packaging")
93 moduleName
= "parseTestRecord"
95 # Create a FileFinder to load Python source files.
97 importlib
.machinery
.SourceFileLoader
,
98 importlib
.machinery
.SOURCE_SUFFIXES
,
100 finder
= importlib
.machinery
.FileFinder(packagingDir
, loader_details
)
102 # Find the module spec.
103 spec
= finder
.find_spec(moduleName
)
105 raise RuntimeError("Can't find parseTestRecord module")
107 # Create and execute the module.
108 module
= importlib
.util
.module_from_spec(spec
)
109 spec
.loader
.exec_module(module
)
111 # Return the executed module
115 def tryParseTestFile(test262parser
, source
, testName
):
117 Returns the result of test262parser.parseTestRecord() or None if a parser
120 See <https://github.com/tc39/test262/blob/main/INTERPRETING.md> for an
121 overview of the returned test attributes.
124 return test262parser
.parseTestRecord(source
, testName
)
125 except Exception as err
:
126 print("Error '%s' in file: %s" % (err
, testName
), file=sys
.stderr
)
127 print("Please report this error to the test262 GitHub repository!")
131 def createRefTestEntry(options
, skip
, skipIf
, error
, isModule
, isAsync
):
133 Returns the |reftest| tuple (terms, comments) from the input arguments. Or a
134 tuple of empty strings if no reftest entry is required.
141 terms
.extend(options
)
145 comments
.extend(skip
)
148 terms
.append("skip-if(" + "||".join([cond
for (cond
, _
) in skipIf
]) + ")")
149 comments
.extend([comment
for (_
, comment
) in skipIf
])
152 terms
.append("error:" + error
)
155 terms
.append("module")
158 terms
.append("async")
160 return (" ".join(terms
), ", ".join(comments
))
163 def createRefTestLine(terms
, comments
):
165 Creates the |reftest| line using the given terms and comments.
170 refTest
+= " -- " + comments
174 def createSource(testSource
, refTest
, prologue
, epilogue
):
176 Returns the post-processed source for |testSource|.
181 # Add the |reftest| line.
183 source
.append(b
"// |reftest| " + refTest
.encode("utf-8"))
185 # Prepend any directives if present.
187 source
.append(prologue
.encode("utf-8"))
189 source
.append(testSource
)
191 # Append the test epilogue, i.e. the call to "reportCompare".
192 # TODO: Does this conflict with raw tests?
194 source
.append(epilogue
.encode("utf-8"))
197 return b
"\n".join(source
)
200 def writeTestFile(test262OutDir
, testFileName
, source
):
202 Writes the test source to |test262OutDir|.
205 with io
.open(os
.path
.join(test262OutDir
, testFileName
), "wb") as output
:
209 def addSuffixToFileName(fileName
, suffix
):
210 (filePath
, ext
) = os
.path
.splitext(fileName
)
211 return filePath
+ suffix
+ ext
214 def writeShellAndBrowserFiles(
215 test262OutDir
, harnessDir
, includesMap
, localIncludesMap
, relPath
218 Generate the shell.js and browser.js files for the test harness.
221 # Find all includes from parent directories.
222 def findParentIncludes():
223 parentIncludes
= set()
226 (parent
, child
) = os
.path
.split(current
)
227 if parent
in includesMap
:
228 parentIncludes
.update(includesMap
[parent
])
230 return parentIncludes
232 # Find all includes, skipping includes already present in parent directories.
234 parentIncludes
= findParentIncludes()
235 for include
in includesMap
[relPath
]:
236 if include
not in parentIncludes
:
239 def readIncludeFile(filePath
):
240 with io
.open(filePath
, "rb") as includeFile
:
241 return b
"// file: %s\n%s" % (
242 os
.path
.basename(filePath
).encode("utf-8"),
246 localIncludes
= localIncludesMap
[relPath
] if relPath
in localIncludesMap
else []
248 # Concatenate all includes files.
249 includeSource
= b
"\n".join(
253 # The requested include files.
254 map(partial(os
.path
.join
, harnessDir
), sorted(findIncludes())),
255 # And additional local include files.
256 map(partial(os
.path
.join
, os
.getcwd()), sorted(localIncludes
)),
261 # Write the concatenated include sources to shell.js.
262 with io
.open(os
.path
.join(test262OutDir
, relPath
, "shell.js"), "wb") as shellFile
:
264 shellFile
.write(b
"// GENERATED, DO NOT EDIT\n")
265 shellFile
.write(includeSource
)
267 # The browser.js file is always empty for test262 tests.
269 os
.path
.join(test262OutDir
, relPath
, "browser.js"), "wb"
271 browserFile
.write(b
"")
274 def pathStartsWith(path
, *args
):
275 prefix
= os
.path
.join(*args
)
276 return os
.path
.commonprefix([path
, prefix
]) == prefix
279 def convertTestFile(test262parser
, testSource
, testName
, includeSet
, strictTests
):
281 Convert a test262 test to a compatible jstests test file.
284 # The test record dictionary, its contents are explained in depth at
285 # <https://github.com/tc39/test262/blob/main/INTERPRETING.md>.
286 testRec
= tryParseTestFile(test262parser
, testSource
.decode("utf-8"), testName
)
288 # jsreftest meta data
293 # Skip all files which contain YAML errors.
295 refTestSkip
.append("has YAML errors")
298 # onlyStrict is set when the test must only be run in strict mode.
299 onlyStrict
= "onlyStrict" in testRec
301 # noStrict is set when the test must not be run in strict mode.
302 noStrict
= "noStrict" in testRec
304 # The "raw" attribute is used in the default test262 runner to prevent
305 # prepending additional content (use-strict directive, harness files)
306 # before the actual test source code.
307 raw
= "raw" in testRec
309 # Negative tests have additional meta-data to specify the error type and
310 # when the error is issued (runtime error or early parse error). We're
311 # currently ignoring the error phase attribute.
312 # testRec["negative"] == {type=<error name>, phase=parse|resolution|runtime}
313 isNegative
= "negative" in testRec
314 assert not isNegative
or type(testRec
["negative"]) == dict
315 errorType
= testRec
["negative"]["type"] if isNegative
else None
317 # Async tests are marked with the "async" attribute.
318 isAsync
= "async" in testRec
320 # Test262 tests cannot be both "negative" and "async". (In principle a
321 # negative async test is permitted when the error phase is not "parse" or
322 # the error type is not SyntaxError, but no such tests exist now.)
323 assert not (isNegative
and isAsync
), (
324 "Can't have both async and negative attributes: %s" % testName
327 # Only async tests may use the $DONE or asyncTest function. However,
328 # negative parse tests may "use" the $DONE (or asyncTest) function (of
329 # course they don't actually use it!) without specifying the "async"
330 # attribute. Otherwise, neither $DONE nor asyncTest must appear in the test.
332 # Some "harness" tests redefine $DONE, so skip this check when the test file
333 # is in the "harness" directory.
335 (b
"$DONE" not in testSource
and b
"asyncTest" not in testSource
)
338 or testName
.split(os
.path
.sep
)[0] == "harness"
339 ), ("Missing async attribute in: %s" % testName
)
341 # When the "module" attribute is set, the source code is module code.
342 isModule
= "module" in testRec
344 # CanBlockIsFalse is set when the test expects that the implementation
345 # cannot block on the main thread.
346 if "CanBlockIsFalse" in testRec
:
347 refTestSkipIf
.append(("xulRuntime.shell", "shell can block main thread"))
349 # CanBlockIsTrue is set when the test expects that the implementation
350 # can block on the main thread.
351 if "CanBlockIsTrue" in testRec
:
352 refTestSkipIf
.append(("!xulRuntime.shell", "browser cannot block main thread"))
354 # Skip tests with unsupported features.
355 if "features" in testRec
:
356 unsupported
= [f
for f
in testRec
["features"] if f
in UNSUPPORTED_FEATURES
]
358 refTestSkip
.append("%s is not supported" % ",".join(unsupported
))
360 releaseOrBeta
= [f
for f
in testRec
["features"] if f
in RELEASE_OR_BETA
]
362 refTestSkipIf
.append(
365 "%s is not released yet" % ",".join(releaseOrBeta
),
369 featureCheckNeeded
= [
370 f
for f
in testRec
["features"] if f
in FEATURE_CHECK_NEEDED
372 if featureCheckNeeded
:
373 refTestSkipIf
.append(
376 [FEATURE_CHECK_NEEDED
[f
] for f
in featureCheckNeeded
]
378 "%s is not enabled unconditionally"
379 % ",".join(featureCheckNeeded
),
384 "Atomics" in testRec
["features"]
385 and "SharedArrayBuffer" in testRec
["features"]
387 refTestSkipIf
.append(
389 "(this.hasOwnProperty('getBuildConfiguration')"
390 "&&getBuildConfiguration('arm64-simulator'))",
391 "ARM64 Simulator cannot emulate atomics",
396 SHELL_OPTIONS
[f
] for f
in testRec
["features"] if f
in SHELL_OPTIONS
399 refTestSkipIf
.append(("!xulRuntime.shell", "requires shell-options"))
400 refTestOptions
.extend(
401 ("shell-option({})".format(opt
) for opt
in sorted(shellOptions
))
404 # Optional shell options. Some tests use feature detection for additional
405 # test coverage. We want to get this extra coverage without having to skip
406 # these tests in browser builds.
407 if "includes" in testRec
:
408 optionalShellOptions
= (
409 SHELL_OPTIONS
[INCLUDE_FEATURE_DETECTED_OPTIONAL_SHELL_OPTIONS
[include
]]
410 for include
in testRec
["includes"]
411 if include
in INCLUDE_FEATURE_DETECTED_OPTIONAL_SHELL_OPTIONS
413 refTestOptions
.extend(
414 ("shell-option({})".format(opt
) for opt
in sorted(optionalShellOptions
))
417 # Includes for every test file in a directory is collected in a single
418 # shell.js file per directory level. This is done to avoid adding all
419 # test harness files to the top level shell.js file.
420 if "includes" in testRec
:
421 assert not raw
, "Raw test with includes: %s" % testName
422 includeSet
.update(testRec
["includes"])
424 # Add reportCompare() after all positive, synchronous tests.
425 if not isNegative
and not isAsync
:
426 testEpilogue
= "reportCompare(0, 0);"
431 refTestOptions
.append("test262-raw")
433 (terms
, comments
) = createRefTestEntry(
434 refTestOptions
, refTestSkip
, refTestSkipIf
, errorType
, isModule
, isAsync
438 externRefTest
= (terms
, comments
)
440 refTest
= createRefTestLine(terms
, comments
)
443 # Don't write a strict-mode variant for raw or module files.
444 noStrictVariant
= raw
or isModule
445 assert not (noStrictVariant
and (onlyStrict
or noStrict
)), (
446 "Unexpected onlyStrict or noStrict attribute: %s" % testName
449 # Write non-strict mode test.
450 if noStrictVariant
or noStrict
or not onlyStrict
:
452 nonStrictSource
= createSource(testSource
, refTest
, testPrologue
, testEpilogue
)
453 testFileName
= testName
454 yield (testFileName
, nonStrictSource
, externRefTest
)
456 # Write strict mode test.
457 if not noStrictVariant
and (onlyStrict
or (not noStrict
and strictTests
)):
458 testPrologue
= "'use strict';"
459 strictSource
= createSource(testSource
, refTest
, testPrologue
, testEpilogue
)
460 testFileName
= testName
462 testFileName
= addSuffixToFileName(testFileName
, "-strict")
463 yield (testFileName
, strictSource
, externRefTest
)
466 def convertFixtureFile(fixtureSource
, fixtureName
):
468 Convert a test262 fixture file to a compatible jstests test file.
471 # jsreftest meta data
473 refTestSkip
= ["not a test file"]
479 (terms
, comments
) = createRefTestEntry(
480 refTestOptions
, refTestSkip
, refTestSkipIf
, errorType
, isModule
, isAsync
482 refTest
= createRefTestLine(terms
, comments
)
484 source
= createSource(fixtureSource
, refTest
, "", "")
486 yield (fixtureName
, source
, externRefTest
)
489 def process_test262(test262Dir
, test262OutDir
, strictTests
, externManifests
):
491 Process all test262 files and converts them into jstests compatible tests.
494 harnessDir
= os
.path
.join(test262Dir
, "harness")
495 testDir
= os
.path
.join(test262Dir
, "test")
496 test262parser
= loadTest262Parser(test262Dir
)
498 # Map of test262 subdirectories to the set of include files required for
499 # tests in that subdirectory. The includes for all tests in a subdirectory
500 # are merged into a single shell.js.
501 # map<dirname, set<includeFiles>>
504 # Additional local includes keyed by test262 directory names. The include
505 # files in this map must be located in the js/src/tests directory.
506 # map<dirname, list<includeFiles>>
507 localIncludesMap
= {}
509 # The root directory contains required harness files and test262-host.js.
510 includesMap
[""] = set(["sta.js", "assert.js"])
511 localIncludesMap
[""] = ["test262-host.js"]
513 # Also add files known to be used by many tests to the root shell.js file.
514 includesMap
[""].update(["propertyHelper.js", "compareArray.js"])
516 # Write the root shell.js file.
517 writeShellAndBrowserFiles(
518 test262OutDir
, harnessDir
, includesMap
, localIncludesMap
, ""
521 # Additional explicit includes inserted at well-chosen locations to reduce
522 # code duplication in shell.js files.
523 explicitIncludes
= {}
524 explicitIncludes
[os
.path
.join("built-ins", "Atomics")] = [
528 explicitIncludes
[os
.path
.join("built-ins", "DataView")] = [
529 "byteConversionValues.js"
531 explicitIncludes
[os
.path
.join("built-ins", "Promise")] = ["promiseHelper.js"]
532 explicitIncludes
[os
.path
.join("built-ins", "Temporal")] = ["temporalHelpers.js"]
533 explicitIncludes
[os
.path
.join("built-ins", "TypedArray")] = [
534 "byteConversionValues.js",
535 "detachArrayBuffer.js",
538 explicitIncludes
[os
.path
.join("built-ins", "TypedArrays")] = [
539 "detachArrayBuffer.js"
541 explicitIncludes
[os
.path
.join("built-ins", "Temporal")] = ["temporalHelpers.js"]
543 # Process all test directories recursively.
544 for dirPath
, dirNames
, fileNames
in os
.walk(testDir
):
545 relPath
= os
.path
.relpath(dirPath
, testDir
)
549 # Skip creating a "prs" directory if it already exists
550 if relPath
not in ("prs", "local") and not os
.path
.exists(
551 os
.path
.join(test262OutDir
, relPath
)
553 os
.makedirs(os
.path
.join(test262OutDir
, relPath
))
556 includesMap
[relPath
] = includeSet
558 if relPath
in explicitIncludes
:
559 includeSet
.update(explicitIncludes
[relPath
])
561 # Convert each test file.
562 for fileName
in fileNames
:
563 filePath
= os
.path
.join(dirPath
, fileName
)
564 testName
= os
.path
.relpath(filePath
, testDir
)
566 # Copy non-test files as is.
567 (_
, fileExt
) = os
.path
.splitext(fileName
)
569 shutil
.copyfile(filePath
, os
.path
.join(test262OutDir
, testName
))
572 # Files ending with "_FIXTURE.js" are fixture files:
573 # https://github.com/tc39/test262/blob/main/INTERPRETING.md#modules
574 isFixtureFile
= fileName
.endswith("_FIXTURE.js")
576 # Read the original test source and preprocess it for the jstests harness.
577 with io
.open(filePath
, "rb") as testFile
:
578 testSource
= testFile
.read()
581 convert
= convertFixtureFile(testSource
, testName
)
583 convert
= convertTestFile(
591 for newFileName
, newSource
, externRefTest
in convert
:
592 writeTestFile(test262OutDir
, newFileName
, newSource
)
594 if externRefTest
is not None:
595 externManifests
.append(
598 "reftest": externRefTest
,
602 # Add shell.js and browers.js files for the current directory.
603 writeShellAndBrowserFiles(
604 test262OutDir
, harnessDir
, includesMap
, localIncludesMap
, relPath
608 def fetch_local_changes(inDir
, outDir
, srcDir
, strictTests
):
610 Fetch the changes from a local clone of Test262.
612 1. Get the list of file changes made by the current branch used on Test262 (srcDir).
613 2. Copy only the (A)dded, (C)opied, (M)odified, and (R)enamed files to inDir.
614 3. inDir is treated like a Test262 checkout, where files will be converted.
615 4. Fetches the current branch name to set the outDir.
616 5. Processed files will be added to `<outDir>/local/<branchName>`.
620 # TODO: fail if it's in the default branch? or require a branch name?
621 # Checks for unstaged or non committed files. A clean branch provides a clean status.
622 status
= subprocess
.check_output(
623 ("git -C %s status --porcelain" % srcDir
).split(" "), encoding
="utf-8"
628 "Please commit files and cleanup the local test262 folder before importing files.\n"
629 "Current status: \n%s" % status
632 # Captures the branch name to be used on the output
633 branchName
= subprocess
.check_output(
634 ("git -C %s rev-parse --abbrev-ref HEAD" % srcDir
).split(" "), encoding
="utf-8"
637 # Fetches the file names to import
638 files
= subprocess
.check_output(
639 ("git -C %s diff main --diff-filter=ACMR --name-only" % srcDir
).split(" "),
643 # Fetches the deleted files to print an output log. This can be used to
644 # set up the skip list, if necessary.
645 deletedFiles
= subprocess
.check_output(
646 ("git -C %s diff main --diff-filter=D --name-only" % srcDir
).split(" "),
650 # Fetches the modified files as well for logging to support maintenance
652 modifiedFiles
= subprocess
.check_output(
653 ("git -C %s diff main --diff-filter=M --name-only" % srcDir
).split(" "),
657 # Fetches the renamed files for the same reason, this avoids duplicate
658 # tests if running the new local folder and the general imported Test262
660 renamedFiles
= subprocess
.check_output(
661 ("git -C %s diff main --diff-filter=R --summary" % srcDir
).split(" "),
665 # Print some friendly output
666 print("From the branch %s in %s \n" % (branchName
, srcDir
))
667 print("Files being copied to the local folder: \n%s" % files
)
670 "Deleted files (use this list to update the skip list): \n%s" % deletedFiles
674 "Modified files (use this list to update the skip list): \n%s"
678 print("Renamed files (already added with the new names): \n%s" % renamedFiles
)
680 for f
in files
.splitlines():
681 # Capture the subdirectories names to recreate the file tree
682 # TODO: join the file tree with -- instead of multiple subfolders?
683 fileTree
= os
.path
.join(inDir
, os
.path
.dirname(f
))
684 if not os
.path
.exists(fileTree
):
685 os
.makedirs(fileTree
)
688 os
.path
.join(srcDir
, f
), os
.path
.join(fileTree
, os
.path
.basename(f
))
691 # Extras from Test262. Copy the current support folders - including the
692 # harness - for a proper conversion process
693 shutil
.copytree(os
.path
.join(srcDir
, "tools"), os
.path
.join(inDir
, "tools"))
694 shutil
.copytree(os
.path
.join(srcDir
, "harness"), os
.path
.join(inDir
, "harness"))
696 # Reset any older directory in the output using the same branch name
697 outDir
= os
.path
.join(outDir
, "local", branchName
)
698 if os
.path
.isdir(outDir
):
699 shutil
.rmtree(outDir
)
702 process_test262(inDir
, outDir
, strictTests
, [])
705 def fetch_pr_files(inDir
, outDir
, prNumber
, strictTests
):
708 prTestsOutDir
= os
.path
.join(outDir
, "prs", prNumber
)
709 if os
.path
.isdir(prTestsOutDir
):
710 print("Removing folder %s" % prTestsOutDir
)
711 shutil
.rmtree(prTestsOutDir
)
712 os
.makedirs(prTestsOutDir
)
714 # Reuses current Test262 clone's harness and tools folders only, the clone's test/
715 # folder can be discarded from here
716 shutil
.rmtree(os
.path
.join(inDir
, "test"))
718 prRequest
= requests
.get(
719 "https://api.github.com/repos/tc39/test262/pulls/%s" % prNumber
721 prRequest
.raise_for_status()
723 pr
= prRequest
.json()
725 if pr
["state"] != "open":
726 # Closed PR, remove respective files from folder
727 return print("PR %s is closed" % prNumber
)
729 url
= "https://api.github.com/repos/tc39/test262/pulls/%s/files" % prNumber
733 files
= requests
.get(url
)
734 files
.raise_for_status()
736 for item
in files
.json():
737 if not item
["filename"].startswith("test/"):
740 filename
= item
["filename"]
741 fileStatus
= item
["status"]
743 print("%s %s" % (fileStatus
, filename
))
745 # Do not add deleted files
746 if fileStatus
== "removed":
749 contents
= requests
.get(item
["raw_url"])
750 contents
.raise_for_status()
752 fileText
= contents
.text
754 filePathDirs
= os
.path
.join(inDir
, *filename
.split("/")[:-1])
756 if not os
.path
.isdir(filePathDirs
):
757 os
.makedirs(filePathDirs
)
760 os
.path
.join(inDir
, *filename
.split("/")), "wb"
762 output_file
.write(fileText
.encode("utf8"))
766 # Check if the pull request changes are split over multiple pages.
767 if "link" in files
.headers
:
768 link
= files
.headers
["link"]
770 # The links are comma separated and the entries within a link are separated by a
771 # semicolon. For example the first two links entries for PR 3199:
773 # https://api.github.com/repos/tc39/test262/pulls/3199/files
775 # <https://api.github.com/repositories/16147933/pulls/3199/files?page=2>; rel="next",
776 # <https://api.github.com/repositories/16147933/pulls/3199/files?page=14>; rel="last"
779 # https://api.github.com/repositories/16147933/pulls/3199/files?page=2
781 # <https://api.github.com/repositories/16147933/pulls/3199/files?page=1>; rel="prev",
782 # <https://api.github.com/repositories/16147933/pulls/3199/files?page=3>; rel="next",
783 # <https://api.github.com/repositories/16147933/pulls/3199/files?page=14>; rel="last",
784 # <https://api.github.com/repositories/16147933/pulls/3199/files?page=1>; rel="first"
787 for pages
in link
.split(", "):
788 (pageUrl
, rel
) = pages
.split("; ")
790 assert pageUrl
[0] == "<"
791 assert pageUrl
[-1] == ">"
793 # Remove the angle brackets around the URL.
794 pageUrl
= pageUrl
[1:-1]
796 # Make sure we only request data from github and not some other place.
797 assert pageUrl
.startswith("https://api.github.com/")
799 # Ensure the relative URL marker has the expected format.
802 or rel
== 'rel="next"'
803 or rel
== 'rel="first"'
804 or rel
== 'rel="last"'
807 # We only need the URL for the next page.
808 if rel
== 'rel="next"':
812 process_test262(inDir
, prTestsOutDir
, strictTests
, [])
815 def general_update(inDir
, outDir
, strictTests
):
818 restoreLocalTestsDir
= False
819 restorePrsTestsDir
= False
820 localTestsOutDir
= os
.path
.join(outDir
, "local")
821 prsTestsOutDir
= os
.path
.join(outDir
, "prs")
823 # Stash test262/local and test262/prs. Currently the Test262 repo does not have any
824 # top-level subdirectories named "local" or "prs".
825 # This prevents these folders from being removed during the update process.
826 if os
.path
.isdir(localTestsOutDir
):
827 shutil
.move(localTestsOutDir
, inDir
)
828 restoreLocalTestsDir
= True
830 if os
.path
.isdir(prsTestsOutDir
):
831 shutil
.move(prsTestsOutDir
, inDir
)
832 restorePrsTestsDir
= True
834 # Create the output directory from scratch.
835 if os
.path
.isdir(outDir
):
836 shutil
.rmtree(outDir
)
840 shutil
.copyfile(os
.path
.join(inDir
, "LICENSE"), os
.path
.join(outDir
, "LICENSE"))
842 # Create the git info file.
843 with io
.open(os
.path
.join(outDir
, "GIT-INFO"), "w", encoding
="utf-8") as info
:
844 subprocess
.check_call(["git", "-C", inDir
, "log", "-1"], stdout
=info
)
846 # Copy the test files.
848 process_test262(inDir
, outDir
, strictTests
, externManifests
)
850 # Create the external reftest manifest file.
851 with io
.open(os
.path
.join(outDir
, "jstests.list"), "wb") as manifestFile
:
852 manifestFile
.write(b
"# GENERATED, DO NOT EDIT\n\n")
853 for externManifest
in sorted(externManifests
, key
=itemgetter("name")):
854 (terms
, comments
) = externManifest
["reftest"]
856 entry
= "%s script %s%s\n" % (
858 externManifest
["name"],
859 (" # %s" % comments
) if comments
else "",
861 manifestFile
.write(entry
.encode("utf-8"))
863 # Move test262/local back.
864 if restoreLocalTestsDir
:
865 shutil
.move(os
.path
.join(inDir
, "local"), outDir
)
867 # Restore test262/prs if necessary after a general Test262 update.
868 if restorePrsTestsDir
:
869 shutil
.move(os
.path
.join(inDir
, "prs"), outDir
)
872 def update_test262(args
):
877 revision
= args
.revision
882 if not os
.path
.isabs(outDir
):
883 outDir
= os
.path
.join(os
.getcwd(), outDir
)
885 strictTests
= args
.strict
887 # Download the requested branch in a temporary directory.
888 with
TemporaryDirectory() as inDir
:
889 # If it's a local import, skip the git clone parts.
891 return fetch_local_changes(inDir
, outDir
, srcDir
, strictTests
)
893 if revision
== "HEAD":
894 subprocess
.check_call(
895 ["git", "clone", "--depth=1", "--branch=%s" % branch
, url
, inDir
]
898 subprocess
.check_call(
899 ["git", "clone", "--single-branch", "--branch=%s" % branch
, url
, inDir
]
901 subprocess
.check_call(["git", "-C", inDir
, "reset", "--hard", revision
])
903 # If a PR number is provided, fetches only the new and modified files
904 # from that PR. It also creates a new folder for that PR or replaces if
905 # it already exists, without updating the regular Test262 tests.
907 return fetch_pr_files(inDir
, outDir
, prNumber
, strictTests
)
909 # Without a PR or a local import, follows through a regular copy.
910 general_update(inDir
, outDir
, strictTests
)
913 if __name__
== "__main__":
916 # This script must be run from js/src/tests to work correctly.
917 if "/".join(os
.path
.normpath(os
.getcwd()).split(os
.sep
)[-3:]) != "js/src/tests":
918 raise RuntimeError("%s must be run from js/src/tests" % sys
.argv
[0])
920 parser
= argparse
.ArgumentParser(description
="Update the test262 test suite.")
923 default
="https://github.com/tc39/test262.git",
924 help="URL to git repository (default: %(default)s)",
927 "--branch", default
="main", help="Git branch (default: %(default)s)"
930 "--revision", default
="HEAD", help="Git revision (default: %(default)s)"
935 help="Output directory. Any existing directory will be removed!"
936 "(default: %(default)s)",
939 "--pull", help="Import contents from a Pull Request specified by its number"
943 help="Import new and modified contents from a local folder, a new folder "
944 "will be created on local/branch_name",
950 help="Generate additional strict mode tests. Not enabled by default.",
952 parser
.set_defaults(func
=update_test262
)
953 args
= parser
.parse_args()