diff --git a/.github/workflows/test.sh b/.github/workflows/test.sh deleted file mode 100644 index 4fff2d1..0000000 --- a/.github/workflows/test.sh +++ /dev/null @@ -1,122 +0,0 @@ -set -e - -PLATFORM=$(python -c 'import sys; print(sys.platform)') - -RED="\033[31;1m" -BLUE="\033[34m" -MAGENTA="\033[35m" -NORMAL="\033[0m" - -ptest() { echo -e "\n${MAGENTA}[TEST] $@${NORMAL}\n" ; } -perror() { echo -e "\n${RED}[ERROR] $@${NORMAL}\n" ; } -prun() { echo -e "${BLUE}\$ $@ ${NORMAL}" ; "$@" ; } - -prun cd example_pkg - -ptest version command runs -prun spin --version - -ptest build command runs -pip install meson-python ninja - - -# Test spin build + debug builds -echo "Creating debug builds" -prun spin build --gcov -ptest Did the build folder get generated? -if [ ! -d "build" ] || [ ! -d "build-install" ]; then - perror build and/or build-install folders did not get generated - exit 1 -else - echo "Yes" -fi -ptest Does the debug build contain gcov files? -matching_files=$(find . -type f -name "*.gc*") -if [ -z "$matching_files" ]; then - perror Debug files did not get generated - exit 1 -else - echo "Yes" -fi - -ptest Does spin expand \$PYTHONPATH? -SPIN_PYTHONPATH=$(spin run 'echo $PYTHONPATH') -echo spin sees PYTHONPATH=\"${SPIN_PYTHONPATH}\" -if [[ ${SPIN_PYTHONPATH} == "\$PYTHONPATH" ]]; then - perror Expected Python path, but got $SPIN_PYTHONPATH instead - exit 1 -fi - -ptest Does \$PYTHONPATH contains site-packages? -if [[ ${SPIN_PYTHONPATH} == *"site-packages" ]]; then - echo "Yes" -else - echo "No; it is $SPIN_PYTHONPATH" -fi - -ptest Does \`spin run\` redirect only command output to stdout? -# Once we're on Python >3.11, can replace syspath manipulation below with -P flag to Python -VERSION=$(spin run python -c 'import sys; del sys.path[0]; import example_pkg; print(example_pkg.__version__)') -if [[ $VERSION == "0.0.0dev0" ]]; then - echo "Yes" -else - perror No, output is $VERSION - exit 1 -fi - -ptest Does spin detect conflict with editable install? -prun pip install --quiet -e . -OUT=$(spin run ls) -if [[ $OUT == *"Warning! An editable installation"* ]]; then - echo "Yes" -else - perror No - exit 1 -fi -prun pip uninstall --quiet -y example_pkg - -if [[ $PLATFORM == linux || $PLATFORM == darwin ]]; then - # Detecting whether a file is executable is not that easy on Windows, - # as it seems to take into consideration whether that file is associated as an executable. - ptest Does \`spin run foo.py\` warn that \`spin run python foo.py\` is likely intended? - OUT=$( touch __foo.py && spin run __foo.py || true ) - rm __foo.py - if [[ $OUT == *"Did you mean to call"* ]]; then - echo "Yes" - else - perror No, output is: $OUT - exit 1 - fi -fi - -ptest test command runs -prun spin test - -ptest Does \`spin test\` work when PYTHONPATH is set? -PYTHONPATH=./tmp spin test - -ptest sdist command runs -prun spin sdist - -ptest example command runs -prun spin example - -ptest docs command runs -pip install --quiet sphinx -prun spin docs - -ptest install command works -prun spin install -(cd /tmp ; [[ $(python -c 'import example_pkg; print(example_pkg.__version__)') == "0.0.0dev0" ]]) -prun pip uninstall -y --quiet example_pkg - -## Platform specialized tests - -if [[ $PLATFORM == linux ]]; then - ptest gdb command runs on linux - prun spin gdb -c 'import example_pkg; example_pkg.echo("hi")' -- --eval "run" --batch -fi - -# if [[ $PLATFORM == darwin ]]; then - -# if [[ $PLATFORM =~ ^win.* ]]; then diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aca2d77..8057750 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,6 +29,6 @@ jobs: run: | sudo apt-get update sudo apt-get install -y gdb - - name: Tests + - name: Tests PyTest run: | - pipx run nox --forcecolor -s tests + pipx run nox --forcecolor -s test diff --git a/noxfile.py b/noxfile.py index 51bbbbc..ea40094 100644 --- a/noxfile.py +++ b/noxfile.py @@ -2,10 +2,6 @@ @nox.session -def tests(session: nox.Session) -> None: - """ - Run the unit and regular tests. - """ - session.install(".", "pytest", "build") +def test(session: nox.Session) -> None: + session.install(".", "pytest", "build", "meson-python", "ninja") session.run("pytest", "spin", *session.posargs) - session.run("bash", ".github/workflows/test.sh", external=True) diff --git a/spin/tests/conftest.py b/spin/tests/conftest.py new file mode 100644 index 0000000..ffafaad --- /dev/null +++ b/spin/tests/conftest.py @@ -0,0 +1,20 @@ +import os + +import pytest + +from spin import util + + +@pytest.fixture(autouse=True) +def pre_post_test(): + # Pre-test code + cwd = os.getcwd() + os.chdir("example_pkg") + + try: + yield + finally: + # Post test code + os.chdir(cwd) + util.run(["git", "clean", "-xdf"], cwd="example_pkg") + os.chdir(cwd) diff --git a/spin/tests/test_build_cmds.py b/spin/tests/test_build_cmds.py new file mode 100644 index 0000000..f802618 --- /dev/null +++ b/spin/tests/test_build_cmds.py @@ -0,0 +1,159 @@ +import os +import subprocess +import sys +import tempfile +from pathlib import Path + +import pytest + +import spin as libspin +from spin.cmds.util import run + +skip_on_windows = pytest.mark.skipif( + sys.platform.startswith("win"), reason="Skipped; platform is Windows" +) + +on_linux = pytest.mark.skipif( + not sys.platform.startswith("linux"), reason="Skipped; platform not Linux" +) + + +def spin(*args, **user_kwargs): + default_kwargs = { + "stdout": subprocess.PIPE, + "stderr": subprocess.PIPE, + "sys_exit": True, + } + return run(["spin"] + list(args), **{**default_kwargs, **user_kwargs}) + + +def stdout(p): + return p.stdout.decode("utf-8").strip() + + +def stderr(p): + return p.stderr.decode("utf-8").strip() + + +def test_get_version(): + p = spin("--version") + assert stdout(p) == f"spin {libspin.__version__}" + + +def test_basic_build(): + """Does the package build?""" + spin("build") + + assert Path("build").exists(), "`build` folder not created after `spin build`" + assert Path( + "build-install" + ).exists(), "`build-install` folder not created after `spin build`" + + +def test_debug_builds(): + """Does spin generate gcov debug output files?""" + spin("build", "--gcov") + + debug_files = Path(".").rglob("*.gcno") + assert len(list(debug_files)) != 0, "debug files not generated for gcov build" + + +def test_expand_pythonpath(): + """Does an $ENV_VAR get expanded in `spin run`?""" + output = spin("run", "echo $PYTHONPATH") + assert any( + p in stdout(output) for p in ("site-packages", "dist-packages") + ), f"Expected value of $PYTHONPATH, got {stdout(output)} instead" + + +def test_run_stdout(): + """Ensure `spin run` only includes command output on stdout.""" + p = spin( + "run", + sys.executable, + "-c", + "import sys; del sys.path[0]; import example_pkg; print(example_pkg.__version__)", + ) + assert ( + stdout(p) == "0.0.0dev0" + ), f"`spin run` stdout did not yield version, but {stdout(p)}" + + +def test_editable_conflict(): + """Do we warn when a conflicting editable install is present?""" + try: + run(["pip", "install", "--quiet", "-e", "."]) + assert "Warning! An editable installation" in stdout( + spin("run", "ls") + ), "Failed to detect and warn about editable install" + finally: + run(["pip", "uninstall", "--quiet", "-y", "example_pkg"]) + + +# Detecting whether a file is executable is not that easy on Windows, +# as it seems to take into consideration whether that file is associated as an executable. +@skip_on_windows +def test_recommend_run_python(): + """If `spin run file.py` is called, is `spin run python file.py` recommended?""" + with tempfile.NamedTemporaryFile(suffix=".py") as f: + p = spin("run", f.name, sys_exit=False) + assert "Did you mean to call" in stdout( + p + ), "Failed to recommend `python run python file.py`" + + +def test_test(): + """Does the test command run?""" + spin("test") + + +def test_test_with_pythonpath(): + """Does `spin test` work when PYTHONPATH is set?""" + spin("test", env={**os.environ, "PYTHONPATH": "/tmp"}) + + +def test_sdist(): + spin("sdist") + + +def test_example(): + spin("example") + + +def test_docs(): + run(["pip", "install", "--quiet", "sphinx"]) + spin("docs") + + +def test_spin_install(): + cwd = os.getcwd() + spin("install") + with tempfile.TemporaryDirectory() as d: + try: + os.chdir(d) + p = run( + [ + sys.executable, + "-c", + "import example_pkg; print(example_pkg.__version__)", + ], + stdout=subprocess.PIPE, + ) + assert stdout(p) == "0.0.0dev0" + finally: + os.chdir(cwd) + run(["pip", "uninstall", "-y", "--quiet", "example_pkg"]) + + +@on_linux +def test_gdb(): + p = spin( + "gdb", + "-c", + 'import example_pkg; example_pkg.echo("hi")', + "--", + "--eval", + "run", + "--batch", + ) + assert "hi" in stdout(p) diff --git a/spin/tests/util.py b/spin/tests/util.py new file mode 100644 index 0000000..09a3d63 --- /dev/null +++ b/spin/tests/util.py @@ -0,0 +1,28 @@ +import os +import subprocess + +from spin.cmds import util + +PKG_NAME = "example_pkg" + + +def assert_cmd(cmd, *args, **kwargs) -> str: + cwd = os.getcwd() + p = util.run( + cmd, + *args, + cwd=PKG_NAME, + replace=False, + sys_exit=False, + output=False, + echo=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kwargs, + ) + os.chdir(cwd) + stdout = p.stdout.decode("utf-8").strip() + if not p.returncode == 0: + print(stdout) + raise AssertionError(f"[{cmd}] failed with exit code {p.returncode}") + return p