Repository: robotpy/robotpy-wpilib-utilities (language: python)

Issue:
Move commandbased to robotpy-commands-v1
I feel this makes sense, since the implementation for commands v2 would be totally different. 


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml
new file mode 100644
index 0000000..36cbd1c
--- /dev/null
+++ b/.github/workflows/dist.yml
@@ -0,0 +1,80 @@
+---
+name: dist
+
+on:
+  pull_request:
+  push:
+    branches:
+    - main
+    tags:
+    - '*'
+
+jobs:
+  check:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+    - uses: psf/black@stable
+
+  #
+  # Test wheels
+  #
+
+  test:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [windows-latest, macos-latest, ubuntu-18.04]
+        python_version: [3.6, 3.7, 3.8, 3.9]
+        architecture: [x86, x64]
+        exclude:
+        - os: macos-latest
+          architecture: x86
+        - os: ubuntu-18.04
+          architecture: x86
+
+    steps:
+    - uses: actions/checkout@v2
+    - uses: actions/setup-python@v2
+      with:
+        python-version: ${{ matrix.python_version }}
+        architecture: ${{ matrix.architecture }}
+
+    - uses: robotpy/build-actions/install-pure-build-deps@v2021
+    - uses: robotpy/build-actions/build-wheel@v2021
+    - uses: robotpy/build-actions/test-native-wheel@v2021
+
+    - uses: robotpy/build-actions/validate-sphinx@v2021
+      if: ${{ matrix.python_version == '3.7' && matrix.os == 'ubuntu-18.04' }}
+
+
+  publish-pypi:
+    runs-on: ubuntu-latest
+    needs: [check, test]
+    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
+
+    steps:
+    - uses: actions/checkout@v2
+    
+    - uses: actions/setup-python@v2
+      with:
+        python-version: 3.8
+    - run: pip install wheel
+
+    - name: Build packages
+      run: python setup.py sdist bdist_wheel
+    - name: Publish to PyPI
+      uses: pypa/gh-action-pypi-publish@master
+      with:
+        user: __token__
+        password: ${{ secrets.pypi_password }}
+
+  ping:
+    runs-on: ubuntu-latest
+    needs: [publish-pypi]
+    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
+
+    steps:
+    - uses: robotpy/build-actions/ping-meta@v2021
+      with:
+        token: ${{ secrets.REPO_ACCESS_TOKEN }}
diff --git a/.gittrack b/.gittrack
deleted file mode 100644
index 80ddaf4..0000000
--- a/.gittrack
+++ /dev/null
@@ -1,6 +0,0 @@
-[git-source-track]
-validation_root = robotpy_ext/common_drivers/navx
-exclude_commits_file = devtools/exclude_commits_navx
-upstream_root = ../navxmxp
-upstream_commit = d8fa37624f03379ed0e2a8f6f458e3cecbb59247
-
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 56ed441..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,51 +0,0 @@
-language: python
-dist: xenial
-
-python:
-  - "3.8"
-  - "3.7"
-  - "3.6"
-
-env:
-  - RUNCOVERAGE=1 ROBOTPY_NO_DEPS=1
-
-# # command to install dependencies
-# install:
-#   - pip install -U pip
-#   - pip install -r travis-requirements.txt
-# # command to run tests
-# script:
-#   - tests/run_tests.sh
-script: true
-
-matrix:
-  fast_finish: true
-
-  include:
-  - name: format-check
-    python: "3.6"
-    install:
-    - pip install black
-    script:
-    - black --check --diff .
-
-  - stage: deploy
-    if: tag IS present
-    python: "3.7"
-    install: skip
-    script: skip
-    deploy:
-    - provider: pypi
-      user: $PYPI_USERNAME
-      password: $PYPI_PASSWORD
-      distributions: sdist bdist_wheel
-      on:
-        tags: true
-
-notifications:
-  webhooks:
-    urls:
-      - https://webhooks.gitter.im/e/2fb1c026e64fdd70d27b
-    on_success: change
-    on_failure: always
-    on_start: never
diff --git a/commandbased/__init__.py b/commandbased/__init__.py
deleted file mode 100644
index e8af80c..0000000
--- a/commandbased/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .commandbasedrobot import CommandBasedRobot
-
-from .cancelcommand import CancelCommand
diff --git a/commandbased/cancelcommand.py b/commandbased/cancelcommand.py
deleted file mode 100644
index e26b687..0000000
--- a/commandbased/cancelcommand.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from wpilib.command import Command
-
-
-def checkIfCanceled(self):
-    if self.forceCancel:
-        self.forceCancel = False
-        return True
-
-    return self._isFinished()
-
-
-class CancelCommand(Command):
-    """
-    When this command is run, it cancels the command it was passed.
-    """
-
-    def __init__(self, command: Command):
-        """
-        :param command: The command to cancel.
-        """
-        super().__init__("Cancel %s" % command)
-
-        self.command = command
-
-    def initialize(self):
-        if self.command.isRunning():
-            self.command.cancel()
-
-    def isFinished(self):
-        return not self.command.isRunning()
diff --git a/commandbased/commandbasedrobot.py b/commandbased/commandbasedrobot.py
deleted file mode 100644
index 73dbafa..0000000
--- a/commandbased/commandbasedrobot.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from wpilib import TimedRobot
-from wpilib.command import Scheduler
-
-
-class CommandBasedRobot(TimedRobot):
-    """
-    The base class for a Command-Based Robot. To use, instantiate commands and
-    trigger them.
-    """
-
-    def startCompetition(self):
-        """Initalizes the scheduler before starting robotInit()"""
-
-        self.scheduler = Scheduler.getInstance()
-        super().startCompetition()
-
-    def commandPeriodic(self):
-        """
-        Run the scheduler regularly. If an error occurs during a competition,
-        prevent it from crashing the program.
-        """
-
-        try:
-            self.scheduler.run()
-        except Exception as error:
-            if not self.ds.isFMSAttached():
-                raise
-
-            """Just to be safe, stop all running commands."""
-            self.scheduler.removeAll()
-
-            self.handleCrash(error)
-
-    autonomousPeriodic = commandPeriodic
-    teleopPeriodic = commandPeriodic
-    disabledPeriodic = commandPeriodic
-    # testPeriodic deliberately omitted
-
-    def handleCrash(self, error):
-        """
-        Called if an exception is raised in the Scheduler during a competition.
-        Writes an error message to the driver station by default. If you want
-        more complex behavior, override this method in your robot class.
-        """
-
-        self.ds.reportError(str(error), printTrace=True)
diff --git a/commandbased/flowcontrol.py b/commandbased/flowcontrol.py
deleted file mode 100644
index 601de49..0000000
--- a/commandbased/flowcontrol.py
+++ /dev/null
@@ -1,205 +0,0 @@
-"""
-These functions can be used to make programming CommandGroups much more
-intuitive. For more information, check each method's docstring.
-"""
-
-from wpilib.command import Command, CommandGroup, ConditionalCommand
-from commandbased.cancelcommand import CancelCommand
-
-import inspect
-
-__all__ = ["IF", "ELIF", "ELSE", "WHILE", "RETURN", "BREAK", "CommandFlow"]
-
-
-class ConditionalFlow(ConditionalCommand):
-    def __init__(self, name, onTrue, onFalse, condition):
-        ConditionalCommand.__init__(self, name, onTrue, onFalse)
-
-        self.flowCondition = condition
-
-    def _condition(self):
-        return self.flowCondition()
-
-
-class CommandFlow(CommandGroup):
-    def __init__(self, name):
-        CommandGroup.__init__(self, name)
-
-        callingFlow = _getCommandFlow()
-        self._source = getattr(callingFlow, "_source", self)
-        self._ifStack = None
-
-    def _popIfStack(self):
-        """
-        We buffer conditionals until the last moment so we don't have trouble with
-        Commands being locked when they're added to a CommandGroup.
-        """
-        if self._ifStack:
-            top = self._ifStack.pop(0)
-            cmd = None
-            for x in reversed(self._ifStack):
-                if x[0]:
-                    cmd = ConditionalFlow("flowcontrolELIF", x[1], cmd, x[0])
-
-                else:
-                    cmd = x[1]
-
-            cmd = ConditionalFlow("flowcontrolIF", top[1], cmd, top[0])
-
-            self._ifStack = None
-            self.addSequential(cmd)
-
-    # These _hook methods ensure we always add our buffered conditions
-    def addSequential(self, cmd, timeout=None):
-        self._popIfStack()
-        if timeout is None:
-            CommandGroup.addSequential(self, cmd)
-        else:
-            CommandGroup.addSequential(self, cmd, timeout)
-
-    def addParallel(self, cmd, timeout=None):
-        self._popIfStack()
-        if timeout is None:
-            CommandGroup.addParallel(self, cmd)
-        else:
-            CommandGroup.addParallel(self, cmd, timeout)
-
-    def start(self):
-        self._popIfStack()
-        CommandGroup.start(self)
-
-    def setParent(self, parent):
-        self._popIfStack()
-        CommandGroup.setParent(self, parent)
-
-
-class CommandFlowWhile(CommandFlow):
-    def __init__(self, name, condition):
-        super().__init__(name)
-
-        self.whileCondition = condition
-
-    def isFinished(self):
-        if CommandGroup.isFinished(self):
-            if not self.whileCondition():
-                return True
-
-            self.start()
-
-        return False
-
-
-def _getCommandFlow():
-    """
-    Does some rather ugly stack inspection to find out which CommandGroup the
-    calling function was triggered from.
-    """
-    # https://stackoverflow.com/a/14694234
-    stack = inspect.stack()
-    frame = stack[2].frame
-    while "self" not in frame.f_locals:
-        frame = frame.f_back
-        if frame is None:
-            raise ValueError("Could not find calling class for %s" % stack[1].function)
-
-    cg = frame.f_locals["self"]
-    if not isinstance(cg, CommandFlow):
-        raise ValueError(
-            "%s may not be used outside of a CommandFlow" % stack[1].function
-        )
-
-    return cg
-
-
-def _buildCommandFlow(func, parent):
-    """Turns the given function into a full CommandGroup."""
-    cg = CommandFlow(func.__name__)
-    func(cg)
-
-    return cg
-
-
-def IF(condition):
-    """
-    Use as a decorator for a function. That function will be placed into a
-    CommandGroup and run inside a ConditionalCommand with the given condition.
-    The decorated function must accept one positional argument that will be used
-    as its 'self'.
-    """
-
-    def flowcontrolIF(func):
-        parent = _getCommandFlow()
-        cg = _buildCommandFlow(func, parent)
-
-        try:
-            parent._popIfStack()
-        except AttributeError:
-            pass
-
-        parent._ifStack = [(condition, cg)]
-
-    return flowcontrolIF
-
-
-def ELIF(condition):
-    """
-    Use as a decorator for a function. That function will be placed into a
-    CommandGroup which will be triggered by a ConditionalCommand that uses the
-    passed condition. That ConditionalCommand will then be added as the onFalse
-    for the ConditionalCommand created by a previous IF or ELIF.
-    """
-
-    def flowcontrolELIF(func):
-        parent = _getCommandFlow()
-        cg = _buildCommandFlow(func, parent)
-
-        try:
-            parent._ifStack.append((condition, cg))
-        except AttributeError:
-            raise ValueError("Cannot use ELIF without IF")
-
-    return flowcontrolELIF
-
-
-def ELSE(func):
-    """
-    Use as a decorator for a function. That function will be placed into a
-    CommandGroup which will be added as the onFalse for the ConditionalCommand
-    created by a previous IF or ELIF.
-    """
-
-    parent = _getCommandFlow()
-    cg = _buildCommandFlow(func, parent)
-
-    try:
-        parent._ifStack.append((None, cg))
-    except AttributeError:
-        raise ValueError("Cannot use ELSE without IF")
-
-    parent._popIfStack()
-
-
-def WHILE(condition):
-    """
-    Use as a decorator for a function. That function will be placed into a
-    CommandGroup, which will be added to a ConditionalCommand. It will be
-    modified to restart itself automatically.
-    """
-    raise NotImplementedError("WHILE does not yet work with Commands v1")
-
-
-def RETURN():
-    """
-    Calling this function will end the source CommandGroup immediately.
-    """
-
-    parent = _getCommandFlow()
-    parent.addSequential(CancelCommand(parent._source))
-
-
-def BREAK(steps=1):
-    """
-    Calling this function will end the loop that contains it. Pass an integer to
-    break out of that number of nested loops.
-    """
-    raise ValueError("Cannot BREAK outside of a loop")
diff --git a/docs/api.rst b/docs/api.rst
index 6816ff2..09d1f59 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -4,7 +4,6 @@ Utilities API
 
 .. toctree::
 
-    commandbased
     magicbot
     robotpy_ext.autonomous
     robotpy_ext.common_drivers
diff --git a/docs/commandbased.rst b/docs/commandbased.rst
deleted file mode 100644
index 9edc7ea..0000000
--- a/docs/commandbased.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-
-commandbased module
--------------------
-
-.. automodule:: commandbased.commandbasedrobot
-    :members:
-    :exclude-members: autonomousPeriodic, disabledPeriodic, teleopPeriodic, robotInit, testPeriodic, startCompetition
-    :show-inheritance:
-
-.. automodule:: commandbased.cancelcommand
-    :members:
-
-.. automodule:: commandbased.flowcontrol
-    :members:
\ No newline at end of file
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 5708dd3..e9f019a 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,6 +1,6 @@
 # This file is intended for use on readthedocs
---find-links https://www.tortall.net/~robotpy/wheels/2020/linux_x86_64
+--find-links https://www.tortall.net/~robotpy/wheels/2021/linux_x86_64
 sphinx-autodoc-typehints
+sphinx-rtd-theme
 robotpy-sphinx-plugin
-robotpy-commands-v1
 -e .
diff --git a/docs/robotpy_ext.autonomous.rst b/docs/robotpy_ext.autonomous.rst
index 4659fa2..b010192 100644
--- a/docs/robotpy_ext.autonomous.rst
+++ b/docs/robotpy_ext.autonomous.rst
@@ -9,14 +9,6 @@ robotpy_ext.autonomous.selector module
     :undoc-members:
     :show-inheritance:
 
-robotpy_ext.autonomous.selector_tests module
---------------------------------------------
-
-.. automodule:: robotpy_ext.autonomous.selector_tests
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
 robotpy_ext.autonomous.stateful_autonomous module
 -------------------------------------------------
 
diff --git a/navx_license.txt b/navx_license.txt
deleted file mode 100644
index a91c2ef..0000000
--- a/navx_license.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2015 Kauai Labs, Inc.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
\ No newline at end of file
diff --git a/robotpy_ext/__init__.py b/robotpy_ext/__init__.py
index 5d337f8..6478f51 100644
--- a/robotpy_ext/__init__.py
+++ b/robotpy_ext/__init__.py
@@ -1,4 +1,4 @@
 try:
-    from .version import __version__
+    from .version import version as __version__
 except ImportError:  # pragma: nocover
     __version__ = "master"
diff --git a/setup.cfg b/setup.cfg
index 0be05c9..ef7d3c9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,38 @@
 [metadata]
+name = robotpy-wpilib-utilities
+description = Useful utility functions/objects for RobotPy
+long_description = file: README.rst
+long_description_content_type = text/x-rst
+author = RobotPy Development Team
+author_email = robotpy@googlegroups.com
+url = https://github.com/robotpy/robotpy-wpilib-utilities
+license = BSD-3-Clause
 # Include the license file in wheels.
 license_file = LICENSE
+
+classifiers =
+    Development Status :: 5 - Production/Stable
+    Intended Audience :: Developers
+    License :: OSI Approved :: BSD License
+    Operating System :: OS Independent
+    Programming Language :: Python :: 3 :: Only
+    Programming Language :: Python :: 3.6
+    Programming Language :: Python :: 3.7
+    Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
+    Topic :: Software Development
+    Topic :: Scientific/Engineering
+
+[options]
+zip_safe = False
+include_package_data = True
+packages = find:
+install_requires =
+    pynetworktables>=2020.0.0
+    wpilib>=2021.1.2.0,<2022.0.0
+setup_requires =
+    setuptools_scm == 5.0.*
+python_requires = >=3.6
+
+[options.package_data]
+magicbot = py.typed, magic_reset.pyi
\ No newline at end of file
diff --git a/setup.py b/setup.py
index d720eec..b0fc364 100644
--- a/setup.py
+++ b/setup.py
@@ -1,69 +1,5 @@
 #!/usr/bin/env python3
 
-import os
-from os.path import dirname, exists, join
-import sys, subprocess
-from setuptools import setup, find_packages
+from setuptools import setup
 
-setup_dir = dirname(__file__)
-git_dir = join(setup_dir, ".git")
-base_package = "robotpy_ext"
-version_file = join(setup_dir, base_package, "version.py")
-
-# Automatically generate a version.py based on the git version
-if exists(git_dir):
-    p = subprocess.Popen(
-        ["git", "describe", "--tags", "--long", "--dirty=-dirty"],
-        stdout=subprocess.PIPE,
-        stderr=subprocess.PIPE,
-    )
-    out, err = p.communicate()
-    # Make sure the git version has at least one tag
-    if err:
-        print("Error: You need to create a tag for this repo to use the builder")
-        sys.exit(1)
-
-    # Convert git version to PEP440 compliant version
-    # - Older versions of pip choke on local identifiers, so we can't include the git commit
-    v, commits, local = out.decode("utf-8").rstrip().split("-", 2)
-    if commits != "0" or "-dirty" in local:
-        v = "%s.post0.dev%s" % (v, commits)
-
-    # Create the version.py file
-    with open(version_file, "w") as fp:
-        fp.write("# Autogenerated by setup.py\n__version__ = '{0}'".format(v))
-
-if exists(version_file):
-    with open(join(setup_dir, base_package, "version.py"), "r") as fp:
-        exec(fp.read(), globals())
-else:
-    __version__ = "master"
-
-with open(join(setup_dir, "README.rst"), "r") as readme_file:
-    long_description = readme_file.read()
-
-setup(
-    name="robotpy-wpilib-utilities",
-    version=__version__,
-    description="Useful utility functions/objects for RobotPy",
-    long_description=long_description,
-    author="RobotPy Development Team",
-    author_email="robotpy@googlegroups.com",
-    url="https://github.com/robotpy/robotpy-wpilib-utilities",
-    keywords="frc first robotics",
-    install_requires=["pynetworktables>=2020.0.0", "wpilib>=2020.3.2.0,<2021.0.0"],
-    packages=find_packages(),
-    package_data={"magicbot": ["py.typed", "magic_reset.pyi"]},
-    license="BSD",
-    classifiers=[
-        "Development Status :: 5 - Production/Stable",
-        "Intended Audience :: Developers",
-        "Intended Audience :: Education",
-        "License :: OSI Approved :: BSD License",
-        "Operating System :: OS Independent",
-        "Programming Language :: Python :: 3.6",
-        "Programming Language :: Python :: 3.7",
-        "Programming Language :: Python :: 3.8",
-        "Topic :: Scientific/Engineering",
-    ],
-)
+setup(use_scm_version={"write_to": "robotpy_ext/version.py"})
diff --git a/travis-requirements.txt b/travis-requirements.txt
deleted file mode 100644
index 620cd26..0000000
--- a/travis-requirements.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-
-git+git://github.com/robotpy/pynetworktables.git#egg=pynetworktables
-
-git+git://github.com/robotpy/robotpy-wpilib.git#egg=robotpy-hal-base&subdirectory=hal-base
-git+git://github.com/robotpy/robotpy-wpilib.git#egg=robotpy-hal-sim&subdirectory=hal-sim
-git+git://github.com/robotpy/robotpy-wpilib.git#egg=wpilib&subdirectory=wpilib
-
--e .
-
-git+git://github.com/robotpy/pyfrc.git#egg=pyfrc
-
-pytest>=2.8
-coverage


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 3e5294f..7532386 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,19 +7,11 @@
 
 
 def pytest_runtest_setup():
-    import networktables
-
-    networktables.NetworkTables.startTestMode()
+    pass
 
 
 def pytest_runtest_teardown():
-    import networktables
-
-    networktables.NetworkTables.shutdown()
-
-    from wpilib import SmartDashboard
-
-    SmartDashboard._reset()
+    pass
 
 
 @pytest.fixture(scope="function")
diff --git a/tests/requirements.txt b/tests/requirements.txt
new file mode 100644
index 0000000..55b033e
--- /dev/null
+++ b/tests/requirements.txt
@@ -0,0 +1,1 @@
+pytest
\ No newline at end of file
diff --git a/tests/run_tests.py b/tests/run_tests.py
new file mode 100755
index 0000000..3068610
--- /dev/null
+++ b/tests/run_tests.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python3
+
+import os
+from os.path import abspath, dirname
+import sys
+import subprocess
+
+if __name__ == "__main__":
+
+    root = abspath(dirname(__file__))
+    os.chdir(root)
+
+    subprocess.check_call([sys.executable, "-m", "py.test"])
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
deleted file mode 100755
index 2e91f80..0000000
--- a/tests/run_tests.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash -e
-
-cd $(dirname $0)
-
-export PYTHONPATH=".."
-
-if [ "$RUNCOVERAGE" == "1" ]; then
-    python3 -m coverage run --source robotpy_ext,magicbot -m pytest "$@"
-    python3 -m coverage report -m
-else
-    python3 -m pytest "$@"
-fi
-
-# Run tests on examples repository
-if [ "$CONTINUOUS_INTEGRATION" == "true" ]; then
-    curl https://raw.githubusercontent.com/robotpy/examples/master/_remote_tests.sh | bash -s ext
-fi
diff --git a/tests/test_flowcontrol.py b/tests/test_flowcontrol.py
deleted file mode 100644
index 8f427af..0000000
--- a/tests/test_flowcontrol.py
+++ /dev/null
@@ -1,388 +0,0 @@
-import pytest
-
-from wpilib.command import Scheduler, InstantCommand
-
-import commandbased.flowcontrol as fc
-
-
-def run_command(cmd):
-    cmd.start()
-
-    scheduler = Scheduler.getInstance()
-    for x in range(0, 2000):
-        scheduler.run()
-        if not cmd.isRunning():
-            return True
-
-    return False
-
-
-class CounterCommand(InstantCommand):
-    def __init__(self):
-        super().__init__("Counter")
-        self.counter = 0
-
-    def initialize(self):
-        self.counter += 1
-
-
-def test_if():
-    cmd1 = CounterCommand()
-    cmd2 = CounterCommand()
-
-    class IfCommand(fc.CommandFlow):
-        def __init__(self):
-            nonlocal cmd1, cmd2
-
-            super().__init__("Test IF")
-            self.setRunWhenDisabled(True)
-
-            @fc.IF(lambda: False)
-            def increment_cmd1(self):
-                self.addSequential(cmd1)
-
-            @fc.IF(lambda: True)
-            def increment_cmd2(self):
-                self.addSequential(cmd2)
-
-    cmd = IfCommand()
-
-    assert run_command(cmd)
-    assert cmd1.counter == 0
-    assert cmd2.counter == 1
-
-
-def test_elif():
-    cmd1 = CounterCommand()
-    cmd2 = CounterCommand()
-    cmd3 = CounterCommand()
-
-    class ElifCommand(fc.CommandFlow):
-        def __init__(self):
-            nonlocal cmd1, cmd2, cmd3
-
-            super().__init__("Test ELIF")
-            self.setRunWhenDisabled(True)
-
-            @fc.IF(lambda: False)
-            def increment_cmd1(self):
-                self.addSequential(cmd1)
-
-            @fc.ELIF(lambda: True)
-            def increment_cmd2(self):
-                self.addSequential(cmd2)
-
-            @fc.ELIF(lambda: True)
-            def increment_cmd3(self):
-                self.addSequential(cmd3)
-
-    cmd = ElifCommand()
-
-    assert run_command(cmd)
-    assert cmd1.counter == 0
-    assert cmd2.counter == 1
-    assert cmd3.counter == 0
-
-
-def test_else():
-    cmd1 = CounterCommand()
-    cmd2 = CounterCommand()
-    cmd3 = CounterCommand()
-
-    class ElseCommand(fc.CommandFlow):
-        def __init__(self):
-            nonlocal cmd1, cmd2, cmd3
-
-            super().__init__("Test ELSE")
-            self.setRunWhenDisabled(True)
-
-            @fc.IF(lambda: False)
-            def increment_cmd1(self):
-                self.addSequential(cmd1)
-
-            @fc.ELIF(lambda: False)
-            def increment_cmd2(self):
-                self.addSequential(cmd2)
-
-            @fc.ELSE
-            def increment_cmd3(self):
-                self.addSequential(cmd3)
-
-    cmd = ElseCommand()
-
-    assert run_command(cmd)
-    assert cmd1.counter == 0
-    assert cmd2.counter == 0
-    assert cmd3.counter == 1
-
-
-# def test_while():
-#     cmd1 = CounterCommand()
-
-#     class WhileCommand(fc.CommandFlow):
-#         def __init__(self):
-#             nonlocal cmd1
-
-#             super().__init__("Test WHILE")
-#             self.setRunWhenDisabled(True)
-
-#             x = 5
-
-#             def do_loop():
-#                 nonlocal x
-
-#                 x -= 1
-
-#                 return x > 0
-
-#             @fc.WHILE(do_loop)
-#             def count_to_four(self):
-#                 self.addSequential(cmd1)
-
-#     cmd = WhileCommand()
-
-#     assert run_command(cmd)
-#     assert cmd1.counter == 4
-
-
-# def test_break():
-#     cmd1 = CounterCommand()
-#     cmd2 = CounterCommand()
-
-#     class BreakCommand(fc.CommandFlow):
-#         def __init__(self):
-#             nonlocal cmd1, cmd2
-
-#             super().__init__("Test BREAK")
-#             self.setRunWhenDisabled(True)
-
-#             x = 5
-
-#             def do_loop():
-#                 nonlocal x
-
-#                 x -= 1
-
-#                 return x > 0
-
-#             @fc.WHILE(do_loop)
-#             def count_to_four(self):
-#                 self.addSequential(cmd1)
-
-#                 @fc.IF(lambda: x == 3)
-#                 def breakLoop(self):
-#                     fc.BREAK()
-
-#             self.addSequential(cmd2)
-
-#     cmd = BreakCommand()
-
-#     assert run_command(cmd)
-#     assert cmd1.counter == 2
-#     assert cmd2.counter == 1
-
-
-def test_return():
-    cmd1 = CounterCommand()
-    cmd2 = CounterCommand()
-    cmd3 = CounterCommand()
-    cmd4 = CounterCommand()
-    cmd5 = CounterCommand()
-    cmd6 = CounterCommand()
-
-    class ReturnCommand(fc.CommandFlow):
-        def __init__(self):
-            nonlocal cmd1, cmd2, cmd3, cmd4, cmd5, cmd6
-
-            super().__init__("Test RETURN")
-            self.setRunWhenDisabled(True)
-
-            self.addSequential(cmd1)
-
-            @fc.IF(lambda: True)
-            def increment_cmd2(self):
-                self.addSequential(cmd2)
-
-                @fc.IF(lambda: True)
-                def increment_cmd3(self):
-                    self.addSequential(cmd3)
-
-                    @fc.IF(lambda: True)
-                    def do_return(self):
-                        fc.RETURN()
-
-                    self.addSequential(cmd4)
-
-                self.addSequential(cmd5)
-
-            self.addSequential(cmd6)
-
-    cmd = ReturnCommand()
-
-    assert run_command(cmd)
-    assert cmd1.counter == 1
-    assert cmd2.counter == 1
-    assert cmd3.counter == 1
-    assert cmd4.counter == 0
-    assert cmd5.counter == 0
-    assert cmd6.counter == 0
-
-
-# def test_break2():
-#     cmd1 = CounterCommand()
-#     cmd2 = CounterCommand()
-#     cmd3 = CounterCommand()
-
-#     class BreakCommand(fc.CommandFlow):
-#         def __init__(self):
-#             nonlocal cmd1, cmd2, cmd3
-
-#             super().__init__("Test BREAK 2")
-#             self.setRunWhenDisabled(True)
-
-#             x = 5
-
-#             def do_loop():
-#                 nonlocal x
-
-#                 x -= 1
-
-#                 if x == 0:
-#                     x = 10
-#                     return False
-
-#                 return True
-
-#             @fc.WHILE(lambda: True)
-#             def loop_forever(self):
-#                 self.addSequential(cmd1)
-
-#                 @fc.WHILE(do_loop)
-#                 def count_to_four(self):
-#                     self.addSequential(cmd2)
-
-#                     @fc.IF(lambda: x == 5)
-#                     def breakLoop(self):
-#                         fc.BREAK(2)
-
-#             self.addSequential(cmd3)
-
-#     cmd = BreakCommand()
-
-#     assert run_command(cmd)
-#     assert cmd1.counter == 2
-#     assert cmd2.counter == 9
-#     assert cmd3.counter == 1
-
-
-def test_contained_return():
-    cmd1 = CounterCommand()
-    cmd2 = CounterCommand()
-    cmd3 = CounterCommand()
-    cmd4 = CounterCommand()
-
-    class ReturnCommand(fc.CommandFlow):
-        def __init__(self):
-            nonlocal cmd2, cmd3
-
-            super().__init__("Test RETURN")
-            self.setRunWhenDisabled(True)
-
-            self.addSequential(cmd2)
-            fc.RETURN()
-            self.addSequential(cmd3)
-
-    class ContainerCommand(fc.CommandFlow):
-        def __init__(self):
-            nonlocal cmd1, cmd4
-
-            super().__init__("Test Contained RETURN")
-            self.setRunWhenDisabled(True)
-
-            self.addSequential(cmd1)
-            self.addSequential(ReturnCommand())
-            self.addSequential(cmd4)
-
-    cmd = ContainerCommand()
-
-    assert run_command(cmd)
-    assert cmd1.counter == 1
-    assert cmd2.counter == 1
-    assert cmd3.counter == 0
-    assert cmd4.counter == 1
-
-
-def test_safety():
-    class ElifBeforeIfCommand(fc.CommandFlow):
-        def __init__(self):
-            super().__init__("Test ELIF before IF")
-
-            @fc.ELIF(lambda: True)
-            def do_nothing(self):
-                self.addSequential(InstantCommand())
-
-    with pytest.raises(ValueError):
-        cmd = ElifBeforeIfCommand()
-
-    class ElseBeforeIfCommand(fc.CommandFlow):
-        def __init__(self):
-            super().__init__("Test ELSE before IF")
-
-            @fc.ELSE
-            def do_nothing(self):
-                self.addSequential(InstantCommand())
-
-    with pytest.raises(ValueError):
-        cmd = ElseBeforeIfCommand()
-
-    # class BreakTooBigCommand(fc.CommandFlow):
-    #     def __init__(self):
-    #         super().__init__("Test BREAK too large")
-    #         self.setRunWhenDisabled(True)
-
-    #         @fc.WHILE(lambda: True)
-    #         def do_loop(self):
-    #             fc.BREAK(2)
-
-    # cmd = BreakTooBigCommand()
-
-    # with pytest.raises(ValueError):
-    #     run_command(cmd)
-
-    # class BreakWithoutLoop(fc.CommandFlow):
-    #     def __init__(self):
-    #         super().__init__("Test BREAK outside loop")
-    #         self.setRunWhenDisabled(True)
-
-    #         fc.BREAK()
-
-    # with pytest.raises(ValueError):
-    #     cmd = BreakWithoutLoop()
-
-    # class BreakAfterLoop(fc.CommandFlow):
-    #     def __init__(self):
-    #         super().__init__("Test BREAK after loop")
-    #         self.setRunWhenDisabled(True)
-
-    #         x = 5
-
-    #         def do_loop():
-    #             nonlocal x
-
-    #             x -= 1
-
-    #             return x > 0
-
-    #         @fc.WHILE(do_loop)
-    #         def count_to_four(self):
-    #             self.addSequential(InstantCommand())
-
-    #         @fc.IF(lambda: True)
-    #         def late_break(self):
-    #             fc.BREAK()
-
-    # with pytest.raises(ValueError):
-    #     cmd = BreakAfterLoop()
-
-    with pytest.raises(ValueError):
-        fc.RETURN()
diff --git a/tests/test_toggle.py b/tests/test_toggle.py
index e3a5fb2..8aa7791 100644
--- a/tests/test_toggle.py
+++ b/tests/test_toggle.py
@@ -1,5 +1,5 @@
 from robotpy_ext.control.toggle import Toggle
-from robotpy_ext.misc.precise_delay import PreciseDelay
+from robotpy_ext.misc.precise_delay import NotifierDelay
 
 
 class FakeJoystick:
@@ -36,9 +36,10 @@ def test_toggle():
 
 
 def test_toggle_debounce():
-    delay = PreciseDelay(2.1)
+    # TODO: use simulated time
+    delay = NotifierDelay(0.5)
     joystick = FakeJoystick()
-    toggleButton = Toggle(joystick, 1, 2)
+    toggleButton = Toggle(joystick, 1, 0.1)
     assert toggleButton.off
     joystick.press(1)
     assert toggleButton.on