Permalink
...
Comparing changes
Open a pull request
- 10 commits
- 12 files changed
- 0 commit comments
- 1 contributor
Unified
Split
Showing
with
266 additions
and 155 deletions.
- +1 −0 .travis.yml
- +12 −3 docs/testing.rst
- +51 −0 lib/pyfrc/mains/cli_add_tests.py
- +35 −26 lib/pyfrc/mains/cli_deploy.py
- +56 −17 lib/pyfrc/mains/cli_test.py
- +90 −104 lib/pyfrc/robotpy/installer.py
- +15 −0 lib/pyfrc/util.py
- +2 −2 requirements.txt
- +1 −1 samples/physics-4wheel/src/robot.py
- +1 −1 samples/physics-mecanum/src/robot.py
- +1 −1 samples/physics/src/robot.py
- +1 −0 setup.py
View
1
.travis.yml
| @@ -1,6 +1,7 @@ | ||
| language: python | ||
| python: | ||
| - "3.4" | ||
| + - "3.5" | ||
| # command to install dependencies | ||
| install: | ||
| - "pip install . coverage" | ||
View
15
docs/testing.rst
| @@ -26,9 +26,18 @@ Builtin unit tests | ||
| pyfrc comes with testing functions that can be used to test basic | ||
| functionality of just about any robot, including running through a | ||
| -simulated practice match. To use these standardized tests, just create a file | ||
| -in your tests directory called pyfrc_test.py, and put the following contents | ||
| -in the file:: | ||
| +simulated practice match. As of pyfrc 2016.1.1, to add these standardized | ||
| +tests to your robot code, you can run the following: | ||
| + | ||
| +.. code-block:: sh | ||
| + | ||
| + Windows: py robot.py add-tests | ||
| + | ||
| + Linux/OSX: python3 robot.py add-tests | ||
| + | ||
| +Running this command creates a directory called 'tests' if it doesn't already | ||
| +exist, and then creates a file in your tests directory called pyfrc_test.py, | ||
| +and put the following contents in the file:: | ||
| from pyfrc.tests import * | ||
View
51
lib/pyfrc/mains/cli_add_tests.py
| @@ -0,0 +1,51 @@ | ||
| + | ||
| +import inspect | ||
| +import os | ||
| +from os.path import abspath, dirname, exists, join | ||
| + | ||
| +builtin_tests = \ | ||
| +"""''' | ||
| + This test module imports tests that come with pyfrc, and can be used | ||
| + to test basic functionality of just about any robot. | ||
| +''' | ||
| + | ||
| +from pyfrc.tests import * | ||
| +""" | ||
| + | ||
| + | ||
| +class PyFrcAddTests: | ||
| + | ||
| + 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) | ||
| + | ||
| + try_dirs = [ | ||
| + abspath(join(robot_path, 'tests')), | ||
| + abspath(join(robot_path, '..', 'tests')) | ||
| + ] | ||
| + | ||
| + test_directory = try_dirs[0] | ||
| + | ||
| + for d in try_dirs: | ||
| + if exists(d): | ||
| + test_directory = d | ||
| + break | ||
| + else: | ||
| + os.makedirs(test_directory) | ||
| + | ||
| + print("Tests directory is %s" % test_directory) | ||
| + print() | ||
| + builtin_tests_file = join(test_directory, 'pyfrc_test.py') | ||
| + if exists(builtin_tests_file): | ||
| + print('- pyfrc_test.py already exists') | ||
| + else: | ||
| + with open(builtin_tests_file, 'w') as fp: | ||
| + fp.write(builtin_tests) | ||
| + print("- builtin tests created at", builtin_tests_file) | ||
| + | ||
| + print() | ||
| + print("Robot tests can be ran via 'python3 robot.py test'") |
View
61
lib/pyfrc/mains/cli_deploy.py
| @@ -10,6 +10,7 @@ | ||
| from os.path import abspath, basename, dirname, exists, join, splitext | ||
| from ..robotpy import installer | ||
| +from ..util import print_err, yesno | ||
| import wpilib | ||
| @@ -21,9 +22,6 @@ def relpath(path): | ||
| '''Path helper, gives you a path relative to this file''' | ||
| return os.path.normpath(os.path.join(os.path.abspath(os.path.dirname(__file__)), path)) | ||
| -def print_err(*args): | ||
| - print(*args, file=sys.stderr) | ||
| - | ||
| class PyFrcDeploy: | ||
| """ | ||
| Uploads your robot code to the robot and executes it immediately | ||
| @@ -51,6 +49,9 @@ def __init__(self, parser): | ||
| parser.add_argument('-n', '--no-version-check', action='store_true', default=False, | ||
| help="If specified, don't verify that your local wpilib install matches the version on the robot (not recommended)") | ||
| + | ||
| + parser.add_argument('--robot', default=None, | ||
| + help="Set hostname or IP address of robot") | ||
| def run(self, options, robot_class, **static_options): | ||
| @@ -65,8 +66,20 @@ def run(self, options, robot_class, **static_options): | ||
| retval = tester.run_test([], robot_class, options.builtin, ignore_missing_test=True) | ||
| if retval != 0: | ||
| - print_err("ERROR: Your robot tests failed, aborting upload. Use --skip-tests if you want to upload anyways") | ||
| - return retval | ||
| + print_err("ERROR: Your robot tests failed, aborting upload.") | ||
| + if not sys.stdin.isatty(): | ||
| + print_err("- Use --skip-tests if you want to upload anyways") | ||
| + return retval | ||
| + | ||
| + print() | ||
| + if not yesno('- Upload anyways?'): | ||
| + return retval | ||
| + | ||
| + if not yesno('- Are you sure? Your robot code may crash!'): | ||
| + return retval | ||
| + | ||
| + print() | ||
| + print("WARNING: Uploading code against my better judgement...") | ||
| # upload all files in the robot.py source directory | ||
| robot_file = abspath(inspect.getfile(robot_class)) | ||
| @@ -135,14 +148,11 @@ def run(self, options, robot_class, **static_options): | ||
| nc_thread = None | ||
| try: | ||
| - controller = installer.SshController(cfg_filename, | ||
| - username='lvuser', | ||
| - password='', | ||
| - allow_mitm=True) | ||
| - | ||
| - # This asks the user if not configured, so get the value first | ||
| - hostname = controller.hostname | ||
| - print("Deploying to robot at", hostname) | ||
| + controller = installer.ssh_from_cfg(cfg_filename, | ||
| + username='lvuser', | ||
| + password='', | ||
| + hostname=options.robot, | ||
| + allow_mitm=True) | ||
| # Housekeeping first | ||
| logger.debug('SSH: %s', sshcmd) | ||
| @@ -159,16 +169,6 @@ def run(self, options, robot_class, **static_options): | ||
| finally: | ||
| shutil.rmtree(tmp_dir) | ||
| - if not options.in_place: | ||
| - # Restart the robot code and we're done! | ||
| - sshcmd = "/bin/bash -ce '" + \ | ||
| - '. /etc/profile.d/natinst-path.sh; ' + \ | ||
| - 'chown -R lvuser:ni %s; ' + \ | ||
| - '/usr/local/frc/bin/frcKillRobot.sh -t -r' + \ | ||
| - "'" | ||
| - | ||
| - sshcmd %= (py_deploy_dir) | ||
| - | ||
| # start the netconsole listener now if requested, *before* we | ||
| # actually start the robot code, so we can see all messages | ||
| if options.nc: | ||
| @@ -181,9 +181,18 @@ def run(self, options, robot_class, **static_options): | ||
| nc_event.wait(5) | ||
| logger.info("Netconsole is listening...") | ||
| - logger.debug('SSH: %s', sshcmd) | ||
| - controller.ssh(sshcmd) | ||
| - controller.close() | ||
| + if not options.in_place: | ||
| + # Restart the robot code and we're done! | ||
| + sshcmd = "/bin/bash -ce '" + \ | ||
| + '. /etc/profile.d/natinst-path.sh; ' + \ | ||
| + 'chown -R lvuser:ni %s; ' + \ | ||
| + '/usr/local/frc/bin/frcKillRobot.sh -t -r' + \ | ||
| + "'" | ||
| + | ||
| + sshcmd %= (py_deploy_dir) | ||
| + | ||
| + logger.debug('SSH: %s', sshcmd) | ||
| + controller.ssh(sshcmd) | ||
| except installer.SshExecError as e: | ||
| if e.retval == 87: | ||
View
73
lib/pyfrc/mains/cli_test.py
| @@ -1,16 +1,21 @@ | ||
| import os | ||
| import inspect | ||
| +import sys | ||
| from os.path import abspath, dirname, exists, join | ||
| import pytest | ||
| +from ..util import yesno | ||
| from ..test_support import pytest_plugin | ||
| # TODO: setting the plugins so that the end user can invoke py.test directly | ||
| # could be a useful thing. Will have to consider that later. | ||
| +class _TryAgain(Exception): | ||
| + pass | ||
| + | ||
| # | ||
| # main test class | ||
| # | ||
| @@ -31,22 +36,29 @@ def __init__(self, parser=None): | ||
| def run(self, options, robot_class, **static_options): | ||
| # wrapper around run_test that sets the appropriate mode | ||
| + | ||
| from .. import config | ||
| config.mode = 'test' | ||
| config.coverage_mode = options.coverage_mode | ||
| return self.run_test(options.pytest_args, robot_class, options.builtin, **static_options) | ||
| - def run_test(self, pytest_args, robot_class, use_builtin, **static_options): | ||
| + def run_test(self, *a, **k): | ||
| + try: | ||
| + return self._run_test(*a, **k) | ||
| + except _TryAgain: | ||
| + return self._run_test(*a, **k) | ||
| + | ||
| + def _run_test(self, pytest_args, robot_class, use_builtin, **static_options): | ||
| # find test directory, change current directory so py.test can find the tests | ||
| # -> assume that tests reside in tests or ../tests | ||
| curdir = abspath(os.getcwd()) | ||
| + self.robot_class = robot_class | ||
| robot_file = abspath(inspect.getfile(robot_class)) | ||
| robot_path = dirname(robot_file) | ||
| - test_directory = None | ||
| if robot_file.endswith('cProfile.py'): | ||
| # so, the module for the robot class is __main__, and __main__ is | ||
| @@ -58,36 +70,63 @@ def run_test(self, pytest_args, robot_class, use_builtin, **static_options): | ||
| "ERROR: Cannot run profiling from a directory that does not contain robot.py" | ||
| return 1 | ||
| - try_dirs = [join(robot_path, 'tests'), abspath(join(robot_path, '..', 'tests'))] | ||
| + self.try_dirs = [ | ||
| + abspath(join(robot_path, 'tests')), | ||
| + abspath(join(robot_path, '..', 'tests')) | ||
| + ] | ||
| - for d in try_dirs: | ||
| + for d in self.try_dirs: | ||
| if exists(d): | ||
| test_directory = d | ||
| + os.chdir(test_directory) | ||
| break | ||
| - | ||
| - if test_directory is None: | ||
| + else: | ||
| if not use_builtin: | ||
| print("ERROR: Cannot run robot tests, as test directory was not found!") | ||
| - print() | ||
| - print("Looked for tests at:") | ||
| - for d in try_dirs: | ||
| - print('- %s' % d) | ||
| - print() | ||
| - print("If you don't want to write your own tests, use the --builtin option to run") | ||
| - print("generic tests that should work on any robot.") | ||
| + retv = self._no_tests() | ||
| return 1 | ||
| from ..tests import basic | ||
| pytest_args.insert(0, abspath(inspect.getfile(basic))) | ||
| - else: | ||
| - os.chdir(test_directory) | ||
| try: | ||
| - return pytest.main(pytest_args, | ||
| + retv = pytest.main(pytest_args, | ||
| plugins=[pytest_plugin.PyFrcPlugin(robot_class, | ||
| robot_file, | ||
| robot_path)]) | ||
| finally: | ||
| os.chdir(curdir) | ||
| - | ||
| + # requires pytest 2.8.x | ||
| + if retv == 5: | ||
| + print() | ||
| + print("ERROR: a tests directory was found, but no tests were defined") | ||
| + retv = self._no_tests(retv) | ||
| + | ||
| + return retv | ||
| + | ||
| + def _no_tests(self, r=1): | ||
| + print() | ||
| + print("Looked for tests at:") | ||
| + for d in self.try_dirs: | ||
| + print('- %s' % d) | ||
| + print() | ||
| + print("If you don't want to write your own tests, pyfrc comes with generic tests") | ||
| + print("that can test basic functionality of most robots. You can run them by") | ||
| + print("specifying the --builtin option.") | ||
| + print() | ||
| + | ||
| + if not sys.stdin.isatty(): | ||
| + print("Alternatively, to create a tests directory with the builtin tests, you can run:") | ||
| + print() | ||
| + print(" python3 robot.py add-tests") | ||
| + print() | ||
| + else: | ||
| + if yesno("Create a tests directory with builtin tests now?"): | ||
| + from .cli_add_tests import PyFrcAddTests | ||
| + add_tests = PyFrcAddTests() | ||
| + add_tests.run(None, self.robot_class) | ||
| + | ||
| + raise _TryAgain() | ||
| + | ||
| + return r | ||
Oops, something went wrong.