diff --git a/force_bdss/bdss_application.py b/force_bdss/bdss_application.py index 2cc751620af346bcad03a6db223f75ec4faaa29c..a822528477d75c8a36ca856b41a5c753d46106b2 100644 --- a/force_bdss/bdss_application.py +++ b/force_bdss/bdss_application.py @@ -12,6 +12,8 @@ from .factory_registry_plugin import FactoryRegistryPlugin from .core_evaluation_driver import CoreEvaluationDriver from .core_mco_driver import CoreMCODriver +log = logging.getLogger(__name__) + class BDSSApplication(Application): """Main application for the BDSS. @@ -47,7 +49,7 @@ class BDSSApplication(Application): try: mgr.map(functools.partial(_import_extensions, plugins)) except NoMatches: - logging.info("No extensions found") + log.info("No extensions found") super(BDSSApplication, self).__init__(plugins=plugins) @@ -56,7 +58,7 @@ def _import_extensions(plugins, ext): """Service routine extracted for testing. Imports the extension in the plugins argument. """ - logging.info("Found extension {}".format(ext.obj)) + log.info("Found extension {}".format(ext.obj)) plugins.append(ext.obj) @@ -65,7 +67,8 @@ def _load_failure_callback(plugins, manager, entry_point, exception): Reports failure to load a module through stevedore, using the on_load_failure_callback option. """ - logging.error( + log.error( "Unable to load plugin {}. Exception: {}. Message: {}".format( - entry_point, exception.__class__.__name__, exception) + entry_point, exception.__class__.__name__, exception), + exc_info=True, ) diff --git a/force_bdss/cli/force_bdss.py b/force_bdss/cli/force_bdss.py index 48ce95353a49a5531c68bd09d4e441c7151179d7..40503ce5116ce15db3d00e9f85d726b10d028c3b 100644 --- a/force_bdss/cli/force_bdss.py +++ b/force_bdss/cli/force_bdss.py @@ -1,3 +1,4 @@ +import logging import click from ..bdss_application import BDSSApplication @@ -10,12 +11,26 @@ push_exception_handler(reraise_exceptions=True) @click.command() @click.option("--evaluate", is_flag=True) +@click.option("--logfile", + type=click.Path(exists=False), + help="If specified, the log filename. " + " If unspecified, the log will be written to stdout.") @click.argument('workflow_filepath', type=click.Path(exists=True)) -def run(evaluate, workflow_filepath): +def run(evaluate, logfile, workflow_filepath): - application = BDSSApplication( - evaluate=evaluate, - workflow_filepath=workflow_filepath - ) + logging_config = {} + if logfile is not None: + logging_config["filename"] = logfile - application.run() + logging.basicConfig(**logging_config) + log = logging.getLogger(__name__) + + try: + application = BDSSApplication( + evaluate=evaluate, + workflow_filepath=workflow_filepath + ) + + application.run() + except Exception as e: + log.exception(e) diff --git a/force_bdss/cli/tests/test_execution.py b/force_bdss/cli/tests/test_execution.py index 3fbf57fb179b41dea4232cacd0067a89562288f6..1311a92dad32fedd02449b4bf21f49b1b037fd1b 100644 --- a/force_bdss/cli/tests/test_execution.py +++ b/force_bdss/cli/tests/test_execution.py @@ -25,8 +25,10 @@ def fixture_dir(): class TestExecution(unittest.TestCase): def test_plain_invocation_mco(self): with cd(fixtures.dirpath()): - out = subprocess.check_call(["force_bdss", "test_empty.json"]) - self.assertEqual(out, 0) + try: + subprocess.check_output(["force_bdss", "test_empty.json"]) + except subprocess.CalledProcessError: + self.fail("force_bdss returned error at plain invocation.") def test_plain_invocation_evaluate(self): with cd(fixtures.dirpath()): @@ -41,13 +43,16 @@ class TestExecution(unittest.TestCase): def test_unsupported_file_input(self): with cd(fixtures.dirpath()): with self.assertRaises(subprocess.CalledProcessError): - subprocess.check_call(["force_bdss", "test_csv_v2.json"]) + subprocess.check_output( + ["force_bdss", "test_csv_v2.json"], + stderr=subprocess.STDOUT) def test_corrupted_file_input(self): with cd(fixtures.dirpath()): with self.assertRaises(subprocess.CalledProcessError): - subprocess.check_call(["force_bdss", - "test_csv_corrupted.json"]) + subprocess.check_output( + ["force_bdss", "test_csv_corrupted.json"], + stderr=subprocess.STDOUT) if __name__ == '__main__': diff --git a/force_bdss/core_evaluation_driver.py b/force_bdss/core_evaluation_driver.py index 33fc4755a456ad4c0609b0365c643e8f844641a5..1f4b9dc42b10b5d10d9e3719246628cd5ed1f88f 100644 --- a/force_bdss/core_evaluation_driver.py +++ b/force_bdss/core_evaluation_driver.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import sys import logging @@ -14,6 +12,8 @@ from .io.workflow_reader import ( CORE_EVALUATION_DRIVER_ID = plugin_id("core", "CoreEvaluationDriver") +log = logging.getLogger(__name__) + class CoreEvaluationDriver(BaseCoreDriver): """Main plugin that handles the execution of the MCO @@ -26,12 +26,12 @@ class CoreEvaluationDriver(BaseCoreDriver): try: workflow = self.workflow except (InvalidVersionException, InvalidFileException) as e: - print(str(e), file=sys.stderr) + log.exception(e) sys.exit(1) mco_model = workflow.mco if mco_model is None: - print("No MCO defined. Nothing to do. Exiting.") + log.info("No MCO defined. Nothing to do. Exiting.") sys.exit(0) mco_factory = mco_model.factory @@ -105,7 +105,7 @@ def _compute_layer_results(environment_data_values, in_slots) # execute data source, passing only relevant data values. - logging.info("Evaluating for Data Source {}".format( + log.info("Evaluating for Data Source {}".format( factory.name)) res = evaluator.run(model, passed_data_values) @@ -118,7 +118,7 @@ def _compute_layer_results(environment_data_values, len(res), factory.name, len(out_slots) ) - logging.error(error_txt) + log.error(error_txt) raise RuntimeError(error_txt) if len(res) != len(model.output_slot_names): @@ -133,7 +133,7 @@ def _compute_layer_results(environment_data_values, len(model.output_slot_names) ) - logging.error(error_txt) + log.error(error_txt) raise RuntimeError(error_txt) # At this point, the returned data values are unnamed. @@ -173,7 +173,7 @@ def _get_data_values_from_mco(model, communicator): " file is corrupted.").format( len(mco_data_values), len(model.parameters) ) - logging.error(error_txt) + log.error(error_txt) raise RuntimeError(error_txt) # The data values obtained by the communicator are unnamed. diff --git a/force_bdss/core_mco_driver.py b/force_bdss/core_mco_driver.py index 478499af138b50ee6ecdd1abe0ca95e583d37e85..ea2cbfafbec5683480450ec2854d402137d2b717 100644 --- a/force_bdss/core_mco_driver.py +++ b/force_bdss/core_mco_driver.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import sys import logging @@ -44,12 +42,12 @@ class CoreMCODriver(BaseCoreDriver): try: workflow = self.workflow except (InvalidVersionException, InvalidFileException) as e: - print(str(e), file=sys.stderr) + log.exception(e) sys.exit(1) mco_model = workflow.mco if mco_model is None: - print("No MCO defined. Nothing to do. Exiting.") + log.info("No MCO defined. Nothing to do. Exiting.") sys.exit(0) mco_factory = mco_model.factory diff --git a/force_bdss/io/tests/test_workflow_reader.py b/force_bdss/io/tests/test_workflow_reader.py index 8ff216d88a04897742bcafbdd28094c40d1c54f8..c85263ae7520734131714bcebea9076fe2971900 100644 --- a/force_bdss/io/tests/test_workflow_reader.py +++ b/force_bdss/io/tests/test_workflow_reader.py @@ -2,6 +2,8 @@ import json import unittest from six import StringIO +import testfixtures + from force_bdss.factory_registry_plugin import FactoryRegistryPlugin from force_bdss.io.workflow_reader import ( WorkflowReader, @@ -29,15 +31,17 @@ class TestWorkflowReader(unittest.TestCase): "workflow": {} } - with self.assertRaises(InvalidVersionException): - self.wfreader.read(self._as_json_stringio(data)) + with testfixtures.LogCapture(): + with self.assertRaises(InvalidVersionException): + self.wfreader.read(self._as_json_stringio(data)) def test_absent_version(self): data = { } - with self.assertRaises(InvalidFileException): - self.wfreader.read(self._as_json_stringio(data)) + with testfixtures.LogCapture(): + with self.assertRaises(InvalidFileException): + self.wfreader.read(self._as_json_stringio(data)) def test_missing_key(self): data = { @@ -45,8 +49,9 @@ class TestWorkflowReader(unittest.TestCase): "workflow": {} } - with self.assertRaises(InvalidFileException): - self.wfreader.read(self._as_json_stringio(data)) + with testfixtures.LogCapture(): + with self.assertRaises(InvalidFileException): + self.wfreader.read(self._as_json_stringio(data)) def _as_json_stringio(self, data): fp = StringIO() diff --git a/force_bdss/io/tests/test_workflow_writer.py b/force_bdss/io/tests/test_workflow_writer.py index 7d649c067abf006eb9e71dd36feea441610fe1dd..74a8dbf90ff1b776f1bccbc9f40a73462a1d16cf 100644 --- a/force_bdss/io/tests/test_workflow_writer.py +++ b/force_bdss/io/tests/test_workflow_writer.py @@ -53,7 +53,6 @@ class TestWorkflowWriter(unittest.TestCase): fp = StringIO() wf = self._create_mock_workflow() wfwriter.write(wf, fp) - print(fp.getvalue()) fp.seek(0) wfreader = WorkflowReader(self.mock_registry) wf_result = wfreader.read(fp) diff --git a/force_bdss/io/workflow_reader.py b/force_bdss/io/workflow_reader.py index 19d4ecbb9b06309cc1169d7cd465da32558543f2..3222733c555f34f27ddd63d155906cc878df44a9 100644 --- a/force_bdss/io/workflow_reader.py +++ b/force_bdss/io/workflow_reader.py @@ -7,6 +7,8 @@ from force_bdss.core.input_slot_map import InputSlotMap from force_bdss.core.workflow import Workflow from ..factory_registry_plugin import IFactoryRegistryPlugin +log = logging.getLogger(__name__) + SUPPORTED_FILE_VERSIONS = ["1"] @@ -69,12 +71,12 @@ class WorkflowReader(HasStrictTraits): try: version = json_data["version"] except KeyError: - logging.error("File missing version information") + log.error("File missing version information") raise InvalidFileException("Corrupted input file, no version" " specified") if version not in SUPPORTED_FILE_VERSIONS: - logging.error( + log.error( "File contains version {} that is not in the " "list of supported versions {}".format( version, SUPPORTED_FILE_VERSIONS) @@ -92,7 +94,7 @@ class WorkflowReader(HasStrictTraits): wf.notification_listeners[:] = \ self._extract_notification_listeners(wf_data) except KeyError as e: - logging.exception("Could not read file {}".format(file)) + log.error("Could not read file {}".format(file), exc_info=True) raise InvalidFileException("Could not read file {}. " "Unable to find key {}".format(file, e)) return wf diff --git a/force_bdss/tests/test_bdss_application.py b/force_bdss/tests/test_bdss_application.py index abf417075b150dba73118863c254dcf3585a0714..53e5a5cf2e3c3b82f0d7260d7a47018c2d672c68 100644 --- a/force_bdss/tests/test_bdss_application.py +++ b/force_bdss/tests/test_bdss_application.py @@ -1,4 +1,6 @@ import unittest +import warnings + import testfixtures from force_bdss.bdss_application import ( @@ -15,7 +17,9 @@ except ImportError: class TestBDSSApplication(unittest.TestCase): def test_initialization(self): - app = BDSSApplication(False, "foo/bar") + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + app = BDSSApplication(False, "foo/bar") self.assertFalse(app.evaluate) self.assertEqual(app.workflow_filepath, "foo/bar") @@ -28,8 +32,10 @@ class TestBDSSApplication(unittest.TestCase): Exception("hello")) log.check( - ('root', 'ERROR', "Unable to load plugin foo. Exception: " - "Exception. Message: hello") + ('force_bdss.bdss_application', + 'ERROR', + "Unable to load plugin foo. Exception: " + "Exception. Message: hello"), ) self.assertEqual(plugins, []) diff --git a/force_bdss/tests/test_core_evaluation_driver.py b/force_bdss/tests/test_core_evaluation_driver.py index 77058e5957621fcf6279d9a0e197f8203d5cae99..a6084e73294e123a6570e3a2086b08cdb948729d 100644 --- a/force_bdss/tests/test_core_evaluation_driver.py +++ b/force_bdss/tests/test_core_evaluation_driver.py @@ -1,5 +1,8 @@ import unittest +import testfixtures +import six + from force_bdss.tests.probe_classes.factory_registry_plugin import \ ProbeFactoryRegistryPlugin from force_bdss.tests.probe_classes.mco import ProbeMCOFactory @@ -46,10 +49,12 @@ class TestCoreEvaluationDriver(unittest.TestCase): nb_output_data_values=1) driver = CoreEvaluationDriver( application=self.mock_application) - with self.assertRaisesRegexp( - RuntimeError, - "The number of data values returned by the MCO"): - driver.application_started() + with testfixtures.LogCapture(): + with six.assertRaisesRegex( + self, + RuntimeError, + "The number of data values returned by the MCO"): + driver.application_started() def test_error_for_incorrect_output_slots(self): data_source_factories = \ @@ -62,12 +67,14 @@ class TestCoreEvaluationDriver(unittest.TestCase): run_function=run) driver = CoreEvaluationDriver( application=self.mock_application) - with self.assertRaisesRegexp( - RuntimeError, - "The number of data values \(1 values\)" - " returned by 'test_data_source' does not match" - " the number of output slots"): - driver.application_started() + with testfixtures.LogCapture(): + with six.assertRaisesRegex( + self, + RuntimeError, + "The number of data values \(1 values\)" + " returned by 'test_data_source' does not match" + " the number of output slots"): + driver.application_started() def test_error_for_missing_ds_output_names(self): data_source_factories = \ @@ -82,12 +89,14 @@ class TestCoreEvaluationDriver(unittest.TestCase): driver = CoreEvaluationDriver( application=self.mock_application, ) - with self.assertRaisesRegexp( - RuntimeError, - "The number of data values \(1 values\)" - " returned by 'test_data_source' does not match" - " the number of user-defined names"): - driver.application_started() + with testfixtures.LogCapture(): + with six.assertRaisesRegex( + self, + RuntimeError, + "The number of data values \(1 values\)" + " returned by 'test_data_source' does not match" + " the number of user-defined names"): + driver.application_started() def test_error_for_incorrect_kpic_output_slots(self): kpi_calculator_factories = \ @@ -101,12 +110,15 @@ class TestCoreEvaluationDriver(unittest.TestCase): driver = CoreEvaluationDriver( application=self.mock_application, ) - with self.assertRaisesRegexp( - RuntimeError, - "The number of data values \(1 values\)" - " returned by 'test_kpi_calculator' does not match" - " the number of output slots"): - driver.application_started() + with testfixtures.LogCapture(): + with six.assertRaisesRegex( + self, + RuntimeError, + "The number of data values \(1 values\)" + " returned by 'test_kpi_calculator' does not match" + " the number of output slots"): + + driver.application_started() def test_error_for_missing_kpic_output_names(self): kpi_calculator_factories = \ @@ -121,12 +133,15 @@ class TestCoreEvaluationDriver(unittest.TestCase): driver = CoreEvaluationDriver( application=self.mock_application, ) - with self.assertRaisesRegexp( - RuntimeError, - "The number of data values \(1 values\)" - " returned by 'test_kpi_calculator' does not match" - " the number of user-defined names"): - driver.application_started() + + with testfixtures.LogCapture(): + with six.assertRaisesRegex( + self, + RuntimeError, + "The number of data values \(1 values\)" + " returned by 'test_kpi_calculator' does not match" + " the number of user-defined names"): + driver.application_started() def test_bind_data_values(self): data_values = [ @@ -154,11 +169,13 @@ class TestCoreEvaluationDriver(unittest.TestCase): InputSlotMap(name="baz"), ) - with self.assertRaisesRegexp( - RuntimeError, - "The length of the slots is not equal to the length of" - " the slot map"): - _bind_data_values(data_values, slot_map, slots) + with testfixtures.LogCapture(): + with six.assertRaisesRegex( + self, + RuntimeError, + "The length of the slots is not equal to the length of" + " the slot map"): + _bind_data_values(data_values, slot_map, slots) # missing value in the given data values. slot_map = ( @@ -166,11 +183,13 @@ class TestCoreEvaluationDriver(unittest.TestCase): InputSlotMap(name="bar") ) - with self.assertRaisesRegexp( - RuntimeError, - "Unable to find requested name 'blap' in available" - " data values."): - _bind_data_values(data_values, slot_map, slots) + with testfixtures.LogCapture(): + with six.assertRaisesRegex( + self, + RuntimeError, + "Unable to find requested name 'blap' in available" + " data values."): + _bind_data_values(data_values, slot_map, slots) def test_compute_layer_results(self): data_values = [ diff --git a/force_bdss/tests/test_factory_registry_plugin.py b/force_bdss/tests/test_factory_registry_plugin.py index 3a90f7b144d7d05a64daa0ea9525719a6c5a0de4..fccc89aa42d3c019c0b619989d5fb4e421d8fcea 100644 --- a/force_bdss/tests/test_factory_registry_plugin.py +++ b/force_bdss/tests/test_factory_registry_plugin.py @@ -1,4 +1,5 @@ import unittest +import warnings from force_bdss.base_extension_plugin import ( BaseExtensionPlugin) @@ -70,10 +71,12 @@ class MySuperPlugin(BaseExtensionPlugin): class TestFactoryRegistryWithContent(unittest.TestCase): def setUp(self): - self.plugin = FactoryRegistryPlugin() - self.app = Application([self.plugin, MySuperPlugin()]) - self.app.start() - self.app.stop() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + self.plugin = FactoryRegistryPlugin() + self.app = Application([self.plugin, MySuperPlugin()]) + self.app.start() + self.app.stop() def test_initialization(self): self.assertEqual(len(self.plugin.mco_factories), 1)