$ touch $TMPDIR/pyrefly.toml && \ > echo "" > $TMPDIR/empty.py && $PYREFLY check --python-version 3.13.0 $TMPDIR/empty.py -a INFO 0 errors* (glob) [0]
$ touch $TMPDIR/pyrefly.toml && \ > echo -e "from typing import reveal_type\nreveal_type(1)" > $TMPDIR/empty.py && $PYREFLY check --python-version 3.13.0 $TMPDIR/empty.py -a INFO 0 errors* (glob) [0]
$ touch $TMPDIR/pyrefly.toml && \ > cp $TEST_PY $TMPDIR/test.py && $PYREFLY check $TMPDIR/test.py INFO Loading new build system at * (glob?) INFO Querying Buck for source DB (glob?) INFO Source DB build ID: * (glob?) INFO Finished querying Buck for source DB (glob?) INFO 0 errors [0]
$ touch $(dirname $PYREFLY_PY)/pyrefly.toml && $PYREFLY check $PYREFLY_PY INFO 0 errors [0]
Two assertions on the same scenario: (1) the upsell text appears on stderr exactly once across all the user-arg files, and (2) stdout stays clean — important so machine-readable output formats (json, omit-errors, …) aren't polluted. Captured to files so the single block can pin both streams.
mktemp -d -p /tmp is used instead of letting mktemp honor $TMPDIR, because earlier tests in this file touch $TMPDIR/pyrefly.toml, and that file would otherwise be picked up by the upward config search and short-circuit the upsell. /tmp itself has no direct pyrefly.toml, so the walk reaches the synthesized fallback.
$ UPSELL_SAME=$(mktemp -d -p /tmp upsell.XXXXXX) && \ > echo "x = 1" > $UPSELL_SAME/a.py && echo "y = 2" > $UPSELL_SAME/b.py && \ > $PYREFLY check $UPSELL_SAME/a.py $UPSELL_SAME/b.py \ > >$UPSELL_SAME/out.txt 2>$UPSELL_SAME/err.txt; \ > echo "---STDOUT---"; cat $UPSELL_SAME/out.txt; \ > echo "---STDERR---"; cat $UPSELL_SAME/err.txt; \ > rm -rf $UPSELL_SAME ---STDOUT--- ---STDERR--- INFO 0 errors* (glob) No `pyrefly.toml` found — using preset `basic`. Run `pyrefly init` to continue setting up Pyrefly. Docs: * (glob) [0]
--config suppresses the upsell$ mkdir $TMPDIR/upsell_explicit && touch $TMPDIR/upsell_explicit/cfg.toml && \ > echo "x = 1" > $TMPDIR/upsell_explicit/foo.py && \ > $PYREFLY check $TMPDIR/upsell_explicit/foo.py --config $TMPDIR/upsell_explicit/cfg.toml INFO 0 errors* (glob) [0]
$ mkdir -p $TMPDIR/upsell_split/p1 && touch $TMPDIR/upsell_split/p1/pyrefly.toml && \ > echo "x = 1" > $TMPDIR/upsell_split/p1/a.py && \ > mkdir $TMPDIR/upsell_split/p2 && echo "y = 2" > $TMPDIR/upsell_split/p2/b.py && \ > $PYREFLY check $TMPDIR/upsell_split/p1/a.py $TMPDIR/upsell_split/p2/b.py INFO 0 errors* (glob) [0]
--summary=none suppresses the upsellThe upsell is part of the summary surface. Tools that pass --summary=none (e.g. pyrefly init‘s self-invocation, scripts that already render their own summary) shouldn’t have unsolicited copy appended on stderr.
$ UPSELL_QUIET=$(mktemp -d -p /tmp upsell.XXXXXX) && \ > echo "x = 1" > $UPSELL_QUIET/a.py && \ > $PYREFLY check $UPSELL_QUIET/a.py --summary=none; rm -rf $UPSELL_QUIET [0]
pyrefly check (project mode, no file args) and pyrefly check <file> (file mode) should produce the same upsell output in an unconfigured repo. Without resolver wiring on the project-mode path, pyrefly check would silently skip the upsell that pyrefly check <file> shows from the same directory.
mktemp -d -p /tmp for the same reason as the file-mode upsell test above: $TMPDIR/pyrefly.toml from earlier blocks would short-circuit the upward search.
$ UPSELL_PROJ=$(mktemp -d -p /tmp upsell.XXXXXX) && \ > echo "x = 1" > $UPSELL_PROJ/a.py && \ > cd $UPSELL_PROJ && $PYREFLY check; cd / && rm -rf $UPSELL_PROJ INFO Checking current directory with auto configuration INFO 0 errors* (glob) No `pyrefly.toml` found — using preset `basic`. Run `pyrefly init` to continue setting up Pyrefly. Docs: * (glob) [0]
. as explicit cwdpyrefly check . is the file-mode equivalent of pyrefly check: a single user-supplied “file” argument that resolves to the cwd. It must produce the same upsell as bare pyrefly check.
$ UPSELL_DOT=$(mktemp -d -p /tmp upsell.XXXXXX) && \ > echo "x = 1" > $UPSELL_DOT/a.py && \ > cd $UPSELL_DOT && $PYREFLY check .; cd / && rm -rf $UPSELL_DOT INFO 0 errors* (glob) No `pyrefly.toml` found — using preset `basic`. Run `pyrefly init` to continue setting up Pyrefly. Docs: * (glob) [0]
mypy.ini works when invoked from a subdirectoryRegression guard: when pyrefly check is invoked from a subdirectory and the resolver migrates a mypy.ini that lives several levels above, the migrated per-module overrides must still apply to the right files. Pyrefly's own upward marker search currently lands the configurer at mypy.ini's parent — so root and config dir already agree — but a future change that decouples those (e.g. routing project-mode through a different entry point) could put them out of sync, and a per-module override silently failing to match is the exact kind of regression this test catches.
The setup: a bad-assignment in app/models/foo.py under [mypy-app.models] with disable_error_code = assignment (mypy's own code name for the same rule), and a separate bad-assignment in app/views/bar.py with no override. Run pyrefly check from sub/inner. The override must hide the models error but leave the views error visible.
$ MYPY_PARENT=$(mktemp -d -p /tmp upsell.XXXXXX) && \ > printf '[mypy]\nfiles = app\n\n[mypy-app.models]\ndisable_error_code = assignment\n' \ > > $MYPY_PARENT/mypy.ini && \ > mkdir -p $MYPY_PARENT/app/models $MYPY_PARENT/app/views && \ > echo "x: str = 0" > $MYPY_PARENT/app/models/foo.py && \ > echo "y: str = 0" > $MYPY_PARENT/app/views/bar.py && \ > mkdir -p $MYPY_PARENT/sub/inner && \ > cd $MYPY_PARENT/sub/inner && \ > $PYREFLY check --output-format=min-text \ > >$MYPY_PARENT/out.txt 2>$MYPY_PARENT/err.txt; \ > echo "---STDOUT---"; cat $MYPY_PARENT/out.txt; \ > echo "---STDERR---"; cat $MYPY_PARENT/err.txt; \ > cd / && rm -rf $MYPY_PARENT ---STDOUT--- ERROR */app/views/bar.py:1:* (glob) ---STDERR--- INFO Found `*/mypy.ini` marking project root, checking root directory with auto configuration (glob) INFO 1 error* (glob) No `pyrefly.toml` found — using settings imported from your `mypy.ini` (preset: legacy). Run `pyrefly init` to continue setting up Pyrefly. Docs: * (glob) [0]
$ touch $TMPDIR/pyrefly.toml && \ > echo "x: str = 42" > $TMPDIR/test.py && $PYREFLY check $TMPDIR/test.py --output-format=min-text ERROR */test.py:1:* (glob) [1]
$ touch $TMPDIR/pyrefly.toml && \ > echo "x: str = 42" > $TMPDIR/test.py && $PYREFLY check $TMPDIR/test.py --output-format json | $JQ '.[] | length' 1 [0]
$ touch $TMPDIR/pyrefly.toml && \ > echo "x: str = 12" > $TMPDIR/same_name.py && \ > echo "x: str = True" > $TMPDIR/same_name.pyi && \ > $PYREFLY check --python-version 3.13.0 $TMPDIR/same_name.py $TMPDIR/same_name.pyi --output-format=min-text ERROR */same_name.py*:1:10-* (glob) ERROR */same_name.py*:1:10-* (glob) [1]
$ touch $TMPDIR/pyrefly.toml && \ > echo "x: str = 12" > $TMPDIR/hidden1.py && \ > echo "import hidden1; y: int = hidden1.x" > $TMPDIR/hidden2.py && \ > $PYREFLY check --python-version 3.13.0 $TMPDIR/hidden2.py --output-format=min-text ERROR */hidden2.py:1:26-35: `str` is not assignable to `int` [bad-assignment] (glob) [1]
$ python3 -m venv $TMPDIR/venv 2>$TMPDIR/venv_stderr || \ > { cat $TMPDIR/venv_stderr >&2; false; } && \ > echo "import third_party.test2" > $TMPDIR/test.py && \ > export site_packages=$($TMPDIR/venv/bin/python -c "import site; print(site.getsitepackages()[0])") && \ > mkdir $site_packages/third_party && \ > echo "x = 1" > $site_packages/third_party/test2.py && \ > $PYREFLY check $TMPDIR/test.py INFO 0 errors* (glob) [0]
$ VENV_PROJECT=$(mktemp -d -p /tmp venv.XXXXXX) && \ > python3 -m venv $VENV_PROJECT/venv 2>$TMPDIR/venv_stderr || \ > { cat $TMPDIR/venv_stderr >&2; false; } && \ > site_packages=$($VENV_PROJECT/venv/bin/python -c "import site; print(site.getsitepackages()[0])") && \ > mkdir $site_packages/third_party && \ > printf 'def f() -> int:\n return 1\n' > $site_packages/third_party/test2.py && \ > mkdir $VENV_PROJECT/pkg && touch $VENV_PROJECT/pkg/__init__.py && \ > echo "from third_party.test2 import f; g = f" > $VENV_PROJECT/pkg/mod.py && \ > echo "from pkg.mod import g; from typing import assert_type; assert_type(g(), int)" > $VENV_PROJECT/main.py && \ > $PYREFLY check $VENV_PROJECT/main.py; STATUS=$?; rm -rf $VENV_PROJECT; exit $STATUS INFO 0 errors* (glob) No `pyrefly.toml` found — using preset `basic`. Run `pyrefly init` to continue setting up Pyrefly. Docs: https://pyrefly.org/en/docs/installation/ [0]
$ CONDA_PROJECT=$(mktemp -d -p /tmp conda.XXXXXX) && \ > python3 -m venv $CONDA_PROJECT/conda 2>$TMPDIR/venv_stderr || \ > { cat $TMPDIR/venv_stderr >&2; false; } && \ > site_packages=$($CONDA_PROJECT/conda/bin/python -c "import site; print(site.getsitepackages()[0])") && \ > mkdir $site_packages/third_party && \ > echo "x = 1" > $site_packages/third_party/test2.py && \ > touch $CONDA_PROJECT/pyrefly.toml && \ > echo "import third_party.test2" > $CONDA_PROJECT/test.py && \ > env -u VIRTUAL_ENV CONDA_PREFIX=$CONDA_PROJECT/conda $PYREFLY check $CONDA_PROJECT/test.py; \ > STATUS=$?; rm -rf $CONDA_PROJECT; exit $STATUS INFO 0 errors* (glob) [0]
$ echo "x: str = 0" > $TMPDIR/test.py && \ > $PYREFLY check $TMPDIR/test.py --warn=bad-assignment INFO 0 errors (1 warning not shown)* (glob) [0]
coverage subcommand and not the hidden report alias$ $PYREFLY --help | grep -E "^ +(coverage| report)" coverage Type coverage commands [0]
pyrefly coverage report --help shows correct usage$ $PYREFLY coverage report --help | grep "^Usage:" Usage: pyrefly coverage report [OPTIONS] [FILES]... [0]
pyrefly report alias emits a warning on stderr$ touch $TMPDIR/pyrefly.toml && \ > echo "def f(x: int) -> int: return x" > $TMPDIR/test.py && \ > $PYREFLY report $TMPDIR/test.py 2>&1 | grep "warning:" warning: `pyrefly report` is deprecated; use `pyrefly coverage report` instead [0]
pyrefly coverage report emits modules in a deterministic order across runs$ cd $TMPDIR && rm -rf detrepo && mkdir detrepo && cd detrepo && touch pyrefly.toml && \ > for i in 1 2 3 4 5 6; do echo "def f$i() -> int: return $i" > "m$i.py"; done && \ > $PYREFLY coverage report m1.py m2.py m3.py m4.py m5.py m6.py > a.json 2>/dev/null && \ > $PYREFLY coverage report m1.py m2.py m3.py m4.py m5.py m6.py > b.json 2>/dev/null && \ > diff a.json b.json && echo IDENTICAL IDENTICAL [0]