diff --git a/plinth/app.py b/plinth/app.py index 70c027639..64097b359 100644 --- a/plinth/app.py +++ b/plinth/app.py @@ -115,6 +115,8 @@ class App: Results are typically collected by diagnosing each component of the app and then supplementing the results with any app level diagnostic tests. + Also see :meth:`.has_diagnostics`. + """ results = [] for component in self.components.values(): @@ -122,6 +124,36 @@ class App: return results + def has_diagnostics(self): + """Return whether at least one diagnostic test is implemented. + + If this method returns True, a button or menu item is shown to the + user to run diagnostics on this app. When the action is selected by the + user, the :meth:`.diagnose` method is called and the results are + presented to the user. Additionally collection of diagnostic results of + all apps can be obtained by the user from the Diagnostics module in + System section. + + If a component of this app implements a diagnostic test, this method + returns True. + + Further, if a subclass of App overrides the :meth:`.diagnose` method, + it is assumed that it is for implementing diagnostic tests and this + method returns True for such an app. Override this method if this + default behavior does not fit the needs. + + """ + # App implements some diagnostics + if self.__class__.diagnose is not App.diagnose: + return True + + # Any of the components implement diagnostics + for component in self.components.values(): + if component.has_diagnostics(): + return True + + return False + class Component: """Interface for an app component.""" @@ -148,9 +180,25 @@ class Component: result. The test result is a string enumeration from 'failed', 'passed' and 'error'. + Also see :meth:`.has_diagnostics`. + """ return [] + def has_diagnostics(self): + """Return whether at least one diagnostic test is implemented. + + If this method return True, the :meth:`App.has_diagnostics`. also + returns True. + + If a subclass of Component overrides the :meth:`.diagnose` method, it + is assumed that it is for implementing diagnostic tests and this method + returns True for such a component. Override this method if this default + behavior does not fit the needs. + + """ + return self.__class__.diagnose is not Component.diagnose + class FollowerComponent(Component): """Interface for an app component that follows other components. diff --git a/plinth/modules/diagnostics/diagnostics.py b/plinth/modules/diagnostics/diagnostics.py index 4eb1b3c45..0b637527c 100644 --- a/plinth/modules/diagnostics/diagnostics.py +++ b/plinth/modules/diagnostics/diagnostics.py @@ -112,6 +112,9 @@ def run_on_all_enabled_modules(): if not app.is_enabled(): continue + if not app.has_diagnostics(): + continue + apps.append((app.app_id, app)) current_results['results'][app.app_id] = None diff --git a/plinth/tests/test_app.py b/plinth/tests/test_app.py index ceebb3df8..b91332251 100644 --- a/plinth/tests/test_app.py +++ b/plinth/tests/test_app.py @@ -19,6 +19,7 @@ Test module for App, base class for all applications. """ import collections +from unittest.mock import patch import pytest @@ -193,6 +194,23 @@ def test_app_diagnose(app_with_components): ('test-result-test-leader-2', 'success')] +def test_app_has_diagnostics(app_with_components): + """Test checking if app has diagnostics implemented.""" + app = app_with_components + + # App with components that has diagnostics + assert app.has_diagnostics() + + # App with components that don't have diagnostics + app.remove('test-leader-1') + app.remove('test-leader-2') + assert not app.has_diagnostics() + + # App with app-level diagnostics + with patch.object(AppTest, 'diagnose', return_value=[('test1', 'passed')]): + assert app.has_diagnostics() + + def test_component_initialization(): """Test that component is initialized properly.""" with pytest.raises(ValueError): @@ -209,6 +227,15 @@ def test_component_diagnose(): assert component.diagnose() == [] +def test_component_has_diagnostics(): + """Test checking if component has diagnostics implemented.""" + component = LeaderTest('test-leader-1') + assert component.has_diagnostics() + + component = FollowerComponent('test-follower-1') + assert not component.has_diagnostics() + + def test_follower_component_initialization(): """Test that follower component is initialized properly.""" component = FollowerComponent('test-follower-1')