From c4d3a53cea62ee5965d6f7633d46a35e1e79ae11 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Fri, 19 Jan 2024 12:02:35 -0800 Subject: [PATCH] Add basic tests and CI workflow --- .github/workflows/run_tests.yml | 34 ++++++++++++++++++++++++++++ .gitignore | 6 ++++- pyproject.toml | 9 +++++++- src/tqdm_publisher/publisher.py | 6 ++++- tests/test_basic.py | 40 +++++++++++++++++++++++++++++++++ tests/utils.py | 17 ++++++++++++++ 6 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/run_tests.yml create mode 100644 tests/test_basic.py create mode 100644 tests/utils.py diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 0000000..6c89f61 --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -0,0 +1,34 @@ +name: Minimal and Full Tests +on: + schedule: + - cron: "0 16 * * *" # Daily at noon EST + workflow_dispatch: + +jobs: + run: + name: ${{ matrix.os }} Python ${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11"] + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: s-weigand/setup-conda@v1 + - uses: actions/checkout@v3 + - run: git fetch --prune --unshallow --tags + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Global Setup + run: | + python -m pip install -U pip # Official recommended way + pip install pytest-xdist + + - name: Install tqdm_publisher with minimal requirements + run: pip install .[test] + + - name: Run full pytest with coverage + run: pytest -rsx -n auto --dist loadscope --cov=./ --cov-report xml:./codecov.xml diff --git a/.gitignore b/.gitignore index 464aa35..6065a51 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ __pycache__/ *.egg-info -dist \ No newline at end of file +dist + +.coverage +.coverage.* +codecov.xml \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index a2591a9..415797e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,14 @@ classifiers = [ "Operating System :: Unix", ] dependencies = [ - 'tqdm>=4.49.0' + "tqdm>=4.49.0" +] + +[project.optional-dependencies] +test = [ + "pytest", + "pytest-asyncio", + "pytest-cov" ] [project.urls] diff --git a/src/tqdm_publisher/publisher.py b/src/tqdm_publisher/publisher.py index 25dceb1..ab53463 100644 --- a/src/tqdm_publisher/publisher.py +++ b/src/tqdm_publisher/publisher.py @@ -27,4 +27,8 @@ def subscribe(self, callback): # Unsubscribe from updates def unsubscribe(self, callback_id): - del self.callbacks[callback_id] \ No newline at end of file + try: + del self.callbacks[callback_id] + return True + except KeyError: + return False \ No newline at end of file diff --git a/tests/test_basic.py b/tests/test_basic.py new file mode 100644 index 0000000..e5e2ce3 --- /dev/null +++ b/tests/test_basic.py @@ -0,0 +1,40 @@ +from tqdm_publisher import TQDMPublisher +import pytest +from utils import create_tasks +import asyncio + +def test_initialization(): + publisher = TQDMPublisher() + assert len(publisher.callbacks) == 0 + +@pytest.mark.asyncio +async def test_subscription_and_callback_execution(): + n_callback_executions = 0 + + def test_callback(data): + nonlocal n_callback_executions + n_callback_executions += 1 + assert 'n' in data and 'total' in data + + tasks = create_tasks() + publisher = TQDMPublisher(asyncio.as_completed(tasks), total=len(tasks)) + callback_id = publisher.subscribe(test_callback) + + assert callback_id in publisher.callbacks + + # Simulate an update to trigger the callback + for f in publisher: + await f + + assert n_callback_executions > 0 + +def test_unsubscription(): + def dummy_callback(data): + pass + + tasks = [] + publisher = TQDMPublisher(asyncio.as_completed(tasks), total=len(tasks)) + callback_id = publisher.subscribe(dummy_callback) + result = publisher.unsubscribe(callback_id) + assert result == True + assert callback_id not in publisher.callbacks diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..d237b06 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,17 @@ +import asyncio +import random + +async def sleep_func(sleep_duration: float = 1) -> float: + await asyncio.sleep(delay=sleep_duration) + + +def create_tasks(): + n = 10**5 + sleep_durations = [random.uniform(0, 5.0) for _ in range(n)] + tasks = [] + + for sleep_duration in sleep_durations: + task = asyncio.create_task(sleep_func(sleep_duration=sleep_duration)) + tasks.append(task) + + return tasks