diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 59846a38f..a3beab361 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -13,12 +13,16 @@ ARTIQ-5 :class:`~artiq.coredevice.ad9914.AD9914` phase reference timestamp parameters have been renamed to ``ref_time_mu`` for consistency, as they are in machine units. -* :func:`~artiq.tools.verbosity_args` renamed to :func:`~artiq.tools.add_common_args`. New feature: adds an option to print the ARTIQ version. +* :func:`~artiq.tools.verbosity_args` has been renamed to + :func:`~artiq.tools.add_common_args`, and now adds a ``--version`` flag. * A gateware-level input edge counter has been added, which offers higher throughput and increased flexibility over the usual TTL input PHYs where edge timestamps are not required. See :mod:`artiq.coredevice.edge_counter` for the core device driver and :mod:`artiq.gateware.rtio.phy.edge_counter`/ :meth:`artiq.gateware.eem.DIO.add_std` for the gateware components. +* The controller manager now ignores device database entries without the + ``"command"`` key set to facilitate sharing of devices between multiple + masters. ARTIQ-4 diff --git a/artiq/test/test_ctlmgr.py b/artiq/test/test_ctlmgr.py index 825fad38c..cfadbd4f7 100644 --- a/artiq/test/test_ctlmgr.py +++ b/artiq/test/test_ctlmgr.py @@ -6,6 +6,7 @@ import asyncio from artiq.devices.ctlmgr import Controllers from artiq.protocols.pc_rpc import AsyncioClient +from artiq.tools import expect_no_log_messages logger = logging.getLogger(__name__) @@ -70,3 +71,13 @@ class ControllerCase(unittest.TestCase): await remote.ping() self.loop.run_until_complete(test()) + + def test_no_command_controller(self): + entry = { + "type": "controller", + "host": "::1", + "port": 3253 + } + with expect_no_log_messages(logging.ERROR): + self.controllers["lda_sim"] = entry + self.assertTrue(self.controllers.queue.empty()) diff --git a/artiq/tools.py b/artiq/tools.py index e5517bebe..30941e7a6 100644 --- a/artiq/tools.py +++ b/artiq/tools.py @@ -1,6 +1,7 @@ import asyncio import atexit import collections +import contextlib import importlib.machinery import logging import os @@ -16,6 +17,7 @@ from artiq.protocols import pyon __all__ = ["parse_arguments", "elide", "short_format", "file_import", "get_experiment", "add_common_args", "simple_network_args", + "UnexpectedLogMessageError", "expect_no_log_messages", "multiline_log_config", "init_logger", "bind_address_from_args", "atexit_register_coroutine", "exc_to_warning", "asyncio_wait_or_cancel", "TaskObject", "Condition", @@ -142,6 +144,36 @@ def simple_network_args(parser, default_port): help=h) +class UnexpectedLogMessageError(Exception): + pass + + +class FailingLogHandler(logging.Handler): + def emit(self, record): + raise UnexpectedLogMessageError("Unexpected log message: '{}'".format( + record.getMessage())) + + +@contextlib.contextmanager +def expect_no_log_messages(level, logger=None): + """Raise an UnexpectedLogMessageError if a log message of the given level + (or above) is emitted while the context is active. + + Example: :: + + with expect_no_log_messages(logging.ERROR): + do_stuff_that_should_not_log_errors() + """ + if logger is None: + logger = logging.getLogger() + handler = FailingLogHandler(level) + logger.addHandler(handler) + try: + yield + finally: + logger.removeHandler(handler) + + class MultilineFormatter(logging.Formatter): def __init__(self): logging.Formatter.__init__(