Source code for instruments.tests

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Module containing InstrumentKit unit tests

This file hosts a few utility functions to assist with creating and running
unit tests.
"""

# IMPORTS ####################################################################

from __future__ import absolute_import
from __future__ import unicode_literals

import contextlib
from io import BytesIO

from builtins import bytes, str

try:
    from unittest import mock  # from Python 3.3 onward, this is in the stdlib
except ImportError:
    import mock

# FUNCTIONS ##################################################################


[docs]@contextlib.contextmanager def expected_protocol(ins_class, host_to_ins, ins_to_host, sep="\n", repeat=1): """ Given an instrument class, expected output from the host and expected input from the instrument, asserts that the protocol in a context block proceeds according to that expectation. For an example of how to write tests using this context manager, see the ``make_name_test`` function below. :param ins_class: Instrument class to use for the protocol assertion. :type ins_class: `~instruments.Instrument` :param host_to_ins: Data to be sent by the host to the instrument; this is checked against the actual data sent by the instrument class during the execution of this context manager. :type host_to_ins: ``str`` or ``list``; if ``list``, each line is concatenated with the separator given by ``sep``. :param ins_to_host: Data to be sent by the instrument; this is played back during the execution of this context manager, and should be used to assert correct behaviour within the context. :type ins_to_host: ``str`` or ``list``; if ``list``, each line is concatenated with the separator given by ``sep``. :param str sep: Character to be inserted after each string in both host_to_ins and ins_to_host parameters. This is typically the termination character you would like to have inserted. :param int repeat: The number of times the host_to_ins and ins_to_host data sets should be duplicated. Typically the default value of 1 is sufficient, but increasing this is useful when testing multiple calls in the same test that should have the same command transactions. """ if isinstance(sep, bytes): sep = sep.decode("utf-8") # Normalize assertion and playback strings. if isinstance(ins_to_host, list): ins_to_host = [ item.encode("utf-8") if isinstance(item, str) else item for item in ins_to_host ] ins_to_host = sep.encode("utf-8").join(ins_to_host) + \ (sep.encode("utf-8") if ins_to_host else b"") elif isinstance(ins_to_host, str): ins_to_host = ins_to_host.encode("utf-8") ins_to_host *= repeat if isinstance(host_to_ins, list): host_to_ins = [ item.encode("utf-8") if isinstance(item, str) else item for item in host_to_ins ] host_to_ins = sep.encode("utf-8").join(host_to_ins) + \ (sep.encode("utf-8") if host_to_ins else b"") elif isinstance(host_to_ins, str): host_to_ins = host_to_ins.encode("utf-8") host_to_ins *= repeat stdin = BytesIO(ins_to_host) stdout = BytesIO() yield ins_class.open_test(stdin, stdout) assert stdout.getvalue() == host_to_ins, \ """Expected: {} Got: {}""".format(repr(host_to_ins), repr(stdout.getvalue()))
# current = stdin.tell() # stdin.seek(0, 2) # end = stdin.tell() # # assert current == end, \ # """Only read {} bytes out of {}""".format(current, end) def unit_eq(a, b, msg=None, thresh=1e-5): """ Asserts that two unitful quantites ``a`` and ``b`` are equal up to a small numerical threshold. """ assert abs((a - b).magnitude) <= thresh, "{} - {} = {}.{}".format( a, b, a - b, "\n" + msg if msg is not None else "" ) assert a.units == b.units, "{} and {} have different units".format(a, b) def make_name_test(ins_class, name_cmd="*IDN?"): """ Given an instrument class, produces a test which asserts that the instrument correctly reports its name in response to a standard command. """ def test(): with expected_protocol(ins_class, name_cmd + "\n", "NAME\n") as ins: assert ins.name == "NAME" return test