Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=2581
guard_failures=2592
internal_compile_panics=0
loops_aborted=0
loops_compiled=3
retraces_compiled=0
5 changes: 3 additions & 2 deletions pyre/bench/synth/pypy_type_surface.cranelift.jitstats
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
bridges_compiled=102
bridges_compiled=5
descr_set_absent=0
descr_set_ambiguous=0
descr_set_stale_absent=0
Expand All @@ -8,7 +8,8 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=20497
guard_failures=1011
internal_compile_panics=0
loops_aborted=0
loops_compiled=11
retraces_compiled=0
5 changes: 3 additions & 2 deletions pyre/bench/synth/pypy_type_surface.dynasm.jitstats
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
bridges_compiled=102
bridges_compiled=5
descr_set_absent=0
descr_set_ambiguous=0
descr_set_stale_absent=0
Expand All @@ -8,7 +8,8 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=20497
guard_failures=1011
internal_compile_panics=0
loops_aborted=0
loops_compiled=11
retraces_compiled=0
5 changes: 3 additions & 2 deletions pyre/bench/synth/pypy_type_surface.wasm.jitstats
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
bridges_compiled=102
bridges_compiled=5
descr_set_absent=0
descr_set_ambiguous=0
descr_set_stale_absent=0
Expand All @@ -8,7 +8,8 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=20497
guard_failures=1011
internal_compile_panics=0
loops_aborted=0
loops_compiled=11
retraces_compiled=0
3 changes: 2 additions & 1 deletion pyre/bench/synth/recursion_memo_branch.wasm.jitstats
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=4724
guard_failures=4704
internal_compile_panics=0
loops_aborted=1
loops_compiled=3
retraces_compiled=0
3 changes: 2 additions & 1 deletion pyre/bench/synth/wasm_ca_trampoline_decline.wasm.jitstats
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=404
guard_failures=601
internal_compile_panics=0
loops_aborted=1
loops_compiled=2
retraces_compiled=0
54 changes: 50 additions & 4 deletions pyre/cpython_tests/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,34 @@ def last_stderr_line(err: str) -> str:
return ""


def failure_digest(out: str, err: str) -> str:
"""Which cases unittest reported against, and its closing verdict.

A FAIL's tail is not its cause: this runner sets `MAJIT_STATS`, so the last
thing every process writes is the JIT summary, and `last_stderr_line` reads
back `Compilation time: 8.1ms` for a module whose real answer is two named
assertions several hundred lines above it. Every FAIL in the suite recorded
that same line, which named no test at all.

unittest writes `FAIL: <case>` / `ERROR: <case>` headers and one closing
`FAILED (...)`; those are the runner's own account of what went wrong, and
they are what a CI log needs to carry.
"""
lines = f"{out}\n{err}".splitlines()
cases: list[str] = []
verdict = ""
for line in lines:
line = line.strip()
if line.startswith(("FAIL: ", "ERROR: ")) and line not in cases:
cases.append(line)
elif line.startswith("FAILED ("):
verdict = line
shown = cases[:4]
if len(cases) > len(shown):
shown.append(f"(+{len(cases) - len(shown)} more)")
return " | ".join(part for part in (verdict, *shown) if part)


def death_signal(rc: int) -> str:
"""`SIGBUS` for a run killed by a signal, else a bare return code.

Expand Down Expand Up @@ -199,8 +227,11 @@ def classify(rc: int, out: str, err: str) -> tuple[str, str]:
# skips it too, so it is honestly SKIP, not an interpreter gap.
if not ran and "SkipTest" in err:
return "SKIP", f"rc={rc} {last}"[:120]
status = "FAIL" if ran else "IMPORTERROR"
return status, f"rc={rc} {last}"[:120]
if ran:
# An IMPORTERROR never reached unittest, so its tail is all there is;
# a FAIL has unittest's own account, and only that names a test.
return "FAIL", f"rc={rc} {failure_digest(out, err) or last}"[:300]
return "IMPORTERROR", f"rc={rc} {last}"[:120]


# ── discovery ────────────────────────────────────────────────────────
Expand Down Expand Up @@ -324,8 +355,17 @@ def run_module(binary: Path, module: str, mode: str, timeout: int,
cmd, cwd=cwd, env=module_env, capture_output=True, text=True,
encoding="utf-8", errors="replace", timeout=timeout,
)
except subprocess.TimeoutExpired:
return "TIMEOUT", f"timeout {timeout}s"
except subprocess.TimeoutExpired as expired:
# A bare "timeout 120s" says only that the module is slow, never
# which case it stopped in — and unittest's progress dots, the one
# record of how far it got, were being thrown away here. `text=True`
# decodes what `communicate()` returns; the timeout path raises with
# the raw chunks it had joined, which is bytes on POSIX and str on
# Windows, so both spellings have to be accepted.
partial = expired.stderr or ""
if isinstance(partial, bytes):
partial = partial.decode("utf-8", "replace")
return "TIMEOUT", f"timeout {timeout}s {last_stderr_line(partial)}"[:300]
return classify(proc.returncode, proc.stdout or "", proc.stderr or "")


Expand Down Expand Up @@ -390,6 +430,12 @@ def parse_args() -> argparse.Namespace:
def main() -> int:
args = parse_args()

# The report is box-drawn and the module names it echoes come from the
# stdlib, so a console whose codepage cannot spell one of those characters
# used to kill the run with a `UnicodeEncodeError` before it reached a
# single test — the header alone does it on cp949.
sys.stdout.reconfigure(encoding="utf-8", errors="replace", line_buffering=True)

binary = Path(args.binary) if args.binary else TARGET_RELEASE / f"{BIN_NAME[args.backend]}{EXE}"
binary = binary.resolve()
if not args.list and not binary.exists():
Expand Down
11 changes: 7 additions & 4 deletions pyre/extra_tests/parity_tests/frame_clear_finalization.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import gc
import resource
import sys
import time
import traceback
import weakref

Expand Down Expand Up @@ -124,11 +124,14 @@ def raises():

check = Check()
count = 200
before = resource.getrusage(resource.RUSAGE_SELF)
# `process_time` rather than `resource.getrusage`: the CPU this process has
# spent is the measurement, and `resource` is a POSIX module, so importing
# it would take the five checks above off Windows along with this one.
before = time.process_time()
for _ in range(count):
check.assertRaises(Expected, raises)
after = resource.getrusage(resource.RUSAGE_SELF)
per_op_ms = (after.ru_utime - before.ru_utime) * 1000.0 / count
after = time.process_time()
per_op_ms = (after - before) * 1000.0 / count
assert per_op_ms < 1.0, per_op_ms


Expand Down
85 changes: 85 additions & 0 deletions pyre/extra_tests/parity_tests/locale_categories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Which locale categories a platform has, and a setlocale that admits failure.

- The `LC_*` numbers belong to the host's C library, and POSIX and the MSVC CRT
number them differently — `LC_ALL` is 6 on one and 0 on the other. A table of
POSIX values published on Windows does not merely read wrong: every number
names a *different* category there, so setting one sets another.
- `LC_MESSAGES` is a POSIX category the MSVC CRT has no counterpart for, and
`locale.py` asks whether the name is here before using it.
- `setlocale` answered "C" for every name it was given wherever the real one
was not being called, so a locale the host cannot install looked installed.
`locale.Error` is how a caller finds out it is not — and code that asks for a
locale in order to skip when it is missing gets the skip wrong otherwise.
"""

import locale
import sys


def check(cond, what):
if not cond:
raise AssertionError(what)


def raises(call, exc):
try:
call()
except exc as e:
return e
raise AssertionError(f"{exc.__name__} was not raised")


CATEGORIES = ["LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", "LC_NUMERIC",
"LC_TIME"]

# ── which categories exist, and what they are numbered ────────────────────
for name in CATEGORIES:
check(hasattr(locale, name), f"locale has no {name}")

values = [getattr(locale, name) for name in CATEGORIES]
check(len(set(values)) == len(values), f"two categories share a number: {values}")

# `LC_MESSAGES` is the one category whose presence is the platform's answer
# rather than a constant, so it is asserted as presence and not as a number.
check(
hasattr(locale, "LC_MESSAGES") == (sys.platform != "win32"),
f"LC_MESSAGES presence is wrong for {sys.platform}",
)

if sys.platform == "win32":
# The MSVC CRT's own numbering, which is not POSIX's in any position.
check(locale.LC_ALL == 0, locale.LC_ALL)
check(locale.LC_COLLATE == 1, locale.LC_COLLATE)
check(locale.LC_CTYPE == 2, locale.LC_CTYPE)
check(locale.LC_MONETARY == 3, locale.LC_MONETARY)
check(locale.LC_NUMERIC == 4, locale.LC_NUMERIC)
check(locale.LC_TIME == 5, locale.LC_TIME)

# ── setlocale answers about the locale that was installed ─────────────────
# The one-argument form asks rather than sets, and what it answers has to be a
# name the two-argument form accepts back — the round trip is how `addCleanup`
# in a test, or a library restoring what it borrowed, puts the locale back.
for name in CATEGORIES:
category = getattr(locale, name)
current = locale.setlocale(category)
check(isinstance(current, str) and current, f"setlocale({name}) -> {current!r}")
check(locale.setlocale(category, current) == current, f"{name} did not round trip")

# "C" is the one locale every host has.
check(locale.setlocale(locale.LC_ALL, "C") == "C", "LC_ALL could not be set to C")

# A name no host can install is refused. Answering "C" here — the shape of a
# setlocale that never reached the C library — reports success for a locale
# that was not installed, and the caller then runs the work it meant to skip.
for absent in ("no-such-locale-xyz", "xx_YY.INVALID"):
raises(lambda: locale.setlocale(locale.LC_CTYPE, absent), locale.Error)
check(
locale.setlocale(locale.LC_CTYPE) == "C",
"a refused setlocale changed the installed locale",
)

# An embedded NUL cannot reach a C string, and is a value error rather than a
# locale that could not be found.
raises(lambda: locale.setlocale(locale.LC_CTYPE, "C\0C"), ValueError)

print("OK")
Loading
Loading