From e1663e71f0146429f9073c3bf1d2694aa863ec7d Mon Sep 17 00:00:00 2001 From: Paulo Mateus Date: Mon, 16 Oct 2023 19:50:20 -0300 Subject: [PATCH 01/11] Refactor: SnapshotName --- .github/workflows/irc.yml | 1 + .github/workflows/pylint.yml | 3 + .github/workflows/unittest.yml | 58 +++++++++++++++++ .gitignore | 4 ++ README.rst | 30 +++++++++ mod/controller/__init__.py | 3 + mod/controller/handler/__init__.py | 3 + .../handler/json_request_handler.py | 41 ++++++++++++ .../handler/timeless_request_handler.py | 15 +++++ mod/controller/rest/__init__.py | 3 + mod/controller/rest/snapshot.py | 33 ++++++++++ mod/webserver.py | 65 +++---------------- test/controller/__init__.py | 3 + test/controller/snapshot_controllers_test.py | 64 ++++++++++++++++++ 14 files changed, 270 insertions(+), 56 deletions(-) create mode 100644 .github/workflows/unittest.yml create mode 100644 mod/controller/__init__.py create mode 100644 mod/controller/handler/__init__.py create mode 100644 mod/controller/handler/json_request_handler.py create mode 100644 mod/controller/handler/timeless_request_handler.py create mode 100644 mod/controller/rest/__init__.py create mode 100644 mod/controller/rest/snapshot.py create mode 100644 test/controller/__init__.py create mode 100644 test/controller/snapshot_controllers_test.py diff --git a/.github/workflows/irc.yml b/.github/workflows/irc.yml index 1bfc96877..eccd27d08 100644 --- a/.github/workflows/irc.yml +++ b/.github/workflows/irc.yml @@ -4,6 +4,7 @@ on: [push] jobs: notification: + if: github.repository == 'moddevices/mod-ui' runs-on: ubuntu-latest name: IRC notification steps: diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index ce3a1944a..31a635ba5 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -19,6 +19,9 @@ jobs: pip3 install pylint - name: Run pylint run: | + export MOD_DEV_HOST=1 + export MOD_DEV_ENVIRONMENT=0 + virtualenv modui-env source modui-env/bin/activate pylint -E mod/*.py diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml new file mode 100644 index 000000000..b5e2e624c --- /dev/null +++ b/.github/workflows/unittest.yml @@ -0,0 +1,58 @@ +name: unittest + +on: [push, pull_request] + +jobs: + unittest: + runs-on: ubuntu-22.04 + name: unittest + steps: + - uses: actions/checkout@v3 + - name: Install virtualenv + run: | + sudo apt install python3-virtualenv + - name: Setup utils + run: | + sudo apt install python3-dev build-essential libasound2-dev libjack-jackd2-dev liblilv-dev libjpeg-dev zlib1g-dev + make -C utils + - name: Setup unittest + run: | + virtualenv modui-env + source modui-env/bin/activate + pip3 install -r requirements.txt + # pytest + pip3 install pytest pytest-cov + sed -i -e 's/collections.MutableMapping/collections.abc.MutableMapping/' modui-env/lib/python3.10/site-packages/tornado/httputil.py + # mod + python3 setup.py develop + - name: Run unittest + run: | + virtualenv modui-env + source modui-env/bin/activate + pytest --cov=mod --cov-report=html --cov-report=term --cov-report=xml + - name: Archive coverage files + uses: actions/upload-artifact@v3 + with: + name: coverage + path: | + .coverage + htmlcov/** + cobertura.xml + - name: Code Coverage Report + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: coverage.xml + badge: true + fail_below_min: true + format: markdown + hide_branch_rate: false + hide_complexity: false + indicators: true + output: both + thresholds: '15 30' + - name: Add Coverage PR Comment + uses: marocchino/sticky-pull-request-comment@v2 + if: github.event_name == 'pull_request' + with: + recreate: true + path: code-coverage-results.md \ No newline at end of file diff --git a/.gitignore b/.gitignore index c9fea7225..953479fe4 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,7 @@ utils/test *node_modules/ .idea *.egg-info +# Coverage +.coverage +coverage.xml +htmlcov/ \ No newline at end of file diff --git a/README.rst b/README.rst index 8c9ac1da5..957f4a4a9 100644 --- a/README.rst +++ b/README.rst @@ -58,3 +58,33 @@ And now you are ready to start the webserver:: Setting the environment variables is needed when developing on a PC. Open your browser and point to http://localhost:8888/. + +Development +----------- + +The source code is expected to be ran on a Python 3.4 version. + +The source code is organized with the following structure + +* ``html``: Frontend source code +* ``mod``: Backend source code + * ``controller/``: Tornado related source code + * ``rest/``: REST classes + * ``websocket/``: web-socket classes + * ``dto/``: Utility model representation + * ``handler/``: Common tornado handlers + * ``file_receiver/``: Common tornado file receivers + * ``model/``: Model abstraction (snapshot, pedalboard, bank) + * ``service/``: High level layer for ``mod-host`` usage + * ``util/``: Common utility source code + * ``settings.py``: Application parameters + * ``host.py``: ``mod-host`` interface for communication with application + * ``webserver.py``: Web-server initialization +* ``modtools``: +* ``test``: Python unit tests +* ``utils``: C++ utility code + + +Some IDEs can improve the code completion if you install mod-ui as an local packaged on virtualenv:: + + $ python3 setup.py develop diff --git a/mod/controller/__init__.py b/mod/controller/__init__.py new file mode 100644 index 000000000..498793f03 --- /dev/null +++ b/mod/controller/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/mod/controller/handler/__init__.py b/mod/controller/handler/__init__.py new file mode 100644 index 000000000..498793f03 --- /dev/null +++ b/mod/controller/handler/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/mod/controller/handler/json_request_handler.py b/mod/controller/handler/json_request_handler.py new file mode 100644 index 000000000..13c2a954b --- /dev/null +++ b/mod/controller/handler/json_request_handler.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later +import json + +from tornado.util import unicode_type + +from mod.controller.handler.timeless_request_handler import TimelessRequestHandler + + +class JsonRequestHandler(TimelessRequestHandler): + def write(self, data): + # FIXME: something is sending strings out, need to investigate what later.. + # it's likely something using write(json.dumps(...)) + # we want to prevent that as it causes issues under Mac OS + + if isinstance(data, (bytes, unicode_type, dict)): + TimelessRequestHandler.write(self, data) + self.finish() + return + + elif data is True: + data = "true" + self.set_header("Content-Type", "application/json; charset=UTF-8") + + elif data is False: + data = "false" + self.set_header("Content-Type", "application/json; charset=UTF-8") + + # TESTING for data types, remove this later + #elif not isinstance(data, list): + #print("=== TESTING: Got new data type for RequestHandler.write():", type(data), "msg:", data) + #data = json.dumps(data) + #self.set_header('Content-type', 'application/json') + + else: + data = json.dumps(data) + self.set_header("Content-Type", "application/json; charset=UTF-8") + + TimelessRequestHandler.write(self, data) + self.finish() diff --git a/mod/controller/handler/timeless_request_handler.py b/mod/controller/handler/timeless_request_handler.py new file mode 100644 index 000000000..47b29da43 --- /dev/null +++ b/mod/controller/handler/timeless_request_handler.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later +from tornado import web + + +class TimelessRequestHandler(web.RequestHandler): + def compute_etag(self): + return None + + def set_default_headers(self): + self._headers.pop("Date") + + def should_return_304(self): + return False diff --git a/mod/controller/rest/__init__.py b/mod/controller/rest/__init__.py new file mode 100644 index 000000000..498793f03 --- /dev/null +++ b/mod/controller/rest/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/mod/controller/rest/snapshot.py b/mod/controller/rest/snapshot.py new file mode 100644 index 000000000..3092d0927 --- /dev/null +++ b/mod/controller/rest/snapshot.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later +from mod.controller.handler.json_request_handler import JsonRequestHandler +from mod.session import SESSION +from mod.settings import DEFAULT_SNAPSHOT_NAME + + +class SnapshotName(JsonRequestHandler): + + + # TODO: Replace GET /snapshot/name + # to GET /pedalboards//snapshots/ + def get(self): + """ + Remove the snapshot name of identifier ``id`` of the loaded pedalboard; + If is requested by an invalid ``id``, will be returned the default snapshot name. + + .. code-block:: json + + { + "ok": true, + "name": "Pedalboard name" + } + + :return: Snapshot name + """ + idx = int(self.get_argument('id')) + name = SESSION.host.snapshot_name(idx) or DEFAULT_SNAPSHOT_NAME + self.write({ + 'ok': bool(name), # FIXME: Always true + 'name': name + }) diff --git a/mod/webserver.py b/mod/webserver.py index a4de1f17f..4a6f82748 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -10,16 +10,19 @@ import subprocess import sys import time - from base64 import b64decode, b64encode from datetime import timedelta from random import randint -from tornado import gen, iostream, web, websocket +from uuid import uuid4 + +from tornado import gen, web, websocket from tornado.escape import squeeze, url_escape, xhtml_escape from tornado.ioloop import IOLoop from tornado.template import Loader -from tornado.util import unicode_type -from uuid import uuid4 + +from mod.controller.handler.json_request_handler import JsonRequestHandler +from mod.controller.handler.timeless_request_handler import TimelessRequestHandler +from mod.controller.rest.snapshot import SnapshotName try: from signal import signal, SIGUSR1, SIGUSR2 @@ -36,13 +39,12 @@ LV2_PLUGIN_DIR, LV2_PEDALBOARDS_DIR, IMAGE_VERSION, UPDATE_CC_FIRMWARE_FILE, UPDATE_MOD_OS_FILE, UPDATE_MOD_OS_HERLPER_FILE, USING_256_FRAMES_FILE, DEFAULT_ICON_TEMPLATE, DEFAULT_SETTINGS_TEMPLATE, DEFAULT_ICON_IMAGE, - DEFAULT_PEDALBOARD, DEFAULT_SNAPSHOT_NAME, DATA_DIR, KEYS_PATH, USER_FILES_DIR, + DEFAULT_PEDALBOARD, DATA_DIR, KEYS_PATH, USER_FILES_DIR, FAVORITES_JSON_FILE, PREFERENCES_JSON_FILE, USER_ID_JSON_FILE, DEV_HOST, UNTITLED_PEDALBOARD_NAME, MODEL_CPU, MODEL_TYPE, PEDALBOARDS_LABS_HTTP_ADDRESS) from mod import ( - TextFileFlusher, WINDOWS, - check_environment, jsoncall, safe_json_load, + TextFileFlusher, check_environment, jsoncall, safe_json_load, get_hardware_descriptor, get_unique_name, os_sync, symbolify, ) from mod.bank import list_banks, save_banks, remove_pedalboard_from_banks @@ -207,15 +209,6 @@ def reset_get_all_pedalboards_cache_with_refresh(ptype): reset_get_all_pedalboards_cache(ptype) IOLoop.instance().add_callback(_reset_get_all_pedalboards_cache_with_refresh_2) -class TimelessRequestHandler(web.RequestHandler): - def compute_etag(self): - return None - - def set_default_headers(self): - self._headers.pop("Date") - - def should_return_304(self): - return False class TimelessStaticFileHandler(web.StaticFileHandler): def compute_etag(self): @@ -235,37 +228,6 @@ def get_cache_time(self, path, modified, mime_type): def get_modified_time(self): return None -class JsonRequestHandler(TimelessRequestHandler): - def write(self, data): - # FIXME: something is sending strings out, need to investigate what later.. - # it's likely something using write(json.dumps(...)) - # we want to prevent that as it causes issues under Mac OS - - if isinstance(data, (bytes, unicode_type, dict)): - TimelessRequestHandler.write(self, data) - self.finish() - return - - elif data is True: - data = "true" - self.set_header("Content-Type", "application/json; charset=UTF-8") - - elif data is False: - data = "false" - self.set_header("Content-Type", "application/json; charset=UTF-8") - - # TESTING for data types, remove this later - #elif not isinstance(data, list): - #print("=== TESTING: Got new data type for RequestHandler.write():", type(data), "msg:", data) - #data = json.dumps(data) - #self.set_header('Content-type', 'application/json') - - else: - data = json.dumps(data) - self.set_header("Content-Type", "application/json; charset=UTF-8") - - TimelessRequestHandler.write(self, data) - self.finish() class CachedJsonRequestHandler(JsonRequestHandler): def set_default_headers(self): @@ -1644,15 +1606,6 @@ def get(self): snapshots = dict((i, snapshots[i]['name']) for i in range(len(snapshots)) if snapshots[i] is not None) self.write(snapshots) -class SnapshotName(JsonRequestHandler): - def get(self): - idx = int(self.get_argument('id')) - name = SESSION.host.snapshot_name(idx) or DEFAULT_SNAPSHOT_NAME - self.write({ - 'ok' : bool(name), - 'name': name - }) - class SnapshotLoad(JsonRequestHandler): @web.asynchronous @gen.engine diff --git a/test/controller/__init__.py b/test/controller/__init__.py new file mode 100644 index 000000000..498793f03 --- /dev/null +++ b/test/controller/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/test/controller/snapshot_controllers_test.py b/test/controller/snapshot_controllers_test.py new file mode 100644 index 000000000..f9b622394 --- /dev/null +++ b/test/controller/snapshot_controllers_test.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later +# This test uses coroutine style. +import json + +from tornado.testing import AsyncHTTPTestCase + +from mod.settings import DEFAULT_SNAPSHOT_NAME +from mod.webserver import application + + +class SnapshotNameTestCase(AsyncHTTPTestCase): + def get_app(self): + return application + + def test_name_missing_snapshot(self): + response = self.fetch("/snapshot/name?id=1000") + + self.assertEqual(response.code, 200) + self.assertDictEqual( + { + 'ok': True, # FIXME: Always true + 'name': DEFAULT_SNAPSHOT_NAME + }, + json.loads(response.body) + ) + + def test_name_negative_snapshot(self): + response = self.fetch("/snapshot/name?id=-1") + + self.assertEqual(response.code, 200) + self.assertDictEqual( + { + 'ok': True, # FIXME: Always true + 'name': DEFAULT_SNAPSHOT_NAME + }, + json.loads(response.body) + ) + + def test_name(self): + expected_name_0 = 'Title-0' + expected_name_1 = 'Title-1' + + self.fetch("/snapshot/saveas?title=" + expected_name_0) + self.fetch("/snapshot/saveas?title=" + expected_name_1) + + response_0 = self.fetch("/snapshot/name?id=0") + response_1 = self.fetch("/snapshot/name?id=1") + + self.assert_equal(expected_name_0, response_0.body) + self.assert_equal(expected_name_1, response_1.body) + + self.fetch("/snapshot/remove?id=1") + self.fetch("/snapshot/remove?id=0") + + def assert_equal(self, name, body): + self.assertDictEqual( + { + 'ok': True, # FIXME: Always true + 'name': name + }, + json.loads(body) + ) From 4728fce36e3ed6c7fb609dabe33141049683cb77 Mon Sep 17 00:00:00 2001 From: Paulo Mateus Date: Mon, 16 Oct 2023 21:20:13 -0300 Subject: [PATCH 02/11] Refactor: SnapshotList --- mod/controller/rest/snapshot.py | 23 +++++++- mod/webserver.py | 8 +-- test/controller/snapshot_list_test.py | 52 +++++++++++++++++++ ...trollers_test.py => snapshot_name_test.py} | 39 +++++++------- 4 files changed, 94 insertions(+), 28 deletions(-) create mode 100644 test/controller/snapshot_list_test.py rename test/controller/{snapshot_controllers_test.py => snapshot_name_test.py} (61%) diff --git a/mod/controller/rest/snapshot.py b/mod/controller/rest/snapshot.py index 3092d0927..e65d7c204 100644 --- a/mod/controller/rest/snapshot.py +++ b/mod/controller/rest/snapshot.py @@ -8,7 +8,6 @@ class SnapshotName(JsonRequestHandler): - # TODO: Replace GET /snapshot/name # to GET /pedalboards//snapshots/ def get(self): @@ -31,3 +30,25 @@ def get(self): 'ok': bool(name), # FIXME: Always true 'name': name }) + + +class SnapshotList(JsonRequestHandler): + + # TODO: Replace GET /snapshot/list + # to GET /pedalboards//snapshots/ + def get(self): + """ + Get snapshots name of the loaded pedalboard + + .. code-block:: json + + { + 0: "First snapshot", + 1: "Second snapshot" + } + + :return: names of the current pedalboard snapshots + """ + snapshots = SESSION.host.pedalboard_snapshots + snapshots = dict((i, snapshots[i]['name']) for i in range(len(snapshots)) if snapshots[i] is not None) + self.write(snapshots) diff --git a/mod/webserver.py b/mod/webserver.py index 4a6f82748..d09a75656 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -22,7 +22,7 @@ from mod.controller.handler.json_request_handler import JsonRequestHandler from mod.controller.handler.timeless_request_handler import TimelessRequestHandler -from mod.controller.rest.snapshot import SnapshotName +from mod.controller.rest.snapshot import SnapshotName, SnapshotList try: from signal import signal, SIGUSR1, SIGUSR2 @@ -1600,12 +1600,6 @@ def get(self): ok = SESSION.host.snapshot_remove(idx) self.write(ok) -class SnapshotList(JsonRequestHandler): - def get(self): - snapshots = SESSION.host.pedalboard_snapshots - snapshots = dict((i, snapshots[i]['name']) for i in range(len(snapshots)) if snapshots[i] is not None) - self.write(snapshots) - class SnapshotLoad(JsonRequestHandler): @web.asynchronous @gen.engine diff --git a/test/controller/snapshot_list_test.py b/test/controller/snapshot_list_test.py new file mode 100644 index 000000000..3174e20b8 --- /dev/null +++ b/test/controller/snapshot_list_test.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later +# This test uses coroutine style. +import json +from uuid import uuid4 + +from tornado.testing import AsyncHTTPTestCase + +from mod.settings import DEFAULT_SNAPSHOT_NAME +from mod.webserver import application + + +class SnapshotListTestCase(AsyncHTTPTestCase): + def get_app(self): + return application + + # Pedalboard starts empty, but after there is one, it isn't possible to exclude all of them + # def test_empty(self): + # response = self.fetch("/snapshot/list") + # + # self.assertEqual(response.code, 200) + # self.assert_equal([], response.body) + + def test_populated_name(self): + original_snapshots = json.loads(self.fetch("/snapshot/list").body) + + # Populate + expected_name_0 = str(uuid4()) + expected_name_1 = str(uuid4()) + + self.fetch("/snapshot/saveas?title=" + expected_name_0) + self.fetch("/snapshot/saveas?title=" + expected_name_1) + + response = self.fetch("/snapshot/list") + + # Assert list + new_snapshots = self._names(original_snapshots) + [expected_name_0, expected_name_1] + self.assert_equal(new_snapshots, response.body) + + # Clear created + for index in reversed(range(len(original_snapshots), len(new_snapshots)+1)): + self.fetch("/snapshot/remove?id=" + str(index)) + + def assert_equal(self, lista, body): + self.assertDictEqual( + {str(index): item for index, item in enumerate(lista)}, + json.loads(body) + ) + + def _names(self, snapshots): + return list(snapshots.values()) diff --git a/test/controller/snapshot_controllers_test.py b/test/controller/snapshot_name_test.py similarity index 61% rename from test/controller/snapshot_controllers_test.py rename to test/controller/snapshot_name_test.py index f9b622394..46c245fee 100644 --- a/test/controller/snapshot_controllers_test.py +++ b/test/controller/snapshot_name_test.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later # This test uses coroutine style. import json +from uuid import uuid4 from tornado.testing import AsyncHTTPTestCase @@ -18,41 +19,31 @@ def test_name_missing_snapshot(self): response = self.fetch("/snapshot/name?id=1000") self.assertEqual(response.code, 200) - self.assertDictEqual( - { - 'ok': True, # FIXME: Always true - 'name': DEFAULT_SNAPSHOT_NAME - }, - json.loads(response.body) - ) + self.assert_equal(DEFAULT_SNAPSHOT_NAME, response.body) def test_name_negative_snapshot(self): response = self.fetch("/snapshot/name?id=-1") self.assertEqual(response.code, 200) - self.assertDictEqual( - { - 'ok': True, # FIXME: Always true - 'name': DEFAULT_SNAPSHOT_NAME - }, - json.loads(response.body) - ) + self.assert_equal(DEFAULT_SNAPSHOT_NAME, response.body) def test_name(self): - expected_name_0 = 'Title-0' - expected_name_1 = 'Title-1' + expected_name_0 = str(uuid4()) + expected_name_1 = str(uuid4()) self.fetch("/snapshot/saveas?title=" + expected_name_0) self.fetch("/snapshot/saveas?title=" + expected_name_1) - response_0 = self.fetch("/snapshot/name?id=0") - response_1 = self.fetch("/snapshot/name?id=1") + snapshots = self.saved_items() + + response_0 = self.fetch("/snapshot/name?id=" + snapshots[expected_name_0]) + response_1 = self.fetch("/snapshot/name?id=" + snapshots[expected_name_1]) self.assert_equal(expected_name_0, response_0.body) self.assert_equal(expected_name_1, response_1.body) - self.fetch("/snapshot/remove?id=1") - self.fetch("/snapshot/remove?id=0") + for index in reversed(range(len(snapshots), 0)): + self.fetch("/snapshot/remove?id=" + str(index)) def assert_equal(self, name, body): self.assertDictEqual( @@ -62,3 +53,11 @@ def assert_equal(self, name, body): }, json.loads(body) ) + + def saved_items(self): + items = self.fetch("/snapshot/list") + + return { + value: key + for key, value in json.loads(items.body).items() + } From d51d64a2f4d04217e01288ba033f10ce7eb29844 Mon Sep 17 00:00:00 2001 From: Paulo Mateus Date: Mon, 16 Oct 2023 21:48:41 -0300 Subject: [PATCH 03/11] Refactor: SnapshotSave --- mod/controller/rest/snapshot.py | 14 ++++++ mod/webserver.py | 7 +-- test/controller/snapshot_list_test.py | 1 - test/controller/snapshot_save_test.py | 68 +++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 test/controller/snapshot_save_test.py diff --git a/mod/controller/rest/snapshot.py b/mod/controller/rest/snapshot.py index e65d7c204..f322de62e 100644 --- a/mod/controller/rest/snapshot.py +++ b/mod/controller/rest/snapshot.py @@ -52,3 +52,17 @@ def get(self): snapshots = SESSION.host.pedalboard_snapshots snapshots = dict((i, snapshots[i]['name']) for i in range(len(snapshots)) if snapshots[i] is not None) self.write(snapshots) + + +class SnapshotSave(JsonRequestHandler): + + # TODO: Replace POST /snapshot/save + # to POST /pedalboards/current/snapshots/current + def post(self): + """ + Update the current snapshot status + + :return: `true` if it was successfully updated + """ + ok = SESSION.host.snapshot_save() + self.write(ok) diff --git a/mod/webserver.py b/mod/webserver.py index d09a75656..ac674f50b 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -22,7 +22,7 @@ from mod.controller.handler.json_request_handler import JsonRequestHandler from mod.controller.handler.timeless_request_handler import TimelessRequestHandler -from mod.controller.rest.snapshot import SnapshotName, SnapshotList +from mod.controller.rest.snapshot import SnapshotName, SnapshotList, SnapshotSave try: from signal import signal, SIGUSR1, SIGUSR2 @@ -1555,11 +1555,6 @@ def post(self, mode): ok = yield gen.Task(SESSION.web_set_sync_mode, transport_sync) self.write(ok) -class SnapshotSave(JsonRequestHandler): - def post(self): - ok = SESSION.host.snapshot_save() - self.write(ok) - class SnapshotSaveAs(JsonRequestHandler): @web.asynchronous @gen.engine diff --git a/test/controller/snapshot_list_test.py b/test/controller/snapshot_list_test.py index 3174e20b8..06d36b1a2 100644 --- a/test/controller/snapshot_list_test.py +++ b/test/controller/snapshot_list_test.py @@ -7,7 +7,6 @@ from tornado.testing import AsyncHTTPTestCase -from mod.settings import DEFAULT_SNAPSHOT_NAME from mod.webserver import application diff --git a/test/controller/snapshot_save_test.py b/test/controller/snapshot_save_test.py new file mode 100644 index 000000000..82eb4fd3b --- /dev/null +++ b/test/controller/snapshot_save_test.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later +# This test uses coroutine style. +import json +from uuid import uuid4 + +from tornado.httpclient import HTTPRequest +from tornado.testing import AsyncHTTPTestCase + +from mod.webserver import application + + +class SnapshotSaveTestCase(AsyncHTTPTestCase): + def get_app(self): + return application + + def test_save(self): + # Create a snapshot + name = str(uuid4()) + snapshot_index = self._save_as(name) + + # Load and save snapshot created + self.fetch("/snapshot/load?id=" + str(snapshot_index)) + response = self.post("/snapshot/save") + + # Assert is saved + self.assertEqual(response.code, 200) + self.assertTrue(json.loads(response.body)) + + # Clear + self.fetch("/snapshot/remove?id=" + str(snapshot_index)) + + def test_save_deleted_snapshot(self): + # Create two snapshots + # because it is necessary to exists at least one + snapshot_1 = str(uuid4()) + snapshot_2 = str(uuid4()) + + snapshot_1_index = self._save_as(snapshot_1) + snapshot_2_index = self._save_as(snapshot_2) + + # Save snapshot created + self.fetch("/snapshot/load?id=" + str(snapshot_2_index)) + response = self.post("/snapshot/save") + + # Assert is saved + self.assertEqual(response.code, 200) + self.assertTrue(json.loads(response.body)) + + # Delete created snapshot + self.fetch("/snapshot/remove?id=" + str(snapshot_2_index)) + + # Try to save deleted snapshot + response = self.post("/snapshot/save") + self.assertEqual(response.code, 200) + self.assertFalse(json.loads(response.body)) + + # Clear + self.fetch("/snapshot/remove?id=" + str(snapshot_1_index)) + + def post(self, url): + self.http_client.fetch(HTTPRequest(self.get_url(url), "POST", allow_nonstandard_methods=True), self.stop) + return self.wait() + + def _save_as(self, name): + response = json.loads(self.fetch("/snapshot/saveas?title=" + name).body) + return response['id'] From 14f76034d8604ebfa74a7df99388c05327109144 Mon Sep 17 00:00:00 2001 From: Paulo Mateus Date: Mon, 16 Oct 2023 22:12:58 -0300 Subject: [PATCH 04/11] Refactor: SnapshotSaveAs --- .github/workflows/unittest.yml | 2 +- mod/controller/rest/snapshot.py | 33 ++++++++ mod/webserver.py | 17 +--- test/controller/snapshot_save_as_test.py | 99 ++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 17 deletions(-) create mode 100644 test/controller/snapshot_save_as_test.py diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index b5e2e624c..17a33f08c 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -46,7 +46,7 @@ jobs: fail_below_min: true format: markdown hide_branch_rate: false - hide_complexity: false + hide_complexity: true indicators: true output: both thresholds: '15 30' diff --git a/mod/controller/rest/snapshot.py b/mod/controller/rest/snapshot.py index f322de62e..ea801676e 100644 --- a/mod/controller/rest/snapshot.py +++ b/mod/controller/rest/snapshot.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # SPDX-FileCopyrightText: 2012-2023 MOD Audio UG # SPDX-License-Identifier: AGPL-3.0-or-later +from tornado import gen, web + from mod.controller.handler.json_request_handler import JsonRequestHandler from mod.session import SESSION from mod.settings import DEFAULT_SNAPSHOT_NAME @@ -66,3 +68,34 @@ def post(self): """ ok = SESSION.host.snapshot_save() self.write(ok) + +class SnapshotSaveAs(JsonRequestHandler): + + # TODO: Replace GET /snapshot/saveas + # to POST /pedalboards/current/snapshots/saveas + @web.asynchronous + @gen.engine + def get(self): + """ + Create a new snapshot with the suggested ``title`` based on the current pedalboard status; + .. code-block:: json + + { + "ok": true, + "id": 1, + "title": "Snapshot name" + } + + :return: `true` if it was successfully deleted + """ + title = self.get_argument('title') + idx = SESSION.host.snapshot_saveas(title) + title = SESSION.host.snapshot_name(idx) + + yield gen.Task(SESSION.host.hmi_report_ss_name_if_current, idx) + + self.write({ + 'ok': idx is not None, # FIXME: Always true + 'id': idx, + 'title': title, + }) diff --git a/mod/webserver.py b/mod/webserver.py index ac674f50b..050c56d1d 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -22,7 +22,7 @@ from mod.controller.handler.json_request_handler import JsonRequestHandler from mod.controller.handler.timeless_request_handler import TimelessRequestHandler -from mod.controller.rest.snapshot import SnapshotName, SnapshotList, SnapshotSave +from mod.controller.rest.snapshot import SnapshotName, SnapshotList, SnapshotSave, SnapshotSaveAs try: from signal import signal, SIGUSR1, SIGUSR2 @@ -1555,21 +1555,6 @@ def post(self, mode): ok = yield gen.Task(SESSION.web_set_sync_mode, transport_sync) self.write(ok) -class SnapshotSaveAs(JsonRequestHandler): - @web.asynchronous - @gen.engine - def get(self): - title = self.get_argument('title') - idx = SESSION.host.snapshot_saveas(title) - title = SESSION.host.snapshot_name(idx) - - yield gen.Task(SESSION.host.hmi_report_ss_name_if_current, idx) - - self.write({ - 'ok': idx is not None, - 'id': idx, - 'title': title, - }) class SnapshotRename(JsonRequestHandler): @web.asynchronous diff --git a/test/controller/snapshot_save_as_test.py b/test/controller/snapshot_save_as_test.py new file mode 100644 index 000000000..cd37eaae5 --- /dev/null +++ b/test/controller/snapshot_save_as_test.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later +# This test uses coroutine style. +import json +from uuid import uuid4 + +from tornado.httpclient import HTTPRequest +from tornado.testing import AsyncHTTPTestCase + +from mod.webserver import application + + +class SnapshotSaveAsTestCase(AsyncHTTPTestCase): + def get_app(self): + return application + + def test_save_as(self): + # Create a snapshot + name = str(uuid4()) + snapshot = self._save_as(name) + + # Assert is saved + self.assertDictEqual( + { + "ok": True, + "id": snapshot['id'], + "title": name + }, + snapshot + ) + + # Clear + self.fetch("/snapshot/remove?id=" + str(snapshot['id'])) + + def test_save_as_deleted_snapshot(self): + """ + It's possible, because the snapshot is created based on pedalboard's + current state instead of a last snapshot loaded + """ + # Create two snapshots + # because it is necessary to exists at least one + snapshot_1_name = str(uuid4()) + snapshot_2_name = str(uuid4()) + + snapshot_1 = self._save_as(snapshot_1_name) + snapshot_2 = self._save_as(snapshot_2_name) + + # Save snapshot created + self.fetch("/snapshot/load?id=" + str(snapshot_2['id'])) + response = self.post("/snapshot/save") + + # Assert is saved + self.assertTrue(json.loads(response.body)) + + # Delete created snapshot + self.fetch("/snapshot/remove?id=" + str(snapshot_2['id'])) + + # Save as deleted snapshot + snapshot_3_name = str(uuid4()) + snapshot_3 = self._save_as(snapshot_3_name) + self.assertEqual(response.code, 200) + self.assertDictEqual( + { + "ok": True, + "id": snapshot_3['id'], + "title": snapshot_3_name + }, + snapshot_3 + ) + + # Clear + self.fetch("/snapshot/remove?id=" + str(snapshot_3['id'])) + self.fetch("/snapshot/remove?id=" + str(snapshot_1['id'])) + + def test_save_duplicated_name(self): + snapshot_1_name = snapshot_2_name = str(uuid4()) + + self.assertEqual(snapshot_1_name, snapshot_2_name) + + snapshot_1 = self._save_as(snapshot_1_name) + snapshot_2 = self._save_as(snapshot_2_name) + + self.assertNotEqual(snapshot_1['title'], snapshot_2['title']) + self.assertTrue(snapshot_1['title'] < snapshot_2['title']) + + # Clear + self.fetch("/snapshot/remove?id=" + str(snapshot_2['id'])) + self.fetch("/snapshot/remove?id=" + str(snapshot_1['id'])) + + def post(self, url): + self.http_client.fetch(HTTPRequest(self.get_url(url), "POST", allow_nonstandard_methods=True), self.stop) + return self.wait() + + def _save_as(self, name): + response = self.fetch("/snapshot/saveas?title=" + name) + self.assertEqual(response.code, 200) + + return json.loads(response.body) From 7493164ebb7253e4fc66b5f9cf8cd7d68f413e32 Mon Sep 17 00:00:00 2001 From: Paulo Mateus Date: Tue, 17 Oct 2023 19:05:28 -0300 Subject: [PATCH 05/11] Refactor: SnapshotRemove --- mod/controller/rest/snapshot.py | 17 ++++++ mod/webserver.py | 8 +-- test/controller/snapshot_remove.py | 96 ++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 test/controller/snapshot_remove.py diff --git a/mod/controller/rest/snapshot.py b/mod/controller/rest/snapshot.py index ea801676e..00a5a37df 100644 --- a/mod/controller/rest/snapshot.py +++ b/mod/controller/rest/snapshot.py @@ -69,6 +69,7 @@ def post(self): ok = SESSION.host.snapshot_save() self.write(ok) + class SnapshotSaveAs(JsonRequestHandler): # TODO: Replace GET /snapshot/saveas @@ -99,3 +100,19 @@ def get(self): 'id': idx, 'title': title, }) + + +class SnapshotRemove(JsonRequestHandler): + + # TODO: Replace GET /snapshot/remove?id= + # to DELETE /pedalboards//snapshots/ + def get(self): + """ + Remove the snapshot of identifier ``id`` of the loaded pedalboard + + :return: `true` if it was successfully deleted + """ + idx = int(self.get_argument('id')) + ok = SESSION.host.snapshot_remove(idx) + + self.write(ok) diff --git a/mod/webserver.py b/mod/webserver.py index 050c56d1d..9a668030f 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -22,7 +22,7 @@ from mod.controller.handler.json_request_handler import JsonRequestHandler from mod.controller.handler.timeless_request_handler import TimelessRequestHandler -from mod.controller.rest.snapshot import SnapshotName, SnapshotList, SnapshotSave, SnapshotSaveAs +from mod.controller.rest.snapshot import SnapshotName, SnapshotList, SnapshotSave, SnapshotSaveAs, SnapshotRemove try: from signal import signal, SIGUSR1, SIGUSR2 @@ -1574,12 +1574,6 @@ def get(self): 'title': title, }) -class SnapshotRemove(JsonRequestHandler): - def get(self): - idx = int(self.get_argument('id')) - ok = SESSION.host.snapshot_remove(idx) - self.write(ok) - class SnapshotLoad(JsonRequestHandler): @web.asynchronous @gen.engine diff --git a/test/controller/snapshot_remove.py b/test/controller/snapshot_remove.py new file mode 100644 index 000000000..7b257b276 --- /dev/null +++ b/test/controller/snapshot_remove.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later +# This test uses coroutine style. +import json +from uuid import uuid4 + +from tornado.httpclient import HTTPRequest +from tornado.testing import AsyncHTTPTestCase + +from mod.webserver import application + + +class SnapshotRemoveTestCase(AsyncHTTPTestCase): + + def get_app(self): + return application + + def test_remove_non_current_snapshot(self): + # Create a default snapshot + name = str(uuid4()) + snapshot_1_index = self._save_as(name) + + # Create a snapshot to be deleted + name = str(uuid4()) + snapshot_2_index = self._save_as(name) + + # Delete snapshot + response = self.fetch("/snapshot/remove?id=" + str(snapshot_2_index)) + self.assertEqual(response.code, 200) + self.assertTrue(json.loads(response.body)) + + # Clear + self.fetch("/snapshot/remove?id=" + str(snapshot_1_index)) + + def test_remove_invalid_snapshot(self): + response = self.fetch("/snapshot/remove?id=" + str(-1)) + self.assertEqual(response.code, 200) + self.assertFalse(json.loads(response.body)) + + response = self.fetch("/snapshot/remove?id=" + str(1000)) + self.assertEqual(response.code, 200) + self.assertFalse(json.loads(response.body)) + + def test_remove_current_snapshot(self): + """ + It's possible to delete current snapshot + """ + # Create a default snapshot + name = str(uuid4()) + snapshot_1_index = self._save_as(name) + + # Create a snapshot to be deleted + name = str(uuid4()) + snapshot_2_index = self._save_as(name) + + # Load save snapshot to be deleted + response = self.fetch("/snapshot/load?id=" + str(snapshot_2_index)) + + # Assert is loaded + self.assertEqual(response.code, 200) + self.assertTrue(json.loads(response.body)) + + # Delete loaded snapshot + response = self.fetch("/snapshot/remove?id=" + str(snapshot_2_index)) + self.assertEqual(response.code, 200) + self.assertTrue(json.loads(response.body)) + + # Clear + self.fetch("/snapshot/remove?id=" + str(snapshot_1_index)) + + def test_remove_all_snapshots(self): + for i in range(5): + self._save_as("Snapshot-" + str(i)) + + snapshots = json.loads(self.fetch("/snapshot/list").body) + + # Remove all snapshots except the last + for i in range(len(snapshots) - 1): + response = self.fetch("/snapshot/remove?id=0") + self.assertEqual(response.code, 200) + self.assertTrue(json.loads(response.body)) + + # Try to remove the last snapshot without success, as expected + for i in range(len(snapshots) - 1): + response = self.fetch("/snapshot/remove?id=0") + self.assertEqual(response.code, 200) + self.assertFalse(json.loads(response.body)) + + def post(self, url): + self.http_client.fetch(HTTPRequest(self.get_url(url), "POST", allow_nonstandard_methods=True), self.stop) + return self.wait() + + def _save_as(self, name): + response = json.loads(self.fetch("/snapshot/saveas?title=" + name).body) + return response['id'] From 39a83c423f2ea3c5f18437fbd22229dab3d727b7 Mon Sep 17 00:00:00 2001 From: Paulo Mateus Date: Tue, 17 Oct 2023 19:35:34 -0300 Subject: [PATCH 06/11] Refactor: SnapshotRename --- mod/controller/rest/snapshot.py | 40 +++++- mod/webserver.py | 21 +--- ...shot_remove.py => snapshot_remove_test.py} | 0 test/controller/snapshot_rename_test.py | 117 ++++++++++++++++++ 4 files changed, 156 insertions(+), 22 deletions(-) rename test/controller/{snapshot_remove.py => snapshot_remove_test.py} (100%) create mode 100644 test/controller/snapshot_rename_test.py diff --git a/mod/controller/rest/snapshot.py b/mod/controller/rest/snapshot.py index 00a5a37df..bbbbbadc2 100644 --- a/mod/controller/rest/snapshot.py +++ b/mod/controller/rest/snapshot.py @@ -89,8 +89,8 @@ def get(self): :return: `true` if it was successfully deleted """ - title = self.get_argument('title') - idx = SESSION.host.snapshot_saveas(title) + title = self.get_argument('title') # TODO rename to name (standardization) + idx = SESSION.host.snapshot_saveas(title) title = SESSION.host.snapshot_name(idx) yield gen.Task(SESSION.host.hmi_report_ss_name_if_current, idx) @@ -98,7 +98,7 @@ def get(self): self.write({ 'ok': idx is not None, # FIXME: Always true 'id': idx, - 'title': title, + 'title': title, # TODO rename to name (standardization) }) @@ -116,3 +116,37 @@ def get(self): ok = SESSION.host.snapshot_remove(idx) self.write(ok) + + +class SnapshotRename(JsonRequestHandler): + + # TODO: Replace GET /snapshot/rename?id=&?name= + # to PATCH /pedalboards//snapshots/ + @web.asynchronous + @gen.engine + def get(self): + """ + Rename the snapshot of ``ìd`` identifier with the suggested ``title``. + + .. code-block:: json + + { + "ok": true, + "title": "Snapshot name" + } + + :return: new snapshot name + """ + idx = int(self.get_argument('id')) + title = self.get_argument('title') # TODO rename to name (standardization) + ok = SESSION.host.snapshot_rename(idx, title) + + if ok: + title = SESSION.host.snapshot_name(idx) + + yield gen.Task(SESSION.host.hmi_report_ss_name_if_current, idx) + + self.write({ + 'ok': ok, + 'title': title, # TODO rename to name (standardization) + }) diff --git a/mod/webserver.py b/mod/webserver.py index 9a668030f..7c071987d 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -22,7 +22,8 @@ from mod.controller.handler.json_request_handler import JsonRequestHandler from mod.controller.handler.timeless_request_handler import TimelessRequestHandler -from mod.controller.rest.snapshot import SnapshotName, SnapshotList, SnapshotSave, SnapshotSaveAs, SnapshotRemove +from mod.controller.rest.snapshot import SnapshotName, SnapshotList, SnapshotSave, SnapshotSaveAs, SnapshotRemove, \ + SnapshotRename try: from signal import signal, SIGUSR1, SIGUSR2 @@ -1556,24 +1557,6 @@ def post(self, mode): self.write(ok) -class SnapshotRename(JsonRequestHandler): - @web.asynchronous - @gen.engine - def get(self): - idx = int(self.get_argument('id')) - title = self.get_argument('title') - ok = SESSION.host.snapshot_rename(idx, title) - - if ok: - title = SESSION.host.snapshot_name(idx) - - yield gen.Task(SESSION.host.hmi_report_ss_name_if_current, idx) - - self.write({ - 'ok': ok, - 'title': title, - }) - class SnapshotLoad(JsonRequestHandler): @web.asynchronous @gen.engine diff --git a/test/controller/snapshot_remove.py b/test/controller/snapshot_remove_test.py similarity index 100% rename from test/controller/snapshot_remove.py rename to test/controller/snapshot_remove_test.py diff --git a/test/controller/snapshot_rename_test.py b/test/controller/snapshot_rename_test.py new file mode 100644 index 000000000..9c8703de8 --- /dev/null +++ b/test/controller/snapshot_rename_test.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later +# This test uses coroutine style. +import json +from uuid import uuid4 + +from tornado.testing import AsyncHTTPTestCase + +from mod.webserver import application + + +class SnapshotRenameTestCase(AsyncHTTPTestCase): + def get_app(self): + return application + + def test_rename_unique_name(self): + original_name = str(uuid4()) + new_name = str(uuid4()) + + other = str(uuid4()) + + self.assertNotEqual(original_name, new_name) + + snapshot_1_index = self._save_as(original_name) + snapshot_2_index = self._save_as(other) + + # Assert rename works + snapshot = self._rename(snapshot_1_index, new_name) + self.assertDictEqual( + { + 'ok': True, + 'title': new_name + }, + snapshot + ) + + # Assert rename doesn't change snapshot's order + snapshot = self.fetch("/snapshot/name?id=" + str(snapshot_1_index)) + self.assertEqual(new_name, json.loads(snapshot.body)['name']) + + self.fetch("/snapshot/remove?id=" + str(snapshot_2_index)) + self.fetch("/snapshot/remove?id=" + str(snapshot_1_index)) + + def test_rename_to_the_same_name(self): + name = str(uuid4()) + + self.assertEqual(name, name) + + snapshot_index = self._save_as(name) + + # Assert rename + snapshot = self._rename(snapshot_index, name) + self.assertDictEqual( + { + 'ok': True, + 'title': name + }, + snapshot + ) + + self.fetch("/snapshot/remove?id=" + str(snapshot_index)) + + def test_rename_invalid_snapshot(self): + name = str(uuid4()) + + snapshot = self._rename(-1, name) + self.assertDictEqual( + {'ok': False, 'title': name}, + snapshot + ) + + snapshot = self._rename(1000, name) + self.assertDictEqual( + {'ok': False, 'title': name}, + snapshot + ) + + def test_rename_duplicated_name(self): + snapshot_sample_1 = str(uuid4()) + snapshot_sample_2 = str(uuid4()) + snapshot_sample_3 = str(uuid4()) + + snapshot_1_index = self._save_as(snapshot_sample_1) + snapshot_2_index = self._save_as(snapshot_sample_2) + snapshot_3_index = self._save_as(snapshot_sample_3) + + snapshot = self._rename(snapshot_1_index, snapshot_sample_3) + self.assertDictEqual( + { + 'ok': True, + 'title': snapshot_sample_3 + " (2)" + }, + snapshot + ) + + snapshot = self._rename(snapshot_2_index, snapshot_sample_3) + self.assertDictEqual( + { + 'ok': True, + 'title': snapshot_sample_3 + " (3)" + }, + snapshot + ) + + self.fetch("/snapshot/remove?id=" + str(snapshot_3_index)) + self.fetch("/snapshot/remove?id=" + str(snapshot_2_index)) + self.fetch("/snapshot/remove?id=" + str(snapshot_1_index)) + + def _rename(self, index, name): + response = self.fetch("/snapshot/rename?id=" + str(index) + "&title=" + name) + self.assertEqual(response.code, 200) + return json.loads(response.body) + + def _save_as(self, name): + response = json.loads(self.fetch("/snapshot/saveas?title=" + name).body) + return response['id'] From 2aba5e40ad36cc947d51c4263c825fa4e74db7bf Mon Sep 17 00:00:00 2001 From: Paulo Mateus Date: Tue, 17 Oct 2023 19:46:49 -0300 Subject: [PATCH 07/11] Refactor: SnapshotLoad --- mod/controller/rest/snapshot.py | 20 +++++++++++- mod/webserver.py | 10 +----- test/controller/snapshot_load_test.py | 47 +++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 test/controller/snapshot_load_test.py diff --git a/mod/controller/rest/snapshot.py b/mod/controller/rest/snapshot.py index bbbbbadc2..f2ec4dca3 100644 --- a/mod/controller/rest/snapshot.py +++ b/mod/controller/rest/snapshot.py @@ -120,7 +120,7 @@ def get(self): class SnapshotRename(JsonRequestHandler): - # TODO: Replace GET /snapshot/rename?id=&?name= + # TODO: Replace GET /snapshot/rename?id=&?title= # to PATCH /pedalboards//snapshots/ @web.asynchronous @gen.engine @@ -150,3 +150,21 @@ def get(self): 'ok': ok, 'title': title, # TODO rename to name (standardization) }) + + +class SnapshotLoad(JsonRequestHandler): + + # TODO: Replace GET /snapshot/load?id= + # to POST /pedalboards/current/snapshots//load + @web.asynchronous + @gen.engine + def get(self): + """ + Loads the snapshot of ``ìd`` identifier. + + :return: Was snapshot loaded? + """ + idx = int(self.get_argument('id')) + abort_catcher = SESSION.host.abort_previous_loading_progress("web SnapshotLoad") + ok = yield gen.Task(SESSION.host.snapshot_load_gen_helper, idx, False, abort_catcher) + self.write(ok) diff --git a/mod/webserver.py b/mod/webserver.py index 7c071987d..b9d448e04 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -23,7 +23,7 @@ from mod.controller.handler.json_request_handler import JsonRequestHandler from mod.controller.handler.timeless_request_handler import TimelessRequestHandler from mod.controller.rest.snapshot import SnapshotName, SnapshotList, SnapshotSave, SnapshotSaveAs, SnapshotRemove, \ - SnapshotRename + SnapshotRename, SnapshotLoad try: from signal import signal, SIGUSR1, SIGUSR2 @@ -1557,14 +1557,6 @@ def post(self, mode): self.write(ok) -class SnapshotLoad(JsonRequestHandler): - @web.asynchronous - @gen.engine - def get(self): - idx = int(self.get_argument('id')) - abort_catcher = SESSION.host.abort_previous_loading_progress("web SnapshotLoad") - ok = yield gen.Task(SESSION.host.snapshot_load_gen_helper, idx, False, abort_catcher) - self.write(ok) class DashboardClean(JsonRequestHandler): @web.asynchronous diff --git a/test/controller/snapshot_load_test.py b/test/controller/snapshot_load_test.py new file mode 100644 index 000000000..d704aacfc --- /dev/null +++ b/test/controller/snapshot_load_test.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later +# This test uses coroutine style. +import json +from uuid import uuid4 + +from tornado.httpclient import HTTPRequest +from tornado.testing import AsyncHTTPTestCase + +from mod.webserver import application + + +class SnapshotLoadTestCase(AsyncHTTPTestCase): + + def get_app(self): + return application + + def test_load_invalid_index(self): + response = self.fetch("/snapshot/load?id=" + str(-1)) + self.assertEqual(response.code, 200) + self.assertFalse(json.loads(response.body)) + + response = self.fetch("/snapshot/load?id=" + str(1000)) + self.assertEqual(response.code, 200) + self.assertFalse(json.loads(response.body)) + + # TODO Test load valid snapshot + # but it is probably better to test host.py directly + + def test_load_invalid_index(self): + # Create a snapshot + name = str(uuid4()) + snapshot_index = self._save_as(name) + + response = self.fetch("/snapshot/load?id=" + str(snapshot_index)) + + # Assert is loaded + self.assertEqual(response.code, 200) + self.assertTrue(json.loads(response.body)) + + # Clean + self.fetch("/snapshot/remove?id=" + str(snapshot_index)) + + def _save_as(self, name): + response = json.loads(self.fetch("/snapshot/saveas?title=" + name).body) + return response['id'] From 845496c6cf4828071c5e48443b9d21145aeea27d Mon Sep 17 00:00:00 2001 From: Paulo Mateus Date: Tue, 17 Oct 2023 20:36:18 -0300 Subject: [PATCH 08/11] Test: host.snapshot_name, host.snapshot_rename --- .gitignore | 2 +- test/host/__init__.py | 3 + test/host/host_snapshot_test.py | 154 ++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 test/host/__init__.py create mode 100644 test/host/host_snapshot_test.py diff --git a/.gitignore b/.gitignore index 953479fe4..3e2fca109 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ package-lock.json *.tar.xz mod-env/ .env/ -host/ +/host/ *.crf *.axf *.hex diff --git a/test/host/__init__.py b/test/host/__init__.py new file mode 100644 index 000000000..498793f03 --- /dev/null +++ b/test/host/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/test/host/host_snapshot_test.py b/test/host/host_snapshot_test.py new file mode 100644 index 000000000..622e84e15 --- /dev/null +++ b/test/host/host_snapshot_test.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later + +import unittest +from uuid import uuid4 + +from mod.development import FakeHost, FakeHMI +from mod.protocol import Protocol +from mod.session import UserPreferences, SESSION + + +def create_host(): + # return SESSION.host + + # Avoid to except "Command is already registered" + Protocol.COMMANDS_USED = [] + + callback_hmi = lambda: None + callback_host = lambda: None + + hmi = FakeHMI(callback_hmi) + return FakeHost(hmi, UserPreferences(), callback_host) + + +class HostSnapshotTestCase(unittest.TestCase): + + def test_empty_state(self): + host = create_host() + self.assertListEqual([], host.pedalboard_snapshots) + self.assertEqual(-1, host.current_pedalboard_snapshot_id) + + self.assertListEqual([None, None, None], host.hmi_snapshots) + + def test_initial_snapshot_name(self): + host = create_host() + + self.assertEqual(None, host.snapshot_name()) + + def test_invalid_index_snapshot_name(self): + host = create_host() + + self.assertEqual(None, host.snapshot_name(-1)) + self.assertEqual(None, host.snapshot_name(1000)) + + def test_snapshot_name(self): + host = create_host() + + snapshot_0 = str(uuid4()) + snapshot_1 = str(uuid4()) + + host.snapshot_saveas(snapshot_0) + host.snapshot_saveas(snapshot_1) + + self.assertEqual(snapshot_0, host.snapshot_name(0)) + self.assertEqual(snapshot_1, host.snapshot_name(1)) + + def test_snapshot_rename_invalid_index(self): + host = create_host() + + name = "MOD" + + self.assertEqual(False, host.snapshot_rename(-1, name)) + self.assertEqual(False, host.snapshot_rename(1000, name)) + + self.assertFalse(host.pedalboard_modified) + + def test_snapshot_rename(self): + host = create_host() + + name = str(uuid4()) + new_name = str(uuid4()) + + self.assertNotEqual(name, new_name) + + # Prepare + host.snapshot_saveas(name) + self.assertEqual(name, host.snapshot_name(0)) + + host.pedalboard_modified = False + + # Rename + self.assertTrue(host.snapshot_rename(0, new_name)) + self.assertEqual(new_name, host.snapshot_name(0)) + self.assertTrue(host.pedalboard_modified) + + def test_snapshot_rename_same_name(self): + host = create_host() + + name = new_name = str(uuid4()) + self.assertEqual(name, new_name) + + # Prepare + host.snapshot_saveas(name) + self.assertEqual(name, host.snapshot_name(0)) + + host.pedalboard_modified = False + + # Rename + self.assertTrue(host.snapshot_rename(0, new_name)) + self.assertEqual(new_name, host.snapshot_name(0)) + self.assertFalse(host.pedalboard_modified) + + def test_snapshot_rename_duplicated_name(self): + host = create_host() + + snapshot_1 = str(uuid4()) + snapshot_2 = str(uuid4()) + snapshot_3 = str(uuid4()) + + # Prepare + host.snapshot_saveas(snapshot_1) + host.snapshot_saveas(snapshot_2) + host.snapshot_saveas(snapshot_3) + + # Rename + self.assertTrue(host.snapshot_rename(0, snapshot_3)) + self.assertTrue(host.snapshot_rename(1, snapshot_3)) + + self.assertEqual(snapshot_3 + " (2)", host.snapshot_name(0)) + self.assertEqual(snapshot_3 + " (3)", host.snapshot_name(1)) + + def methods_to_test(self): + host = create_host() + + # OK + # host.snapshot_name() + # host.snapshot_rename() + + # TODO + # host._snapshot_unique_name() + # host.snapshot_make() + # host.snapshot_save() + # host.snapshot_saveas() + # host.snapshot_remove() + # host.snapshot_load() + # host.snapshot_clear() + + # To check if they are on scope + # host.load_pb_snapshots() + # host.save_state_snapshots() + # host.readdress_snapshots() + + # Out of scope + # host.hmi_list_pedalboard_snapshots() + # host.hmi_pedalboard_reorder_snapshots() + # host.hmi_pedalboard_snapshot_save() + # host.hmi_pedalboard_snapshot_delete() + # host.hmi_pedalboard_snapshot_load() + # host.hmi_snapshot_save() + # host.hmi_snapshot_load() + # host.hmi_clear_ss_name() + # host.hmi_report_ss_name_if_current() + From ebfa75a282822fce0c4cdd7619dd63d1153ef29e Mon Sep 17 00:00:00 2001 From: Paulo Mateus Date: Tue, 17 Oct 2023 23:14:24 -0300 Subject: [PATCH 09/11] Test: host.snapshot_[save,saveas,remove,clear] --- mod/host.py | 7 + test/host/host_hmi_snapshot_test.py | 39 +++++ test/host/host_snapshot_test.py | 212 ++++++++++++++++++++++++---- 3 files changed, 228 insertions(+), 30 deletions(-) create mode 100644 test/host/host_hmi_snapshot_test.py diff --git a/mod/host.py b/mod/host.py index 23a468985..b38836587 100644 --- a/mod/host.py +++ b/mod/host.py @@ -3003,8 +3003,15 @@ def _snapshot_unique_name(self, name): return get_unique_name(name, names) or name def snapshot_make(self, name): + """ + Create a snapshot based on current pedalboard + + :param name: snapshot name + :return: snapshot created + """ self.pedalboard_modified = True + # TODO Create Snapshot model class snapshot = { "name": name, "data": {}, diff --git a/test/host/host_hmi_snapshot_test.py b/test/host/host_hmi_snapshot_test.py new file mode 100644 index 000000000..3c985869b --- /dev/null +++ b/test/host/host_hmi_snapshot_test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later + +import unittest + +from mod.development import FakeHost, FakeHMI +from mod.protocol import Protocol +from mod.session import UserPreferences + + +def create_host(): + # return SESSION.host + + # Avoid to except "Command is already registered" + Protocol.COMMANDS_USED = [] + + callback_hmi = lambda: None + callback_host = lambda: None + + hmi = FakeHMI(callback_hmi) + return FakeHost(hmi, UserPreferences(), callback_host) + + +class HostHmiSnapshotTestCase(unittest.TestCase): + + def test(self): + # TODO + # host.hmi_list_pedalboard_snapshots() + # host.hmi_pedalboard_reorder_snapshots() + # host.hmi_pedalboard_snapshot_save() + # host.hmi_pedalboard_snapshot_delete() + # host.hmi_pedalboard_snapshot_load() + # host.hmi_snapshot_save() + # host.hmi_snapshot_load() + # host.hmi_clear_ss_name() + # host.hmi_report_ss_name_if_current() + pass + diff --git a/test/host/host_snapshot_test.py b/test/host/host_snapshot_test.py index 622e84e15..46d08f9e0 100644 --- a/test/host/host_snapshot_test.py +++ b/test/host/host_snapshot_test.py @@ -7,12 +7,11 @@ from mod.development import FakeHost, FakeHMI from mod.protocol import Protocol -from mod.session import UserPreferences, SESSION +from mod.session import UserPreferences +from mod.settings import DEFAULT_SNAPSHOT_NAME def create_host(): - # return SESSION.host - # Avoid to except "Command is already registered" Protocol.COMMANDS_USED = [] @@ -24,6 +23,25 @@ def create_host(): class HostSnapshotTestCase(unittest.TestCase): + # OK + # host.snapshot_name() + # host.snapshot_rename() + # host.snapshot_save() + # host.snapshot_saveas() + # host.snapshot_remove() + # host.snapshot_clear() + + # Private methods + # host._snapshot_unique_name() + + # Doing + # host.snapshot_make() + # host.snapshot_load() + + # Consider change them to private method + # host.load_pb_snapshots() + # host.save_state_snapshots() + # host.readdress_snapshots() def test_empty_state(self): host = create_host() @@ -32,6 +50,17 @@ def test_empty_state(self): self.assertListEqual([None, None, None], host.hmi_snapshots) + def test__snapshot_unique_name(self): + host = create_host() + + name = str(uuid4()) + self.assertEqual(name, host._snapshot_unique_name(name)) + + host.pedalboard_snapshots.append({'name': name}) + self.assertEqual(name + " (2)", host._snapshot_unique_name(name)) + host.pedalboard_snapshots.append({'name': name + " (2)"}) + self.assertEqual(name + " (3)", host._snapshot_unique_name(name)) + def test_initial_snapshot_name(self): host = create_host() @@ -120,35 +149,158 @@ def test_snapshot_rename_duplicated_name(self): self.assertEqual(snapshot_3 + " (2)", host.snapshot_name(0)) self.assertEqual(snapshot_3 + " (3)", host.snapshot_name(1)) - def methods_to_test(self): + def test_snapshot_save_empty_snapshot_list(self): + host = create_host() + + # Ensure non empty for improve test quality + self.assertEqual(0, len(host.pedalboard_snapshots)) + self.assertEqual(-1, host.current_pedalboard_snapshot_id) + + self.assertFalse(host.pedalboard_modified) + + self.assertFalse(host.snapshot_save()) + self.assertFalse(host.pedalboard_modified) + + def test_snapshot_save(self): + host = create_host() + + # Create at least one snapshot for enable saving + host.snapshot_saveas("Test") + host.pedalboard_modified = False + + self.assertTrue(host.snapshot_save()) + self.assertTrue(host.pedalboard_modified) + + def test_snapshot_save_as_empty(self): + """ + The snapshot creation is based on current pedalboard status. + So, there isn't any problem if there are any snapshots + """ + host = create_host() + + self.assertListEqual([], host.pedalboard_snapshots) + + snapshot_1 = str(uuid4()) + + self.assertEqual(0, host.snapshot_saveas(snapshot_1)) + + def test_snapshot_save_as(self): + host = create_host() + + snapshot_1 = str(uuid4()) + snapshot_2 = str(uuid4()) + snapshot_3 = str(uuid4()) + + self.assertEqual(0, host.snapshot_saveas(snapshot_1)) + self.assertEqual(1, host.snapshot_saveas(snapshot_2)) + self.assertEqual(2, host.snapshot_saveas(snapshot_3)) + + def test_snapshot_saveas_duplicated_name(self): host = create_host() - # OK - # host.snapshot_name() - # host.snapshot_rename() + common_name = str(uuid4()) + + self.assertEqual(0, host.snapshot_saveas(common_name)) + self.assertEqual(1, host.snapshot_saveas(common_name)) + self.assertEqual(2, host.snapshot_saveas(common_name)) + + self.assertEqual(common_name, host.snapshot_name(0)) + self.assertEqual(common_name + " (2)", host.snapshot_name(1)) + self.assertEqual(common_name + " (3)", host.snapshot_name(2)) + def test_snapshot_make_empty_pedalboard(self): + host = create_host() + + name = str(uuid4()) + snapshot = host.snapshot_make(name) + + self.assertEqual(name, snapshot["name"]) + self.assertTrue("data" in snapshot) + + self.assertDictEqual({}, snapshot['data']) + + def test_snapshot_make(self): + # FIXME + pass + + def test_snapshot_load_invalid_index(self): + host = create_host() + + expected_true = lambda it: self.assertTrue(it) + expected_false = lambda it: self.assertFalse(it) + + # FIXME + host.snapshot_load(-1, from_hmi=False, abort_catcher={}, callback=expected_false) + host.snapshot_load(1000, from_hmi=False, abort_catcher={}, callback=expected_false) + + def test_snapshot_load_idx_in_hmi_snapshot(self): + host = create_host() # TODO - # host._snapshot_unique_name() - # host.snapshot_make() - # host.snapshot_save() - # host.snapshot_saveas() - # host.snapshot_remove() - # host.snapshot_load() - # host.snapshot_clear() - - # To check if they are on scope - # host.load_pb_snapshots() - # host.save_state_snapshots() - # host.readdress_snapshots() - - # Out of scope - # host.hmi_list_pedalboard_snapshots() - # host.hmi_pedalboard_reorder_snapshots() - # host.hmi_pedalboard_snapshot_save() - # host.hmi_pedalboard_snapshot_delete() - # host.hmi_pedalboard_snapshot_load() - # host.hmi_snapshot_save() - # host.hmi_snapshot_load() - # host.hmi_clear_ss_name() - # host.hmi_report_ss_name_if_current() + def test_snapshot_load_abort_catcher_is_true(self): + host = create_host() + # TODO + + def test_snapshot_remove_invalid_index(self): + host = create_host() + + # Ensure non empty for improve test quality + self.assertEqual(0, host.snapshot_saveas("test")) + + host.pedalboard_modified = False + + self.assertFalse(host.snapshot_remove(-1)) + self.assertFalse(host.pedalboard_modified) + self.assertFalse(host.snapshot_remove(100)) + self.assertFalse(host.pedalboard_modified) + + def test_snapshot_dont_remove_unique_snapshot(self): + host = create_host() + + # Ensure non empty for improve test quality + self.assertEqual(0, host.snapshot_saveas("test")) + self.assertEqual(1, host.snapshot_saveas("test 2")) + + self.assertEqual(2, len(host.pedalboard_snapshots)) + + host.pedalboard_modified = False + + self.assertTrue(host.snapshot_remove(0)) + self.assertTrue(host.pedalboard_modified) + + host.pedalboard_modified = False + + # Assert non delete unique snapshot + self.assertFalse(host.snapshot_remove(0)) + self.assertFalse(host.pedalboard_modified) + + self.assertEqual(1, len(host.pedalboard_snapshots)) + + def test_snapshot_remove_mark_pedalboard_as_modified(self): + host = create_host() + + # Ensure non empty for improve test quality + self.assertEqual(0, host.snapshot_saveas("test")) + self.assertEqual(1, host.snapshot_saveas("test 2")) + + self.assertEqual(2, len(host.pedalboard_snapshots)) + host.pedalboard_modified = False + + self.assertTrue(host.snapshot_remove(0)) + self.assertTrue(host.pedalboard_modified) + + def test_snapshot_clear(self): + host = create_host() + + # Ensure non empty for improve test quality + self.assertEqual(0, len(host.pedalboard_snapshots)) + self.assertEqual(-1, host.current_pedalboard_snapshot_id) + + self.assertFalse(host.pedalboard_modified) + host.snapshot_clear() + self.assertTrue(host.pedalboard_modified) + + self.assertEqual(1, len(host.pedalboard_snapshots)) + self.assertEqual(0, host.current_pedalboard_snapshot_id) + + self.assertEqual(DEFAULT_SNAPSHOT_NAME, host.snapshot_name(0)) From 941d2f6e657afcc1acacbf5398f885bdf9a2a15e Mon Sep 17 00:00:00 2001 From: Paulo Mateus Date: Wed, 18 Oct 2023 08:50:41 -0300 Subject: [PATCH 10/11] test: host.snapshot_load (partially) --- mod/host.py | 1 + test/host/host_snapshot_test.py | 61 +++++++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/mod/host.py b/mod/host.py index b38836587..690d0d297 100644 --- a/mod/host.py +++ b/mod/host.py @@ -3088,6 +3088,7 @@ def snapshot_remove(self, idx): def snapshot_load_gen_helper(self, idx, from_hmi, abort_catcher, callback): self.snapshot_load(idx, from_hmi, abort_catcher, callback) + # FIXME: Rename callback argument (https://stackoverflow.com/a/57121126) @gen.coroutine def snapshot_load(self, idx, from_hmi, abort_catcher, callback): if idx in (self.HMI_SNAPSHOTS_1, self.HMI_SNAPSHOTS_2, self.HMI_SNAPSHOTS_3): diff --git a/test/host/host_snapshot_test.py b/test/host/host_snapshot_test.py index 46d08f9e0..9033565c4 100644 --- a/test/host/host_snapshot_test.py +++ b/test/host/host_snapshot_test.py @@ -5,6 +5,9 @@ import unittest from uuid import uuid4 +from tornado.gen import Task +from tornado.testing import AsyncTestCase, gen_test + from mod.development import FakeHost, FakeHMI from mod.protocol import Protocol from mod.session import UserPreferences @@ -16,13 +19,13 @@ def create_host(): Protocol.COMMANDS_USED = [] callback_hmi = lambda: None - callback_host = lambda: None + message_callback = lambda text: print(text) hmi = FakeHMI(callback_hmi) - return FakeHost(hmi, UserPreferences(), callback_host) + return FakeHost(hmi, UserPreferences(), message_callback) -class HostSnapshotTestCase(unittest.TestCase): +class HostSnapshotTestCase(AsyncTestCase): # OK # host.snapshot_name() # host.snapshot_rename() @@ -223,23 +226,42 @@ def test_snapshot_make(self): # FIXME pass + @gen_test def test_snapshot_load_invalid_index(self): host = create_host() - expected_true = lambda it: self.assertTrue(it) - expected_false = lambda it: self.assertFalse(it) - - # FIXME - host.snapshot_load(-1, from_hmi=False, abort_catcher={}, callback=expected_false) - host.snapshot_load(1000, from_hmi=False, abort_catcher={}, callback=expected_false) + invalid_indexes = (-1, 1000) + for index in invalid_indexes: + loaded = yield Task(host.snapshot_load_gen_helper, index, False, {}) + self.assertFalse(loaded, msg="index sent: " + str(index)) + @gen_test def test_snapshot_load_idx_in_hmi_snapshot(self): + # FIXME host = create_host() - # TODO + @gen_test def test_snapshot_load_abort_catcher_is_true(self): host = create_host() - # TODO + + host.snapshot_saveas("first") + host.snapshot_saveas("second") + + abort_catcher = {'abort': True} + + loaded = yield Task(host.snapshot_load_gen_helper, 0, False, abort_catcher) + self.assertFalse(loaded) + + @gen_test + def test_snapshot_load(self): + # FIXME - Add plugins + host = create_host() + + host.snapshot_saveas("first") + host.snapshot_saveas("second") + + loaded = yield Task(host.snapshot_load_gen_helper, 0, False, {}) + self.assertTrue(loaded) def test_snapshot_remove_invalid_index(self): host = create_host() @@ -289,6 +311,23 @@ def test_snapshot_remove_mark_pedalboard_as_modified(self): self.assertTrue(host.snapshot_remove(0)) self.assertTrue(host.pedalboard_modified) + def test_snapshot_remove_current_snapshot(self): + host = create_host() + + self.assertEqual(0, host.snapshot_saveas("test")) + self.assertEqual(0, host.current_pedalboard_snapshot_id) + + self.assertEqual(1, host.snapshot_saveas("test 2")) + self.assertEqual(1, host.current_pedalboard_snapshot_id) + + self.assertEqual(2, len(host.pedalboard_snapshots)) + host.pedalboard_modified = False + + # Current snapshot is the hast one + self.assertTrue(host.snapshot_remove(1)) + self.assertTrue(host.pedalboard_modified) + self.assertEqual(-1, host.current_pedalboard_snapshot_id) + def test_snapshot_clear(self): host = create_host() From 3cc9990515bf304f998b251b033af27535371742 Mon Sep 17 00:00:00 2001 From: Paulo Mateus Date: Mon, 23 Oct 2023 19:15:51 -0300 Subject: [PATCH 11/11] build: disabled pr comment --- .github/workflows/unittest.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index 17a33f08c..f69d68a25 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -50,9 +50,9 @@ jobs: indicators: true output: both thresholds: '15 30' - - name: Add Coverage PR Comment - uses: marocchino/sticky-pull-request-comment@v2 - if: github.event_name == 'pull_request' - with: - recreate: true - path: code-coverage-results.md \ No newline at end of file +# - name: Add Coverage PR Comment +# uses: marocchino/sticky-pull-request-comment@v2 +# if: github.event_name == 'pull_request' +# with: +# recreate: true +# path: code-coverage-results.md \ No newline at end of file