diff --git a/pyfrc/mains/cli_add_tests.py b/pyfrc/mains/cli_add_tests.py index 27dae17..404af8d 100644 --- a/pyfrc/mains/cli_add_tests.py +++ b/pyfrc/mains/cli_add_tests.py @@ -1,6 +1,6 @@ -import inspect import os -from os.path import abspath, dirname, exists, join +import pathlib +import sys builtin_tests = """''' This test module imports tests that come with pyfrc, and can be used @@ -12,31 +12,36 @@ class PyFrcAddTests: + """ + Adds default pyfrc tests to your robot project directory + """ + def __init__(self, parser=None): pass - def run(self, options, robot_class, **static_options): - robot_file = abspath(inspect.getfile(robot_class)) - robot_path = dirname(robot_file) + def run(self, main_file: pathlib.Path, project_path: pathlib.Path): + if not main_file.exists(): + print( + f"ERROR: is this a robot project? {main_file} does not exist", + file=sys.stderr, + ) + return 1 - try_dirs = [ - abspath(join(robot_path, "tests")), - abspath(join(robot_path, "..", "tests")), - ] + try_dirs = [project_path / "tests", project_path / ".." / "tests"] test_directory = try_dirs[0] for d in try_dirs: - if exists(d): + if d.exists(): test_directory = d break else: - os.makedirs(test_directory) + test_directory.mkdir(parents=True) - print("Tests directory is %s" % test_directory) + print(f"Tests directory is {test_directory}") print() - builtin_tests_file = join(test_directory, "pyfrc_test.py") - if exists(builtin_tests_file): + builtin_tests_file = test_directory / "pyfrc_test.py" + if builtin_tests_file.exists(): print("- pyfrc_test.py already exists") else: with open(builtin_tests_file, "w") as fp: @@ -44,4 +49,4 @@ def run(self, options, robot_class, **static_options): print("- builtin tests created at", builtin_tests_file) print() - print("Robot tests can be ran via 'python3 robot.py test'") + print("Robot tests can be ran via 'python3 -m robotpy test'") diff --git a/pyfrc/mains/cli_coverage.py b/pyfrc/mains/cli_coverage.py index de309ea..35060af 100644 --- a/pyfrc/mains/cli_coverage.py +++ b/pyfrc/mains/cli_coverage.py @@ -1,14 +1,16 @@ import argparse -import inspect from os.path import dirname +import pathlib import subprocess import sys +import typing class PyFrcCoverage: """ - Wraps other commands by running them via the coverage module. Requires - the coverage module to be installed. + Wraps other commands by running them via the coverage module. + + Requires the coverage module to be installed. """ def __init__(self, parser: argparse.ArgumentParser): @@ -19,7 +21,13 @@ def __init__(self, parser: argparse.ArgumentParser): "args", nargs=argparse.REMAINDER, help="Arguments to pass to robot.py" ) - def run(self, options, robot_class, **static_options): + def run( + self, + main_file: pathlib.Path, + project_path: pathlib.Path, + parallel_mode: bool, + args: typing.List[str], + ): try: import coverage except ImportError: @@ -30,13 +38,11 @@ def run(self, options, robot_class, **static_options): ) return 1 - if len(options.args) == 0: + if len(args) == 0: print("ERROR: Coverage command requires arguments to run other commands") return 1 - file_location = inspect.getfile(robot_class) - - option_args = list(options.args) + option_args = args if option_args[0] == "test": option_args.insert(1, "--coverage-mode") @@ -47,19 +53,20 @@ def run(self, options, robot_class, **static_options): "coverage", "run", "--source", - dirname(file_location), + str(project_path), ] - if options.parallel_mode: + if parallel_mode: args.append("--parallel-mode") - args.append(file_location) + args += ["-m", "robotpy", "--main", main_file] args += option_args + print("+", *args, file=sys.stderr) retval = subprocess.call(args) if retval != 0: return retval - if options.parallel_mode: + if parallel_mode: subprocess.call([sys.executable, "-m", "coverage", "combine"]) args = [sys.executable, "-m", "coverage", "report", "-m"] diff --git a/pyfrc/mains/cli_create_physics.py b/pyfrc/mains/cli_create_physics.py index 27c6921..548adda 100644 --- a/pyfrc/mains/cli_create_physics.py +++ b/pyfrc/mains/cli_create_physics.py @@ -1,7 +1,6 @@ -import inspect -import json -from os import mkdir -from os.path import abspath, dirname, exists, join +import pathlib +import sys + physics_starter = ''' # @@ -90,16 +89,27 @@ def update_sim(self, now: float, tm_diff: float) -> None: class PyFrcCreatePhysics: + """ + Create physics + """ + def __init__(self, parser=None): pass - def run(self, options, robot_class, **static_options): - robot_file = abspath(inspect.getfile(robot_class)) - robot_path = dirname(robot_file) - sim_path = join(robot_path, "sim") - - physics_file = join(robot_path, "physics.py") - if exists(physics_file): + def run( + self, + main_file: pathlib.Path, + project_path: pathlib.Path, + ): + if not main_file.exists(): + print( + f"ERROR: is this a robot project? {main_file} does not exist", + file=sys.stderr, + ) + return 1 + + physics_file = project_path / "physics.py" + if physics_file.exists(): print("- physics.py already exists") else: with open(physics_file, "w") as fp: @@ -107,4 +117,4 @@ def run(self, options, robot_class, **static_options): print("- physics file created at", physics_file) print() - print("Robot simulation can be run via 'python3 robot.py sim'") + print("Robot simulation can be run via 'python3 -m robotpy sim'") diff --git a/pyfrc/mains/cli_profiler.py b/pyfrc/mains/cli_profiler.py index ddce1c2..fc0986f 100644 --- a/pyfrc/mains/cli_profiler.py +++ b/pyfrc/mains/cli_profiler.py @@ -1,13 +1,16 @@ import argparse import inspect from os.path import abspath +import pathlib import subprocess import sys +import typing class PyFrcProfiler: """ Wraps other commands by running them via the built in cProfile module. + Use this to profile your program and figure out where you're spending a lot of time (note that cProfile only profiles the main thread) """ @@ -17,13 +20,15 @@ def __init__(self, parser): "-o", "--outfile", default=None, help="Save stats to " ) parser.add_argument( - "args", nargs=argparse.REMAINDER, help="Arguments to pass to robot.py" + "args", nargs=argparse.REMAINDER, help="Arguments to pass to robotpy module" ) - def run(self, options, robot_class, **static_options): - print("profiling is not yet implemented for RobotPy 2020") - return 1 - + def run( + self, + main_file: pathlib.Path, + outfile: typing.Optional[str], + args: typing.List[str], + ): try: import cProfile except ImportError: @@ -33,14 +38,12 @@ def run(self, options, robot_class, **static_options): ) return 1 - if len(options.args) == 0: + if len(args) == 0: print("ERROR: Profiler command requires arguments to run other commands") return 1 - file_location = abspath(inspect.getfile(robot_class)) - - if options.outfile: - profile_args = ["-o", options.outfile] + if outfile: + profile_args = ["-o", outfile] else: profile_args = ["-s", "tottime"] @@ -48,8 +51,9 @@ def run(self, options, robot_class, **static_options): args = ( [sys.executable, "-m", "cProfile"] + profile_args - + [file_location] - + options.args + + ["-m", "robotpy", "--main", str(main_file)] + + args ) + print("+", *args, file=sys.stderr) return subprocess.call(args) diff --git a/pyfrc/mains/cli_sim.py b/pyfrc/mains/cli_sim.py index 904bfb6..13c0766 100644 --- a/pyfrc/mains/cli_sim.py +++ b/pyfrc/mains/cli_sim.py @@ -1,11 +1,12 @@ import os -from os.path import abspath, dirname import argparse import importlib.metadata -import inspect import logging import pathlib import sys +import typing + +import wpilib logger = logging.getLogger("pyfrc.sim") @@ -56,12 +57,18 @@ def __init__(self, parser: argparse.ArgumentParser): help=cmd_help, ) - def run(self, options, robot_class, **static_options): - if not options.nogui: + def run( + self, + options: argparse.Namespace, + nogui: bool, + project_path: pathlib.Path, + robot_class: typing.Type[wpilib.RobotBase], + ): + if not nogui: try: import halsim_gui except ImportError: - print("robotpy-halsim-gui is not installed!") + print("robotpy-halsim-gui is not installed!", file=sys.stderr) exit(1) else: halsim_gui.loadExtension() @@ -74,7 +81,7 @@ def run(self, options, robot_class, **static_options): try: module.loadExtension() except: - print(f"Error loading {name}!") + print(f"Error loading {name}!", file=sys.stderr) raise os.chdir(cwd) @@ -82,11 +89,9 @@ def run(self, options, robot_class, **static_options): # initialize physics, attach to the user robot class from ..physics.core import PhysicsInterface, PhysicsInitException - robot_file = pathlib.Path(inspect.getfile(robot_class)).absolute() - try: _, robot_class = PhysicsInterface._create_and_attach( - robot_class, robot_file.parent + robot_class, project_path ) # run the robot diff --git a/pyfrc/mains/cli_test.py b/pyfrc/mains/cli_test.py index 8bcf0a0..5b59f33 100644 --- a/pyfrc/mains/cli_test.py +++ b/pyfrc/mains/cli_test.py @@ -3,7 +3,9 @@ import inspect import pathlib import sys +import typing +import wpilib import pytest @@ -47,47 +49,51 @@ def __init__(self, parser=None): help="To pass args to pytest, specify --, then the args", ) - def run(self, options, robot_class, **static_options): - # wrapper around run_test that sets the appropriate mode - - return self.run_test( - options.pytest_args, robot_class, options.builtin, **static_options - ) - - def run_test(self, *a, **k): + def run( + self, + main_file: pathlib.Path, + project_path: pathlib.Path, + robot_class: typing.Type[wpilib.RobotBase], + builtin: bool, + coverage_mode: bool, + pytest_args: typing.List[str], + ): try: - return self._run_test(*a, **k) + return self._run_test( + main_file, + project_path, + robot_class, + builtin, + coverage_mode, + pytest_args, + ) except _TryAgain: - return self._run_test(*a, **k) + return self._run_test( + main_file, + project_path, + robot_class, + builtin, + coverage_mode, + pytest_args, + ) - def _run_test(self, pytest_args, robot_class, use_builtin, **static_options): + def _run_test( + self, + main_file: pathlib.Path, + project_path: pathlib.Path, + robot_class: typing.Type[wpilib.RobotBase], + builtin: bool, + coverage_mode: bool, + pytest_args: typing.List[str], + ): # find test directory, change current directory so pytest can find the tests # -> assume that tests reside in tests or ../tests curdir = pathlib.Path.cwd().absolute() - self.robot_class = robot_class - robot_file = pathlib.Path(inspect.getfile(robot_class)).absolute() - - # In some cases __main__.__file__ is not an absolute path, and some - # internals depend on that being correct. Set it up before we change - # directories - sys.modules["__main__"].__file__ = abspath(sys.modules["__main__"].__file__) - - if robot_file.name == "cProfile.py": - # so, the module for the robot class is __main__, and __main__ is - # cProfile so try to find it - robot_file = curdir / "robot.py" - - if not robot_file.exists(): - print( - "ERROR: Cannot run profiling from a directory that does not contain robot.py" - ) - return 1 - self.try_dirs = [ - (robot_file.parent / "tests").absolute(), - (robot_file.parent / ".." / "tests").absolute(), + (project_path / "tests").absolute(), + (project_path / ".." / "tests").absolute(), ] for d in self.try_dirs: @@ -96,9 +102,9 @@ def _run_test(self, pytest_args, robot_class, use_builtin, **static_options): os.chdir(test_directory) break else: - if not use_builtin: + if not builtin: print("ERROR: Cannot run robot tests, as test directory was not found!") - retv = self._no_tests() + retv = self._no_tests(main_file, project_path) return 1 from ..tests import basic @@ -108,7 +114,7 @@ def _run_test(self, pytest_args, robot_class, use_builtin, **static_options): try: retv = pytest.main( pytest_args, - plugins=[pytest_plugin.PyFrcPlugin(robot_class, robot_file)], + plugins=[pytest_plugin.PyFrcPlugin(robot_class, main_file)], ) finally: os.chdir(curdir) @@ -117,11 +123,13 @@ def _run_test(self, pytest_args, robot_class, use_builtin, **static_options): if retv == 5: print() print("ERROR: a tests directory was found, but no tests were defined") - retv = self._no_tests(retv) + retv = self._no_tests(main_file, project_path, retv) return retv - def _no_tests(self, r=1): + def _no_tests( + self, main_file: pathlib.Path, project_path: pathlib.Path, r: int = 1 + ): print() print("Looked for tests at:") for d in self.try_dirs: @@ -146,7 +154,7 @@ def _no_tests(self, r=1): from .cli_add_tests import PyFrcAddTests add_tests = PyFrcAddTests() - add_tests.run(None, self.robot_class) + add_tests.run(main_file, project_path) raise _TryAgain() diff --git a/setup.cfg b/setup.cfg index a189ca3..4ddd905 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ install_requires = pint>=0.11.0 wpilib>=2024.0.0b2.post1,<2025 + robotpy-cli~=2024.0b setup_requires = setuptools_scm > 6 python_requires = >=3.8