From 55a7ce72c723cf1e4e854e5c37fb798ff8511348 Mon Sep 17 00:00:00 2001 From: David Lai Date: Sat, 7 Nov 2020 22:28:01 +0800 Subject: [PATCH 1/5] fix bleeding-rez project name import error --- allzpark/_rezapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/allzpark/_rezapi.py b/allzpark/_rezapi.py index 93f4272..20b6e71 100644 --- a/allzpark/_rezapi.py +++ b/allzpark/_rezapi.py @@ -47,7 +47,7 @@ def find_latest(name, range_=None, paths=None): try: - from rez import project + from rez import __project__ as project except ImportError: # nerdvegas/rez project = "rez" From 031b5790c59758bc932e008a04e9df89c2cd2e48 Mon Sep 17 00:00:00 2001 From: David Lai Date: Fri, 9 Oct 2020 01:32:36 +0800 Subject: [PATCH 2/5] change log print condition The default log.level may be NOTSET, which will not equal to DEBUG level. Change condition to less then INFO level fixed the silence. --- allzpark/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/allzpark/control.py b/allzpark/control.py index de84585..078198c 100644 --- a/allzpark/control.py +++ b/allzpark/control.py @@ -844,7 +844,7 @@ def list_profiles(self, root=None): profiles = root() except Exception: - if log.level == logging.DEBUG: + if log.level < logging.INFO: traceback.print_exc() self.error("Could not find profiles in %s" % root) From 78fb7f94d6aca2f84ea53960bfb80cf1f1eaa791 Mon Sep 17 00:00:00 2001 From: David Lai Date: Sun, 8 Nov 2020 04:50:29 +0800 Subject: [PATCH 3/5] add tests --- tests/test_launch.py | 54 ++++++++++++++++++++++++++++++++++++++++++ tests/test_profiles.py | 36 ++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 tests/test_launch.py diff --git a/tests/test_launch.py b/tests/test_launch.py new file mode 100644 index 0000000..16b1d5d --- /dev/null +++ b/tests/test_launch.py @@ -0,0 +1,54 @@ + +import sys +from tests import util + + +class TestLaunch(util.TestBase): + + def test_launch_subprocess(self): + """Test launching subprocess command""" + util.memory_repository({ + "foo": { + "1": { + "name": "foo", + "version": "1", + "requires": ["~app"], + } + }, + "app": { + "1": { + "name": "app", + "version": "1", + } + }, + }) + self.ctrl.reset(["foo"]) + util.wait(self.ctrl.resetted) + + self.ctrl.select_profile("foo") + util.wait(self.ctrl.state_changed, "ready") + + self.ctrl.select_application("app==1") + self.assertEqual("app==1", self.ctrl.state["appRequest"]) + + commands = self.ctrl.state["commands"] + self.assertEqual(len(commands), 0) + + stdout = list() + stderr = list() + command = ( + '%s -c "' + 'import sys;' + 'sys.stdout.write(\'meow\')"' + ) % sys.executable + + self.ctrl.launch(command=command, + stdout=lambda m: stdout.append(m), + stderr=lambda m: stderr.append(m)) + + util.wait(self.ctrl.state_changed, "launching") + self.assertEqual(len(commands), 1) + + util.wait(commands[0].killed) + self.assertIn("meow", "\n".join(stdout)) + self.assertEqual("", "\n".join(stderr)) diff --git a/tests/test_profiles.py b/tests/test_profiles.py index 93ef796..11ab697 100644 --- a/tests/test_profiles.py +++ b/tests/test_profiles.py @@ -1,4 +1,5 @@ +from unittest import mock from tests import util @@ -100,3 +101,38 @@ def test_profile_list_apps(self): ], list(self.ctrl.state["rezApps"].keys()) ) + + def test_profile_listing_without_root_err(self): + """Listing profile without root will raise AssertionError""" + self.assertRaises(AssertionError, self.ctrl.reset) + self.assertRaises(AssertionError, self.ctrl.list_profiles) + + def test_profile_listing_callable_root_err(self): + """Listing profile with bad callable will prompt error message""" + import traceback + import logging + from allzpark import control + + traceback.print_exc = mock.MagicMock(name="traceback.print_exc") + self.ctrl.error = mock.MagicMock(name="Controller.error") + + def bad_root(): + raise Exception("This should be caught.") + self.ctrl.list_profiles(bad_root) + + # ctrl.error must be called in all cases + self.ctrl.error.assert_called_once() + # traceback.print_exc should be called if logging level is set + # lower than INFO, e.g. DEBUG or NOTSET + if control.log.level < logging.INFO: + traceback.print_exc.assert_called_once() + + def test_profile_listing_invalid_type_root_err(self): + """Listing profile with invalid input type will raise TypeError""" + self.assertRaises(TypeError, self.ctrl.list_profiles, {"foo"}) + + def test_profile_listing_filter_out_empty_names(self): + """Listing profile with empty names will be filtered""" + expected = ["foo", "bar"] + profiles = self.ctrl.list_profiles(expected + [None, ""]) + self.assertEqual(profiles, expected) From 55960b9e448a8c8c8dae5331083ae6052e32d50b Mon Sep 17 00:00:00 2001 From: David Lai Date: Sun, 8 Nov 2020 19:37:26 +0800 Subject: [PATCH 4/5] try fixing "XDG_RUNTIME_DIR not set" --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 47f950c..624d3d6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -102,6 +102,7 @@ jobs: displayName: "Setup Xvfb" - script: | + export DISPLAY=:99 xvfb-run sudo nosetests displayName: "Run tests" From 4e6524251df03c8af101f1cb1356485ee6746ac2 Mon Sep 17 00:00:00 2001 From: David Lai Date: Sun, 8 Nov 2020 19:39:03 +0800 Subject: [PATCH 5/5] fix signal waiting in test Change to connect signals before test --- tests/test_apps.py | 16 +++++++------- tests/test_launch.py | 20 +++++++++-------- tests/test_profiles.py | 25 +++++++++++---------- tests/util.py | 50 ++++++++++++++++++++++++++++-------------- 4 files changed, 66 insertions(+), 45 deletions(-) diff --git a/tests/test_apps.py b/tests/test_apps.py index ad840d3..5ec4314 100644 --- a/tests/test_apps.py +++ b/tests/test_apps.py @@ -31,11 +31,11 @@ def test_select_app(self): } }, }) - self.ctrl.reset(["foo"]) - util.wait(self.ctrl.resetted) + with util.wait_signal(self.ctrl.resetted): + self.ctrl.reset(["foo"]) - self.ctrl.select_profile("foo") - util.wait(self.ctrl.state_changed, "ready") + with util.wait_signal(self.ctrl.state_changed, "ready"): + self.ctrl.select_profile("foo") env = self.ctrl.state["rezEnvirons"] @@ -80,11 +80,11 @@ def test_app_environ(self): } }, }) - self.ctrl.reset(["foo"]) - util.wait(self.ctrl.resetted) + with util.wait_signal(self.ctrl.resetted): + self.ctrl.reset(["foo"]) - self.ctrl.select_profile("foo") - util.wait(self.ctrl.state_changed, "ready") + with util.wait_signal(self.ctrl.state_changed, "ready"): + self.ctrl.select_profile("foo") env = self.ctrl.state["rezEnvirons"] diff --git a/tests/test_launch.py b/tests/test_launch.py index 16b1d5d..aedcb79 100644 --- a/tests/test_launch.py +++ b/tests/test_launch.py @@ -22,11 +22,11 @@ def test_launch_subprocess(self): } }, }) - self.ctrl.reset(["foo"]) - util.wait(self.ctrl.resetted) + with util.wait_signal(self.ctrl.resetted): + self.ctrl.reset(["foo"]) - self.ctrl.select_profile("foo") - util.wait(self.ctrl.state_changed, "ready") + with util.wait_signal(self.ctrl.state_changed, "ready"): + self.ctrl.select_profile("foo") self.ctrl.select_application("app==1") self.assertEqual("app==1", self.ctrl.state["appRequest"]) @@ -42,13 +42,15 @@ def test_launch_subprocess(self): 'sys.stdout.write(\'meow\')"' ) % sys.executable - self.ctrl.launch(command=command, - stdout=lambda m: stdout.append(m), - stderr=lambda m: stderr.append(m)) + with util.wait_signal(self.ctrl.state_changed, "launching"): + self.ctrl.launch(command=command, + stdout=lambda m: stdout.append(m), + stderr=lambda m: stderr.append(m)) - util.wait(self.ctrl.state_changed, "launching") self.assertEqual(len(commands), 1) - util.wait(commands[0].killed) + with util.wait_signal(commands[0].killed): + pass + self.assertIn("meow", "\n".join(stdout)) self.assertEqual("", "\n".join(stderr)) diff --git a/tests/test_profiles.py b/tests/test_profiles.py index 11ab697..b154917 100644 --- a/tests/test_profiles.py +++ b/tests/test_profiles.py @@ -22,9 +22,8 @@ def test_reset(self): } } }) - self.ctrl.reset(["foo", "bar"]) - - util.wait(self.ctrl.resetted) + with util.wait_signal(self.ctrl.resetted): + self.ctrl.reset(["foo", "bar"]) # last profile will be selected by default self.assertEqual("bar", self.ctrl.state["profileName"]) @@ -47,12 +46,13 @@ def test_select_profile_with_out_apps(self): } } }) - self.ctrl.reset(["foo", "bar"]) - util.wait(self.ctrl.resetted) + with util.wait_signal(self.ctrl.resetted): + self.ctrl.reset(["foo", "bar"]) + + with util.wait_signal(self.ctrl.state_changed, "noapps"): + self.ctrl.select_profile("foo") + # wait enter 'noapps' state - self.ctrl.select_profile("foo") - # enter 'noapps' state - util.wait(self.ctrl.state_changed, "noapps") self.assertEqual("foo", self.ctrl.state["profileName"]) def test_profile_list_apps(self): @@ -89,11 +89,12 @@ def test_profile_list_apps(self): } }, }) - self.ctrl.reset(["foo"]) - util.wait(self.ctrl.resetted) + with util.wait_signal(self.ctrl.resetted): + self.ctrl.reset(["foo"]) + + with util.wait_signal(self.ctrl.state_changed, "ready"): + self.ctrl.select_profile("foo") - self.ctrl.select_profile("foo") - util.wait(self.ctrl.state_changed, "ready") self.assertEqual( [ "app_A==1.0.0", diff --git a/tests/util.py b/tests/util.py index dc5cd15..aa8dbbc 100644 --- a/tests/util.py +++ b/tests/util.py @@ -1,6 +1,7 @@ import os import unittest +import contextlib MEMORY_LOCATION = "memory@any" @@ -34,30 +35,47 @@ def memory_repository(packages): repository.data = packages -def wait(signal=None, on_value=None, timeout=1000): +def wait(timeout=1000): from allzpark.vendor.Qt import QtCore loop = QtCore.QEventLoop() timer = QtCore.QTimer() - state = {"timeout": False} - if on_value: + timer.timeout.connect(loop.quit) + timer.start(timeout) + loop.exec_() + + +@contextlib.contextmanager +def wait_signal(signal, on_value=None, timeout=1000): + from allzpark.vendor.Qt import QtCore + + loop = QtCore.QEventLoop() + timer = QtCore.QTimer() + state = {"received": False} + + if on_value is None: + def trigger(*args): + state["received"] = True + timer.stop() + loop.quit() + else: def trigger(value): if value == on_value: + state["received"] = True + timer.stop() loop.quit() - state["timeout"] = False - else: - def trigger(*args): - loop.quit() - state["timeout"] = False - if signal is not None: - state["timeout"] = True - signal.connect(trigger) - timer.timeout.connect(loop.quit) + def on_timeout(): + loop.quit() + raise Exception("Signal waiting timeout.") - timer.start(timeout) - loop.exec_() + signal.connect(trigger) + timer.timeout.connect(on_timeout) - if state["timeout"]: - raise Exception("Signal waiting timeout.") + try: + yield + finally: + if not state["received"]: + timer.start(timeout) + loop.exec_()