From e810edd08ba6e4cf798ec23cd8d93c284500689f Mon Sep 17 00:00:00 2001 From: dwomble Date: Fri, 17 Apr 2026 09:47:36 -0700 Subject: [PATCH 1/4] Added thread-safe tk scheduler --- tests/edmc/TkScheduler.py | 141 ++++++++++++++++++++++++++++++++++++++ tests/harness.py | 58 ++++++++++++++-- 2 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 tests/edmc/TkScheduler.py diff --git a/tests/edmc/TkScheduler.py b/tests/edmc/TkScheduler.py new file mode 100644 index 0000000..a6d11c8 --- /dev/null +++ b/tests/edmc/TkScheduler.py @@ -0,0 +1,141 @@ +import threading +import heapq +import tkinter as tk +from typing import Callable +from typing import Optional, Callable, Dict +from time import sleep, monotonic + +class HarnessTkScheduler: + """Thread-safe scheduler for Tk callbacks during tests.""" + + def __init__(self, root: tk.Tk): + self.root = root + self.main_thread_id = threading.get_ident() + self._lock = threading.Lock() + self._pending: list[tuple[float, str, Callable, tuple]] = [] + self._cancelled: set[str] = set() + self._counter = 0 + self._installed = False + + self._orig_after = None + self._orig_after_idle = None + self._orig_after_cancel = None + + self.enqueued_count = 0 + self.executed_count = 0 + self.failures: list[str] = [] + + def install(self) -> None: + """Install Tk monkeypatches for thread-safe scheduling.""" + if self._installed: + return + + self._orig_after = tk.Misc.after + self._orig_after_idle = tk.Misc.after_idle + self._orig_after_cancel = tk.Misc.after_cancel + + scheduler = self + orig_after = self._orig_after + orig_after_idle = self._orig_after_idle + orig_after_cancel = self._orig_after_cancel + + def patched_after(self, ms, func=None, *args): + if func is None: + func = lambda: None + args = () + + if threading.get_ident() == scheduler.main_thread_id: + return orig_after(self, ms, func, *args) + + try: + delay_ms = int(ms) + except Exception: + delay_ms = 0 + + with scheduler._lock: + token = f"harness-after-{scheduler._counter}" + scheduler._counter += 1 + scheduler.enqueued_count += 1 + heapq.heappush( + scheduler._pending, + (monotonic() + max(delay_ms, 0) / 1000.0, token, func, args), + ) + + return token + + def patched_after_idle(self, func, *args): + if threading.get_ident() == scheduler.main_thread_id: + return orig_after_idle(self, func, *args) + + with scheduler._lock: + token = f"harness-after-{scheduler._counter}" + scheduler._counter += 1 + scheduler.enqueued_count += 1 + heapq.heappush(scheduler._pending, (monotonic(), token, func, args)) + + return token + + def patched_after_cancel(self, id): + if isinstance(id, str) and id.startswith("harness-after-"): + with scheduler._lock: + scheduler._cancelled.add(id) + return None + + return orig_after_cancel(self, id) + + tk.Misc.after = patched_after # type: ignore[assignment] + tk.Misc.after_idle = patched_after_idle # type: ignore[assignment] + tk.Misc.after_cancel = patched_after_cancel # type: ignore[assignment] + self._installed = True + + def uninstall(self) -> None: + """Restore original Tk behavior.""" + if not self._installed: + return + + if self._orig_after is not None: + tk.Misc.after = self._orig_after + if self._orig_after_idle is not None: + tk.Misc.after_idle = self._orig_after_idle + if self._orig_after_cancel is not None: + tk.Misc.after_cancel = self._orig_after_cancel + + self._installed = False + + def pending_count(self) -> int: + with self._lock: + return len(self._pending) + + def drain_due_callbacks(self) -> int: + """Run all queued callbacks that are due on the main thread.""" + if threading.get_ident() != self.main_thread_id: + return 0 + + now = monotonic() + ready: list[tuple[str, Callable, tuple]] = [] + + with self._lock: + while self._pending and self._pending[0][0] <= now: + _, token, func, args = heapq.heappop(self._pending) + ready.append((token, func, args)) + + ran = 0 + for token, func, args in ready: + with self._lock: + if token in self._cancelled: + self._cancelled.remove(token) + continue + + try: + func(*args) + ran += 1 + self.executed_count += 1 + except Exception as exc: + self.failures.append(f"Scheduler callback {token}: {type(exc).__name__}: {exc}") + + return ran + + def consume_failures(self) -> list[str]: + failures = self.failures[:] + self.failures.clear() + return failures diff --git a/tests/harness.py b/tests/harness.py index cf646a2..a6f25d3 100644 --- a/tests/harness.py +++ b/tests/harness.py @@ -9,13 +9,14 @@ threading.get_native_id = lambda: 0 import os +import atexit import json import sys import tomllib from pathlib import Path from typing import Optional, Callable, Dict from datetime import datetime, timezone, timedelta, UTC -from time import sleep +from time import sleep, monotonic import logging import tkinter as tk import threading @@ -59,7 +60,10 @@ import tests.edmc.requests import tests.edmc.mocks as mocks +from tests.edmc.TkScheduler import HarnessTkScheduler from tests.edmc.monitor import monitor + + class TestHarness: """ Main test harness. """ # Prevent pytest from trying to collect this helper class as a test class @@ -102,17 +106,24 @@ def __init__(self, plugin_dir:Optional[str] = None, live_requests:bool = False): self._original_threading_excepthook = threading.excepthook threading.excepthook = self._capture_thread_exception - os.environ['EDMC_NO_UI'] = '1' + #os.environ['EDMC_NO_UI'] = '1' # Create Tk root for headless mode try: if not hasattr(self, '_initialized'): - root:tk.Tk = tk.Tk() - self.parent:tk.Frame = tk.Frame(root) - root.withdraw() + self.root: tk.Tk = tk.Tk() + self.parent:tk.Frame = tk.Frame(self.root) + #root.withdraw() except Exception as e: logging.error(f"Failed to create Tk root: {e}") + if hasattr(self, 'root') and not hasattr(self, '_tk_scheduler'): + self._tk_scheduler = HarnessTkScheduler(self.root) + self._tk_scheduler.install() + if not hasattr(self, '_atexit_registered'): + atexit.register(self._tk_scheduler.uninstall) + self._atexit_registered = True + self._initialized = True def _capture_thread_exception(self, args: threading.ExceptHookArgs) -> None: @@ -126,6 +137,14 @@ def _capture_thread_exception(self, args: threading.ExceptHookArgs) -> None: def assert_no_unhandled_exceptions(self) -> None: """Fail the current test if any unhandled thread exceptions were captured.""" + self.pump_ui(timeout_s=0.4) + + scheduler_failures: list[str] = [] + if hasattr(self, '_tk_scheduler'): + scheduler_failures = self._tk_scheduler.consume_failures() + if scheduler_failures: + self.unhandled_exceptions.extend(scheduler_failures) + if not self.unhandled_exceptions: return @@ -136,6 +155,32 @@ def assert_no_unhandled_exceptions(self) -> None: f"{failures}" ) + def pump_ui(self, timeout_s: float = 0.2, poll_interval_s: float = 0.01) -> None: + """Pump deferred Tk callbacks and process pending UI events.""" + if not hasattr(self, 'root'): + return + + end_time = monotonic() + max(timeout_s, 0.0) + + while True: + callbacks_ran = 0 + if hasattr(self, '_tk_scheduler'): + callbacks_ran = self._tk_scheduler.drain_due_callbacks() + + try: + self.root.update_idletasks() + self.root.update() + except tk.TclError: + return + + pending = self._tk_scheduler.pending_count() if hasattr(self, '_tk_scheduler') else 0 + if pending == 0 and callbacks_ran == 0: + return + if monotonic() >= end_time: + return + + sleep(poll_interval_s) + def set_requests_mode(self, live_requests:bool) -> None: """ Set whether the harness should use live HTTPS requests or mocked responses. """ self.live_requests = live_requests @@ -300,12 +345,15 @@ def fire_event(self, event:dict, state:dict = {}) -> None: logging.error(f"Error in journal handler: {e}") raise + self.pump_ui() + def play_sequence(self, name:str, delay:float = 0.5, state:dict = {}) -> None: """ Fire a sequence of events """ for event in self.events.get(name, []): self.fire_event(event, state=state) state = {} # Clear state after the first event sleep(delay) + self.pump_ui() def _update_journal_files(self, event:dict, state:dict = {}) -> None: """ Simulate EDMC's journal file updates based on the event type. """ From 02cc6edd0631fd0cf1d624267103475d90361d19 Mon Sep 17 00:00:00 2001 From: dwomble Date: Thu, 23 Apr 2026 13:48:58 -0700 Subject: [PATCH 2/4] Added automations and minor harness updates Co-authored-by: Copilot --- .github/workflows/release.yml | 61 ++++++++++++++++++++++++++++++ .github/workflows/unit-testing.yml | 19 ++++++---- README.md | 12 ++++++ tests/harness.py | 58 +++------------------------- tests/pytest.ini | 3 +- tests/test_conformance.py | 4 ++ 6 files changed, 96 insertions(+), 61 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..23515b4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,61 @@ +name: Build Release +on: + release: + types: + - published + +permissions: + contents: write + pull-requests: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: master + fetch-depth: 0 + + - name: Get the Release Tag + id: get_version + run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + + - name: Update release file + run: | + # Overwrite the file 'release' with the tag name + echo "${{ env.VERSION }}" | sed 's/^v//' > version + + - name: Commit and push updated version file + # Adds a commit with the updated version file and pushes it to the master branch. + # The version file is used by the auto-update script to determine the latest version of the plugin. + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add version + git diff --cached --quiet || git commit -m "Update version to ${{ env.VERSION }}" + git push origin HEAD:master + + - name: Zip Folder + # Create a zip file of the repository, excluding unnecessary files and folders. + # The zip file will be attached to the release. + run: zip -r ${{ github.event.repository.name }}.zip . -x ".git/*" ".github/*" ".DS_Store" ".gitignore" ".python-version" ".editorconfig" ".isort.cfg" "requirements*.txt" "history/*" "tests/*" + + - name: Release + uses: softprops/action-gh-release@v2 + with: + files: ${{ github.event.repository.name }}.zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: List files for debugging + run: ls -R + + - name: VirusTotal Scan + # Will run a VirustTotal scan and add the results to the release notes. Requires a VT API key in the repository secrets. + uses: cssnr/virustotal-action@v1 + with: + vt_api_key: ${{ secrets.VT_API_KEY }} + file_globs: ${{ github.event.repository.name }}.zip + release_heading: '### Virus Scan' + update_release: true diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml index e3f3873..6337bfa 100644 --- a/.github/workflows/unit-testing.yml +++ b/.github/workflows/unit-testing.yml @@ -22,6 +22,7 @@ jobs: uses: actions/setup-python@v5.4.0 with: python-version: "3.11" + - name: Install dependencies run: | sudo apt-get update @@ -29,13 +30,17 @@ jobs: python -m pip install --upgrade pip pip install pytest pytest-xvfb if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi - #- name: Lint with flake8 - # run: | - # pip install flake8 + + - name: Lint with flake8 + run: | + pip install flake8 + # stop the build if there are Python syntax errors or undefined names - # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + + # Enable if you wish + #flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with xvfb-pytest run: | - xvfb-run pytest + xvfb-run pytest -m "not slow" diff --git a/README.md b/README.md index 9a604ed..3c9feb4 100644 --- a/README.md +++ b/README.md @@ -29,3 +29,15 @@ Examples using the harness include [Navl's Neutron Dancer](https://github.com/dw ## utils A Library of utilities for EDMC plugins and an EDMC headless test harness. Some utilities are drop-in ready to go, some may require some configuration, and others may need adapting to your plugin. They have comments or README's describing their functionality. + +## .github/workflows + +Some useful `GitHub` workflow scripts. + +### release.yml + +Creates a release `.zip` and puts it through VirusTotal and adds the result to the release notes + +### unit-testing.yml + +Runs `flake8` and `pytest` when code is pushed to the main branch or a PR is created for the main branch. \ No newline at end of file diff --git a/tests/harness.py b/tests/harness.py index a6f25d3..cf646a2 100644 --- a/tests/harness.py +++ b/tests/harness.py @@ -9,14 +9,13 @@ threading.get_native_id = lambda: 0 import os -import atexit import json import sys import tomllib from pathlib import Path from typing import Optional, Callable, Dict from datetime import datetime, timezone, timedelta, UTC -from time import sleep, monotonic +from time import sleep import logging import tkinter as tk import threading @@ -60,10 +59,7 @@ import tests.edmc.requests import tests.edmc.mocks as mocks -from tests.edmc.TkScheduler import HarnessTkScheduler from tests.edmc.monitor import monitor - - class TestHarness: """ Main test harness. """ # Prevent pytest from trying to collect this helper class as a test class @@ -106,24 +102,17 @@ def __init__(self, plugin_dir:Optional[str] = None, live_requests:bool = False): self._original_threading_excepthook = threading.excepthook threading.excepthook = self._capture_thread_exception - #os.environ['EDMC_NO_UI'] = '1' + os.environ['EDMC_NO_UI'] = '1' # Create Tk root for headless mode try: if not hasattr(self, '_initialized'): - self.root: tk.Tk = tk.Tk() - self.parent:tk.Frame = tk.Frame(self.root) - #root.withdraw() + root:tk.Tk = tk.Tk() + self.parent:tk.Frame = tk.Frame(root) + root.withdraw() except Exception as e: logging.error(f"Failed to create Tk root: {e}") - if hasattr(self, 'root') and not hasattr(self, '_tk_scheduler'): - self._tk_scheduler = HarnessTkScheduler(self.root) - self._tk_scheduler.install() - if not hasattr(self, '_atexit_registered'): - atexit.register(self._tk_scheduler.uninstall) - self._atexit_registered = True - self._initialized = True def _capture_thread_exception(self, args: threading.ExceptHookArgs) -> None: @@ -137,14 +126,6 @@ def _capture_thread_exception(self, args: threading.ExceptHookArgs) -> None: def assert_no_unhandled_exceptions(self) -> None: """Fail the current test if any unhandled thread exceptions were captured.""" - self.pump_ui(timeout_s=0.4) - - scheduler_failures: list[str] = [] - if hasattr(self, '_tk_scheduler'): - scheduler_failures = self._tk_scheduler.consume_failures() - if scheduler_failures: - self.unhandled_exceptions.extend(scheduler_failures) - if not self.unhandled_exceptions: return @@ -155,32 +136,6 @@ def assert_no_unhandled_exceptions(self) -> None: f"{failures}" ) - def pump_ui(self, timeout_s: float = 0.2, poll_interval_s: float = 0.01) -> None: - """Pump deferred Tk callbacks and process pending UI events.""" - if not hasattr(self, 'root'): - return - - end_time = monotonic() + max(timeout_s, 0.0) - - while True: - callbacks_ran = 0 - if hasattr(self, '_tk_scheduler'): - callbacks_ran = self._tk_scheduler.drain_due_callbacks() - - try: - self.root.update_idletasks() - self.root.update() - except tk.TclError: - return - - pending = self._tk_scheduler.pending_count() if hasattr(self, '_tk_scheduler') else 0 - if pending == 0 and callbacks_ran == 0: - return - if monotonic() >= end_time: - return - - sleep(poll_interval_s) - def set_requests_mode(self, live_requests:bool) -> None: """ Set whether the harness should use live HTTPS requests or mocked responses. """ self.live_requests = live_requests @@ -345,15 +300,12 @@ def fire_event(self, event:dict, state:dict = {}) -> None: logging.error(f"Error in journal handler: {e}") raise - self.pump_ui() - def play_sequence(self, name:str, delay:float = 0.5, state:dict = {}) -> None: """ Fire a sequence of events """ for event in self.events.get(name, []): self.fire_event(event, state=state) state = {} # Clear state after the first event sleep(delay) - self.pump_ui() def _update_journal_files(self, event:dict, state:dict = {}) -> None: """ Simulate EDMC's journal file updates based on the event type. """ diff --git a/tests/pytest.ini b/tests/pytest.ini index 8e1643b..57e4957 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,3 +1,4 @@ [pytest] markers = - live_requests: run the harness with the live requests backend \ No newline at end of file + live_requests: run the harness with the live requests backend + slow: tests that take a long time so should not be run by an automation \ No newline at end of file diff --git a/tests/test_conformance.py b/tests/test_conformance.py index 91e2b45..249e559 100644 --- a/tests/test_conformance.py +++ b/tests/test_conformance.py @@ -166,6 +166,10 @@ def test_event_sequence(self, harness) -> None: assert journal.system == "Bleae Thua ED-D c12-5" assert journal.entry['event'] == "NavBeaconScan" + @pytest.mark.slow + def test_manual_only(self, harness) -> None: + """ A demo slow test that won't be run by the unit-testing.yml. """ + assert True if __name__ == '__main__': pytest.main([__file__, '-v', '--tb=short']) From 6feec39771e6774b8c8f4ea59761fb458ddfd938 Mon Sep 17 00:00:00 2001 From: dwomble Date: Thu, 23 Apr 2026 13:50:08 -0700 Subject: [PATCH 3/4] slight reorg Co-authored-by: Copilot --- .github/workflows/unit-testing.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml index 6337bfa..b4d0566 100644 --- a/.github/workflows/unit-testing.yml +++ b/.github/workflows/unit-testing.yml @@ -28,17 +28,15 @@ jobs: sudo apt-get update sudo apt-get install -y libjpeg-dev zlib1g-dev libpng-dev python -m pip install --upgrade pip - pip install pytest pytest-xvfb + pip install flake8 pytest pytest-xvfb if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi - name: Lint with flake8 run: | - pip install flake8 - # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # Enable if you wish + # Enable if you wish for more comprehensive linting, but it can be noisy and may require code changes to pass #flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with xvfb-pytest From 06d86685fbec29226de173aaadeed1321bec7514 Mon Sep 17 00:00:00 2001 From: dwomble Date: Thu, 23 Apr 2026 14:15:14 -0700 Subject: [PATCH 4/4] flake fixes --- .github/workflows/unit-testing.yml | 2 +- load.py | 6 +++--- requirements-dev.txt | 5 +++-- tests/test_conformance.py | 11 ++++++++++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml index b4d0566..1096025 100644 --- a/.github/workflows/unit-testing.yml +++ b/.github/workflows/unit-testing.yml @@ -34,7 +34,7 @@ jobs: - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 . --extend-exclude .venv,tests/edmc,utils/dateutil --count --select=E9,F63,F7,F82 --show-source --statistics # Enable if you wish for more comprehensive linting, but it can be noisy and may require code changes to pass #flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics diff --git a/load.py b/load.py index 05f6ced..70e8d6a 100644 --- a/load.py +++ b/load.py @@ -68,7 +68,7 @@ def prefs_changed(cmdr: str, is_beta: bool) -> None: def journal_entry(cmdr, is_beta, system, station, entry, state): """ Parse an incoming journal entry and store the data we need """ - global journal + journal.cmdr = cmdr journal.is_beta = is_beta journal.system = system @@ -78,12 +78,12 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): def dashboard_entry(cmdr:str, is_beta:bool, entry:dict) -> None: """ Handle dashboard state changes """ - global dashboard + dashboard.cmdr = cmdr dashboard.is_beta = is_beta dashboard.entry = entry def capi_fleetcarrier(data:CAPIData): """ Handle Fleet carrier data """ - global carrier + carrier.data = data diff --git a/requirements-dev.txt b/requirements-dev.txt index 8ba2e57..2d37bdb 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,8 +1,9 @@ semantic-version ~= 2.10.0 sentry-sdk ~= 2.50.0 l10n ~= 0.1.0 -pytest ~= 7.0.0 requests ~= 2.31.0 +psutil ~= 5.9.0 +pytest ~= 7.0.0 pytest-cov ~= 3.0.0 pytest-mock ~= 3.10.0 -psutil ~= 5.9.0 +flake8 \ No newline at end of file diff --git a/tests/test_conformance.py b/tests/test_conformance.py index 249e559..b35970e 100644 --- a/tests/test_conformance.py +++ b/tests/test_conformance.py @@ -49,7 +49,6 @@ def test_harness_initialization(self, harness:TestHarness) -> None: def test_plugin_registration(self, harness:TestHarness) -> None: """Test that the plugin registered correctly.""" - global plugin assert harness.plugin_dir != "" assert harness.parent is not None @@ -60,6 +59,7 @@ def test_plugin_registration(self, harness:TestHarness) -> None: def test_mock_config(self, harness:TestHarness) -> None: """Test the mock config.""" + harness.config.set('DummyPlugin_intval', 42) harness.config.set('DummyPlugin_strval', "Hello, World!") @@ -69,6 +69,7 @@ def test_mock_config(self, harness:TestHarness) -> None: def test_load_state(self, harness:TestHarness) -> None: """Test that state files are loaded correctly.""" + assert harness.monitor.state['Credits'] == 1000000 state_data = harness.load_state('state.json') assert state_data is not None @@ -81,6 +82,7 @@ def test_load_state(self, harness:TestHarness) -> None: class TestHTTPRequests: def test_mock_http_requests(self, harness:TestHarness) -> None: """Test that mock requests work.""" + queue_response('get', MockResponse(200, url='https://testy.com/file.txt', json_data={'result': 'success'}), url='https://testy.com/file.txt') @@ -100,6 +102,7 @@ def test_live_http_requests(self, harness:TestHarness) -> None: def test_mock_capi_event(self, harness) -> None: """ Test a capi event is processed and saved correctly. """ + # Load a minimalist sample CAPI json and verify it doesn't fail. capi_data:dict = harness.get_config_data('capi_data.json') assert capi_data is not None @@ -111,6 +114,7 @@ class TestJournalEvents: def test_null_event(self, harness) -> None: """ Just a music event to test the machinery of loading and playing events. """ + harness.load_events("journal_events.json") harness.play_sequence("null", 0.1) assert journal.cmdr == "Testy" @@ -118,6 +122,7 @@ def test_null_event(self, harness) -> None: def test_startup_events(self, harness) -> None: """ Test a sequence of journal events are processed and saved correctly. """ + harness.load_events("journal_events.json") harness.play_sequence("startup", 0.1) @@ -127,6 +132,7 @@ def test_startup_events(self, harness) -> None: def test_cargo_event_state(self, harness) -> None: """ Test cargo events. Verify the cargo count is updated in the state and the Cargo.json is saved. """ + amt:int = 1298 assert harness.monitor.state['Cargo']['steel'] == 0 harness.load_events("journal_events.json", count=amt, price=4179) @@ -136,6 +142,7 @@ def test_cargo_event_state(self, harness) -> None: def test_cargo_event_json(self, harness) -> None: """ Test cargo events. Verify the cargo count is updated in the state and the Cargo.json is saved. """ + amt:int = 1298 harness.load_events("journal_events.json", count=amt, price=4179) harness.play_sequence("cargo", 0.1) @@ -147,6 +154,7 @@ def test_cargo_event_json(self, harness) -> None: def incomplete_test_backpack_event(self, harness) -> None: """ Test backpack events. Verify the cargo count is updated in the state and the Backpack.json is saved. """ + seq:dict = harness.load_events("journal_events.json") harness.play_sequence("backpack", 0.1) @@ -158,6 +166,7 @@ def incomplete_test_backpack_event(self, harness) -> None: def test_event_sequence(self, harness) -> None: """ Test a sequence of journal events are processed and saved correctly. """ + harness.load_events("journal_events.json") harness.play_sequence("jump", 0.1)