[BE][tests] show local variables on failure in tests (#131151)

------

As per the title, add argument `--locals` for `unittest` and `--showlocals --tb=long` for `pytest` in CI.

Some failures cannot be reproduced on the local machine but exist on cloud CI. This change allows us to investigate the test failure more easily.

Example output: https://github.com/pytorch/pytorch/actions/runs/9961546996/job/27523888353?pr=130710#step:20:3361

```text
/opt/conda/envs/py_3.8/lib/python3.8/site-packages/sympy/core/function.py:307:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

cls = FloorDiv, base = -1.00000000000000, divisor = -1.00000000000000

    @classmethod
    def eval(cls, base, divisor):
        # python test/test_dynamic_shapes.py -k TestDimConstraints.test_dim_constraints_solve_full
        # Assert triggered by inequality solver
        # assert base.is_integer, base
        # assert divisor.is_integer, divisor

        # We don't provide the same error message as in Python because SymPy
        # makes it difficult to check the types.
        if divisor.is_zero:
            raise ZeroDivisionError("division by zero")
        if base in (int_oo, -int_oo, sympy.oo, -sympy.oo) and divisor in (
            int_oo,
            -int_oo,
            sympy.oo,
            -sympy.oo,
        ):
            return sympy.nan
        if base is sympy.nan or divisor is sympy.nan:
            return sympy.nan

        if base.is_zero:
            return sympy.S.Zero
        if base.is_integer and divisor == 1:
            return base
        if base.is_integer and divisor == -1:
            return sympy.Mul(base, -1)
        if (
            isinstance(base, sympy.Number)
            and isinstance(divisor, sympy.Number)
            and (
                base in (int_oo, -int_oo, sympy.oo, -sympy.oo)
                or divisor in (int_oo, -int_oo, sympy.oo, -sympy.oo)
            )
        ):
            r = float(base) / float(divisor)
            if r == math.inf:
                return int_oo
            elif r == -math.inf:
                return -int_oo
            elif math.isnan(r):
                return sympy.nan
            else:
                return sympy.Integer(math.floor(r))
        if isinstance(base, sympy.Integer) and isinstance(divisor, sympy.Integer):
            return sympy.Integer(int(base) // int(divisor))
        if isinstance(base, FloorDiv):
            return FloorDiv(base.args[0], base.args[1] * divisor)

        # Expands (x + y) // b into x // b + y // b.
        # This only works if floor is an identity, i.e. x / b is an integer.
        for term in sympy.Add.make_args(base):
            quotient = term / divisor
            if quotient.is_integer and isinstance(divisor, sympy.Integer):
                # NB: this is correct even if the divisor is not an integer, but it
                # creates rational expressions that cause problems with dynamic
                # shapes.
                return FloorDiv(base - term, divisor) + quotient

        try:
            gcd = sympy.gcd(base, divisor)
            if gcd != 1:
>               return FloorDiv(
                    sympy.simplify(base / gcd), sympy.simplify(divisor / gcd)
                )

base       = -1.00000000000000
cls        = FloorDiv
divisor    = -1.00000000000000
gcd        = 1.00000000000000
quotient   = 1.00000000000000
term       = -1.00000000000000

/opt/conda/envs/py_3.8/lib/python3.8/site-packages/torch/utils/_sympy/functions.py:159:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = (FloorDiv, -1.00000000000000, -1.00000000000000), kwargs = {}

    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
>           retval = cfunc(*args, **kwargs)
E           RecursionError: maximum recursion depth exceeded in comparison
E
E           To execute this test, run the following from the base repo dir:
E               python test/test_sympy_utils.py -k TestValueRanges.test_binary_ref_fn_floordiv_dtype_float
E
E           This message can be suppressed by setting PYTORCH_PRINT_REPRO_ON_FAILURE=0

args       = (FloorDiv, -1.00000000000000, -1.00000000000000)
cfunc      = <functools._lru_cache_wrapper object at 0x7fc5303173a0>
func       = <function Function.__new__ at 0x7fc530317280>
kwargs     = {}
```

Pull Request resolved: https://github.com/pytorch/pytorch/pull/131151
Approved by: https://github.com/ezyang
This commit is contained in:
Xuehai Pan
2024-07-30 00:04:26 +08:00
committed by PyTorch MergeBot
parent ab912b7fef
commit 4694ee1ad2
3 changed files with 38 additions and 2 deletions

View File

@ -4,6 +4,8 @@ addopts =
-rEfX
# Make tracebacks shorter
--tb=native
# Color the output
--color=yes
# capture only Python print and C++ py::print, but not C output (low-level Python errors)
--capture=sys
# don't suppress warnings, but don't shove them all to the end either

View File

@ -430,7 +430,14 @@ def run_test(
)
)
unittest_args.extend(test_module.get_pytest_args())
unittest_args = [arg if arg != "-f" else "-x" for arg in unittest_args]
replacement = {"-f": "-x"}
unittest_args = [replacement.get(arg, arg) for arg in unittest_args]
if options.showlocals:
if options.pytest:
unittest_args.extend(["--showlocals", "--tb=long", "--color=yes"])
else:
unittest_args.append("--locals")
# NB: These features are not available for C++ tests, but there is little incentive
# to implement it because we have never seen a flaky C++ test before.
@ -1118,6 +1125,21 @@ def parse_args():
default=0,
help="Print verbose information and test-by-test results",
)
if sys.version_info >= (3, 9):
parser.add_argument(
"--showlocals",
action=argparse.BooleanOptionalAction,
default=False,
help="Show local variables in tracebacks (default: True)",
)
else:
parser.add_argument(
"--showlocals",
action="store_true",
default=False,
help="Show local variables in tracebacks (default: True)",
)
parser.add_argument("--no-showlocals", dest="showlocals", action="store_false")
parser.add_argument("--jit", "--jit", action="store_true", help="run all jit tests")
parser.add_argument(
"--distributed-tests",

View File

@ -859,6 +859,11 @@ parser.add_argument('--import-slow-tests', type=str, nargs='?', const=DEFAULT_SL
parser.add_argument('--import-disabled-tests', type=str, nargs='?', const=DEFAULT_DISABLED_TESTS_FILE)
parser.add_argument('--rerun-disabled-tests', action='store_true')
parser.add_argument('--pytest-single-test', type=str, nargs=1)
if sys.version_info >= (3, 9):
parser.add_argument('--showlocals', action=argparse.BooleanOptionalAction, default=False)
else:
parser.add_argument('--showlocals', action='store_true', default=False)
parser.add_argument('--no-showlocals', dest='showlocals', action='store_false')
# Only run when -h or --help flag is active to display both unittest and parser help messages.
def run_unittest_help(argv):
@ -894,6 +899,7 @@ TEST_IN_SUBPROCESS = args.subprocess
TEST_SAVE_XML = args.save_xml
REPEAT_COUNT = args.repeat
SEED = args.seed
SHOWLOCALS = args.showlocals
if not getattr(expecttest, "ACCEPT", False):
expecttest.ACCEPT = args.accept
UNITTEST_ARGS = [sys.argv[0]] + remaining
@ -1131,6 +1137,13 @@ def run_tests(argv=UNITTEST_ARGS):
if not lint_test_case_extension(suite):
sys.exit(1)
if SHOWLOCALS:
argv = [
argv[0],
*(["--showlocals", "--tb=long", "--color=yes"] if USE_PYTEST else ["--locals"]),
*argv[1:],
]
if TEST_IN_SUBPROCESS:
other_args = []
if DISABLED_TESTS_FILE:
@ -4914,7 +4927,6 @@ dtype_abbrs = {
}
@functools.lru_cache
def get_cycles_per_ms() -> float:
"""Measure and return approximate number of cycles per millisecond for torch.cuda._sleep