diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index 88ab14d..ffa9482 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -27,6 +27,7 @@ jobs: run: | pip --disable-pip-version-check install mypy pip --disable-pip-version-check install -e . + pip --disable-pip-version-check install -r tests/requirements.txt - name: Run mypy uses: liskin/gh-problem-matcher-wrap@v2 with: @@ -57,9 +58,42 @@ jobs: name: dist path: dist + test: + needs: [build] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ["ubuntu-22.04", "macos-14", "windows-2022"] + + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: dist + path: dist + + - name: Install + shell: bash + working-directory: dist + run: python -m pip --disable-pip-version-check install *.whl + + - name: Install test dependencies + working-directory: tests + run: python -m pip --disable-pip-version-check install -r requirements.txt + + - name: Test wheel + working-directory: tests + run: python run_tests.py + publish: runs-on: ubuntu-latest - needs: [check, check-mypy, build] + needs: [check, check-mypy, build, test] permissions: id-token: write if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') diff --git a/robotpy/main.py b/robotpy/main.py index 0d290d0..203c56d 100644 --- a/robotpy/main.py +++ b/robotpy/main.py @@ -49,6 +49,17 @@ def _load_robot_class(): print(f"ERROR: {robot_py_path} is a directory", file=sys.stderr) sys.exit(1) + # Ensure that it's robot.py and not Robot.py or similar by checking the + # directory entries of its parent + lower_name = robot_py_path.name.lower() + for entry in robot_py_path.parent.iterdir(): + if entry.name.lower() == lower_name and entry.name != robot_py_path.name: + print( + f"ERROR: {robot_py_path} must be {robot_py_path.name} (found '{entry.name}')", + file=sys.stderr, + ) + sys.exit(1) + # Add that directory to sys.path to ensure that imports work as expected sys.path.insert(0, str(robot_py_path.parent.absolute())) diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..e079f8a --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1 @@ +pytest diff --git a/tests/run_tests.py b/tests/run_tests.py new file mode 100755 index 0000000..e0c13e5 --- /dev/null +++ b/tests/run_tests.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import os +from os.path import abspath, dirname, join +import sys +import subprocess + +if __name__ == "__main__": + root = abspath(dirname(__file__)) + os.chdir(root) + + # Run pytest + subprocess.check_call([sys.executable, "-m", "pytest"]) diff --git a/tests/test_robot_case.py b/tests/test_robot_case.py new file mode 100644 index 0000000..6d2bd3a --- /dev/null +++ b/tests/test_robot_case.py @@ -0,0 +1,62 @@ +import sys +import textwrap + +import pytest + +from robotpy import main + + +class DummyWpilib: + class RobotBase: + pass + + +@pytest.fixture(autouse=True) +def mock_wpilib(monkeypatch): + monkeypatch.setitem(sys.modules, "wpilib", DummyWpilib) + + +def test_load_robot_class_exact_case(tmp_path, monkeypatch, capsys): + # create correct-case file + robot_py = tmp_path / "robot.py" + robot_py.write_text( + textwrap.dedent( + """ + import wpilib + class MyRobot(wpilib.RobotBase): + pass + """ + ) + ) + + monkeypatch.setattr(main, "robot_py_path", robot_py) + + # Should pass (exact case) + main._load_robot_class() + + +def test_load_robot_class_wrong_case(tmp_path, monkeypatch, capsys): + robot_py = tmp_path / "robot.py" + Robot_py = tmp_path / "Robot.py" + Robot_py.write_text( + textwrap.dedent( + """ + import wpilib + class MyRobot(wpilib.RobotBase): + pass + """ + ) + ) + + case_insensitive_fs = robot_py.exists() + + monkeypatch.setattr(main, "robot_py_path", robot_py) + + with pytest.raises(SystemExit): + main._load_robot_class() + err = capsys.readouterr().err + + if case_insensitive_fs: + assert "must be robot.py" in err + else: + assert "does not exist" in err