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 "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",
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()
69 def loadTest262Parser(test262Dir
):
71 Loads the test262 test record parser.
73 import importlib
.machinery
76 packagingDir
= os
.path
.join(test262Dir
, "tools", "packaging")
77 moduleName
= "parseTestRecord"
79 # Create a FileFinder to load Python source files.
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
)
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
99 def tryParseTestFile(test262parser
, source
, testName
):
101 Returns the result of test262parser.parseTestRecord() or None if a parser
104 See <https://github.com/tc39/test262/blob/main/INTERPRETING.md> for an
105 overview of the returned test attributes.
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!")
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.
125 terms
.extend(options
)
129 comments
.extend(skip
)
132 terms
.append("skip-if(" + "||".join([cond
for (cond
, _
) in skipIf
]) + ")")
133 comments
.extend([comment
for (_
, comment
) in skipIf
])
136 terms
.append("error:" + error
)
139 terms
.append("module")
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.
154 refTest
+= " -- " + comments
158 def createSource(testSource
, refTest
, prologue
, epilogue
):
160 Returns the post-processed source for |testSource|.
165 # Add the |reftest| line.
167 source
.append(b
"// |reftest| " + refTest
.encode("utf-8"))
169 # Prepend any directives if present.
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?
178 source
.append(epilogue
.encode("utf-8"))
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
:
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()
210 (parent
, child
) = os
.path
.split(current
)
211 if parent
in includesMap
:
212 parentIncludes
.update(includesMap
[parent
])
214 return parentIncludes
216 # Find all includes, skipping includes already present in parent directories.
218 parentIncludes
= findParentIncludes()
219 for include
in includesMap
[relPath
]:
220 if include
not in parentIncludes
:
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"),
230 localIncludes
= localIncludesMap
[relPath
] if relPath
in localIncludesMap
else []
232 # Concatenate all includes files.
233 includeSource
= b
"\n".join(
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
:
248 shellFile
.write(b
"// GENERATED, DO NOT EDIT\n")
249 shellFile
.write(includeSource
)
251 # The browser.js file is always empty for test262 tests.
253 os
.path
.join(test262OutDir
, relPath
, "browser.js"), "wb"
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
277 # Skip all files which contain YAML errors.
279 refTestSkip
.append("has YAML errors")
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.
319 (b
"$DONE" not in testSource
and b
"asyncTest" not in testSource
)
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
]
342 refTestSkip
.append("%s is not supported" % ",".join(unsupported
))
344 releaseOrBeta
= [f
for f
in testRec
["features"] if f
in RELEASE_OR_BETA
]
346 refTestSkipIf
.append(
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(
360 [FEATURE_CHECK_NEEDED
[f
] for f
in featureCheckNeeded
]
362 "%s is not enabled unconditionally"
363 % ",".join(featureCheckNeeded
),
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",
380 SHELL_OPTIONS
[f
] for f
in testRec
["features"] if f
in SHELL_OPTIONS
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);"
402 refTestOptions
.append("test262-raw")
404 (terms
, comments
) = createRefTestEntry(
405 refTestOptions
, refTestSkip
, refTestSkipIf
, errorType
, isModule
, isAsync
409 externRefTest
= (terms
, comments
)
411 refTest
= createRefTestLine(terms
, comments
)
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
:
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
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
444 refTestSkip
= ["not a test file"]
450 (terms
, comments
) = createRefTestEntry(
451 refTestOptions
, refTestSkip
, refTestSkipIf
, errorType
, isModule
, isAsync
453 refTest
= createRefTestLine(terms
, comments
)
455 source
= createSource(fixtureSource
, refTest
, "", "")
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>>
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")] = [
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",
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
)
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
))
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
)
540 shutil
.copyfile(filePath
, os
.path
.join(test262OutDir
, testName
))
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()
552 convert
= convertFixtureFile(testSource
, testName
)
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(
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>`.
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(" ")
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(" ")
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
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
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
)
633 "Deleted files (use this list to update the skip list): \n%s" % deletedFiles
637 "Modified files (use this list to update the skip list): \n%s"
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
)
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
)
665 process_test262(inDir
, outDir
, strictTests
, [])
668 def fetch_pr_files(inDir
, outDir
, prNumber
, strictTests
):
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
696 files
= requests
.get(url
)
697 files
.raise_for_status()
699 for item
in files
.json():
700 if not item
["filename"].startswith("test/"):
703 filename
= item
["filename"]
704 fileStatus
= item
["status"]
706 print("%s %s" % (fileStatus
, filename
))
708 # Do not add deleted files
709 if fileStatus
== "removed":
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
)
723 os
.path
.join(inDir
, *filename
.split("/")), "wb"
725 output_file
.write(fileText
.encode("utf8"))
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
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"
742 # https://api.github.com/repositories/16147933/pulls/3199/files?page=2
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"
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.
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"':
775 process_test262(inDir
, prTestsOutDir
, strictTests
, [])
778 def general_update(inDir
, outDir
, strictTests
):
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
)
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.
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"]
819 entry
= "%s script %s%s\n" % (
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
):
840 revision
= args
.revision
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.
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
]
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.
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__":
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.")
886 default
="https://github.com/tc39/test262.git",
887 help="URL to git repository (default: %(default)s)",
890 "--branch", default
="main", help="Git branch (default: %(default)s)"
893 "--revision", default
="HEAD", help="Git revision (default: %(default)s)"
898 help="Output directory. Any existing directory will be removed!"
899 "(default: %(default)s)",
902 "--pull", help="Import contents from a Pull Request specified by its number"
906 help="Import new and modified contents from a local folder, a new folder "
907 "will be created on local/branch_name",
913 help="Generate additional strict mode tests. Not enabled by default.",
915 parser
.set_defaults(func
=update_test262
)
916 args
= parser
.parse_args()