diff --git a/docs/source/conf.py b/docs/source/conf.py index a69d094e..d2150791 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -15,20 +15,18 @@ import os import sys -sys.path.append( - os.path.join(os.path.dirname(__file__), '..', '..', 'src') -) +sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "src")) # -- Project information ----------------------------------------------------- -project = 'Fixate' -copyright = '2018, Clint Lawrence, Ryan Parry-Jones' -author = 'Clint Lawrence' +project = "Fixate" +copyright = "2018, Clint Lawrence, Ryan Parry-Jones" +author = "Clint Lawrence" # The short X.Y version -version = '' +version = "" # The full version, including alpha/beta/rc tags -release = '' +release = "" # -- General configuration --------------------------------------------------- @@ -40,20 +38,19 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc' -] +extensions = ["sphinx.ext.autodoc"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -76,7 +73,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -104,7 +101,7 @@ # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'Fixatedoc' +htmlhelp_basename = "Fixatedoc" # -- Options for LaTeX output ------------------------------------------------ @@ -113,15 +110,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -131,8 +125,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'Fixate.tex', 'Fixate Documentation', - 'Clint Lawrence', 'manual'), + (master_doc, "Fixate.tex", "Fixate Documentation", "Clint Lawrence", "manual"), ] @@ -140,10 +133,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'fixate', 'Fixate Documentation', - [author], 1) -] +man_pages = [(master_doc, "fixate", "Fixate Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -152,9 +142,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'Fixate', 'Fixate Documentation', - author, 'Fixate', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "Fixate", + "Fixate Documentation", + author, + "Fixate", + "One line description of project.", + "Miscellaneous", + ), ] @@ -173,4 +169,4 @@ # epub_uid = '' # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] \ No newline at end of file +epub_exclude_files = ["search.html"] diff --git a/setup.py b/setup.py index c83509ff..16600352 100644 --- a/setup.py +++ b/setup.py @@ -7,13 +7,15 @@ BASE_LOCATION = os.path.abspath(os.path.dirname(__file__)) -VERSION_FILE = os.path.join(BASE_LOCATION, 'src', 'fixate', '__init__.py') -REQUIRES_FILE = 'requirements.txt' +VERSION_FILE = os.path.join(BASE_LOCATION, "src", "fixate", "__init__.py") +REQUIRES_FILE = "requirements.txt" DEPENDENCIES_FILE = None def filter_comments(fd): - no_comments = list(filter(lambda l: l.strip().startswith("#") is False, fd.readlines())) + no_comments = list( + filter(lambda l: l.strip().startswith("#") is False, fd.readlines()) + ) return list(filter(lambda l: l.strip().startswith("-") is False, no_comments)) @@ -22,15 +24,18 @@ def readfile(filename, func): with open(os.path.join(BASE_LOCATION, filename)) as f: data = func(f) except (IOError, IndexError): - sys.stderr.write(u""" + sys.stderr.write( + u""" Can't find '%s' file. This doesn't seem to be a valid release. -""" % filename) +""" + % filename + ) sys.exit(1) return data def get_version(): - with open(VERSION_FILE, 'r') as f: + with open(VERSION_FILE, "r") as f: data = f.read() m = re.search(r"__version__ ?= ?\"[\d.]+\"", data) res = m.group(0) @@ -54,20 +59,21 @@ def get_dependencies(): author="Ryan Parry-Jones", author_email="ryanspj+github@gmail.com", description="Framework for hardware test fixtures and automated test environments", - package_dir={'': 'src'}, - packages=find_packages('src'), + package_dir={"": "src"}, + packages=find_packages("src"), scripts=[], url="http://pyfixate.com/", version=get_version(), - install_requires=["pyvisa", - "pypubsub", - # "pyqt5", # Required for the QT GUI - "pynput", - "ruamel.yaml", - "pyserial", - "cmd2", # required for fxconfig - ], - python_requires='~=3.4', + install_requires=[ + "pyvisa", + "pypubsub", + # "pyqt5", # Required for the QT GUI + "pynput", + "ruamel.yaml", + "pyserial", + "cmd2", # required for fxconfig + ], + python_requires="~=3.4", dependency_links=[], include_package_data=True, zip_safe=False, @@ -76,7 +82,7 @@ def get_dependencies(): "Intended Audience :: Manufacturing", "License :: OSI Approved :: MIT License", "Operating System :: Microsoft :: Windows", - "Programming Language :: Python :: 3 :: Only" + "Programming Language :: Python :: 3 :: Only", ], entry_points=""" [console_scripts] diff --git a/src/fixate/core/common.py b/src/fixate/core/common.py index abe71c57..2282f962 100644 --- a/src/fixate/core/common.py +++ b/src/fixate/core/common.py @@ -448,7 +448,7 @@ def tear_down(self): Optionally override this code that is always executed at the end of the test whether it was successful or not """ - def test(self): + def test(self, **kwargs): """ This method should be overridden with the test code This is the test sequence code diff --git a/src/fixate/drivers/daq/daqmx.py b/src/fixate/drivers/daq/daqmx.py index b5274bba..0ec97139 100644 --- a/src/fixate/drivers/daq/daqmx.py +++ b/src/fixate/drivers/daq/daqmx.py @@ -71,9 +71,7 @@ class DaqTask: - """ - - """ + """""" task_state = "" task = None @@ -115,9 +113,7 @@ def start(self): class DigitalOut(DaqTask): - """ - - """ + """""" def __init__(self, task_string, io_length): self.io_length = io_length @@ -189,9 +185,7 @@ def write(self, data): class DigitalIn(DaqTask): - """ - - """ + """""" def __init__(self, task_string, io_length): self.io_length = io_length @@ -226,9 +220,7 @@ def read(self): class BufferedWrite(DaqTask): - """ - - """ + """""" def __init__(self, task_string, io_length, frequency): self.task_string = task_string diff --git a/src/fixate/drivers/ftdi.py b/src/fixate/drivers/ftdi.py index e4647fde..68cb75c7 100644 --- a/src/fixate/drivers/ftdi.py +++ b/src/fixate/drivers/ftdi.py @@ -297,12 +297,12 @@ def baud_rate(self, rate): def write_bit_mode(self, mask, validate=False): """ - handle; gained from device info - mask; value to write for the mask - for BIT_MODE.FT_BITMODE_CBUS_BITBANG - upper nibble is input (0) output (1) - lower nibble is pin value low (0) high (1) - bit_mode; Type BIT_MODE + handle; gained from device info + mask; value to write for the mask + for BIT_MODE.FT_BITMODE_CBUS_BITBANG + upper nibble is input (0) output (1) + lower nibble is pin value low (0) high (1) + bit_mode; Type BIT_MODE """ check_return(ftdI2xx.FT_SetBitMode(self.handle, UCHAR(mask), self.bit_mode)) data_bus = UCHAR() diff --git a/src/fixate/examples/shared_test_data.py b/src/fixate/examples/shared_test_data.py new file mode 100644 index 00000000..4d302078 --- /dev/null +++ b/src/fixate/examples/shared_test_data.py @@ -0,0 +1,83 @@ +from fixate.core.ui import user_info +from fixate.core.common import TestClass, TestList +import fixate + +__version__ = "2" + +seq = fixate.config.RESOURCES["SEQUENCER"] + + +class TestListOuter(TestList): + outer: int + other_data: float + + def __init__(self, seq, *, outer: int = 5): + super(TestListOuter, self).__init__(seq) + self.outer = outer + + def enter(self): + self.other_data = 0.5 + + +class TestListInner(TestList): + inner: int + other_data: str + + def enter(self): + self.inner = 10 + self.other_data = "Hello" + + +class TestListNotRun(TestList): + other_data: int + + +class TestOuter(TestClass): + def test(self, outer_list: TestListOuter): + user_info(f"Outer.outer: {outer_list.outer}") + user_info(f"Outer.other: {outer_list.other_data}") + + +class TestBoth(TestClass): + def test(self, outer_list: TestListOuter, inner_list: TestListInner): + user_info(f"Outer.outer: {outer_list.outer}") + user_info(f"Outer.other: {outer_list.other_data}") + user_info(f"Inner.inner: {inner_list.inner}") + user_info(f"Inner.other: {inner_list.other_data}") + + +class TestOther(TestClass): + def test( + self, + outer_list: TestListOuter, + inner_list: TestListInner, + not_run: TestListNotRun, + ): + user_info(f"Outer.outer: {outer_list.outer}") + user_info(f"Outer.other: {outer_list.other_data}") + user_info(f"Inner.inner: {inner_list.inner}") + user_info(f"Inner.other: {inner_list.other_data}") + + +test_data = { + "standard": TestList([TestListOuter([TestListInner([TestBoth()])])]), + # Should fail as TestListNotRun in not run + "test_not_run": TestList([TestListOuter([TestListInner([TestOther()])])]), + # Should fail as TestBoth doesn't have TestListOuter in scope + "list_out_of_scope": TestList( + [TestListOuter([TestOuter()]), TestListInner([TestBoth()])] + ), + # Should use the inner most match to the test list. First TestOuter should print 10, second should print 20 + # Eg. 1.1 Outer.outer: 10, 1.2.1 Outer.outer: 20 + "used_multi_level": TestList( + [ + TestListOuter( + [ + TestOuter(), + TestListOuter([TestOuter()], outer=20), + ], + outer=10, + ) + ] + ), +} diff --git a/src/fixate/reporting/csv.py b/src/fixate/reporting/csv.py index 7cfba965..bebfdaf9 100644 --- a/src/fixate/reporting/csv.py +++ b/src/fixate/reporting/csv.py @@ -111,7 +111,9 @@ def test(self): class CSVWriter: - def __init__(self,): + def __init__( + self, + ): self.csv_queue = Queue() self.csv_writer = None # data = fixate.config.get_config_dict() diff --git a/src/fixate/sequencer.py b/src/fixate/sequencer.py index 6337246c..91c36615 100644 --- a/src/fixate/sequencer.py +++ b/src/fixate/sequencer.py @@ -1,9 +1,10 @@ import sys import time import re +import inspect from pubsub import pub from fixate.core.common import TestList, TestClass -from fixate.core.exceptions import SequenceAbort, CheckFail +from fixate.core.exceptions import SequenceAbort, CheckFail, TestError from fixate.core.ui import user_retry_abort_fail STATUS_STATES = ["Idle", "Running", "Paused", "Finished", "Restart", "Aborted"] @@ -91,6 +92,17 @@ def get_parent_level(level): return level +def get_params_to_inject(f, classes): + params = {} + sig = inspect.signature(f) + for key, param in sig.parameters.items(): + try: + params[key] = classes[param.annotation] + except KeyError: + raise TestError(f"Missing pre-requisite test list {key}") + return params + + class Sequencer: def __init__(self): self.tests = TestList() @@ -312,10 +324,14 @@ def run_test(self): break self.chk_fail, self.chk_pass = 0, 0 # Run the test + classes = {} try: for index_context, current_level in enumerate(self.context): - current_level.current().set_up() - active_test.test() + current = current_level.current() + classes[type(current)] = current + current.set_up() + params = get_params_to_inject(active_test.test, classes) + active_test.test(**params) finally: for current_level in self.context[index_context::-1]: current_level.current().tear_down() diff --git a/src/fixate/ui_cmdline/kbhit.py b/src/fixate/ui_cmdline/kbhit.py index efb097a7..6c2008eb 100644 --- a/src/fixate/ui_cmdline/kbhit.py +++ b/src/fixate/ui_cmdline/kbhit.py @@ -34,8 +34,7 @@ class KBHit: def __init__(self): - """Creates a KBHit object that you can call to do various keyboard things. - """ + """Creates a KBHit object that you can call to do various keyboard things.""" if os.name == "nt": pass @@ -55,8 +54,7 @@ def __init__(self): atexit.register(self.set_normal_term) def set_normal_term(self): - """ Resets to normal terminal. On Windows this is a no-op. - """ + """Resets to normal terminal. On Windows this is a no-op.""" if os.name == "nt": pass @@ -65,8 +63,8 @@ def set_normal_term(self): termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term) def getch(self): - """ Returns a keyboard character after kbhit() has been called. - Should not be called in the same program as getarrow(). + """Returns a keyboard character after kbhit() has been called. + Should not be called in the same program as getarrow(). """ s = "" @@ -78,7 +76,7 @@ def getch(self): return sys.stdin.read(1) def getarrow(self): - """ Returns an arrow-key code after kbhit() has been called. Codes are + """Returns an arrow-key code after kbhit() has been called. Codes are 0 : up 1 : right 2 : down @@ -98,8 +96,7 @@ def getarrow(self): return vals.index(ord(c.decode("utf-8"))) def kbhit(self): - """ Returns True if keyboard character was hit, False otherwise. - """ + """Returns True if keyboard character was hit, False otherwise.""" if os.name == "nt": return msvcrt.kbhit() diff --git a/src/fixate/ui_gui_qt/ui_gui_qt.py b/src/fixate/ui_gui_qt/ui_gui_qt.py index ca005962..14951ba6 100644 --- a/src/fixate/ui_gui_qt/ui_gui_qt.py +++ b/src/fixate/ui_gui_qt/ui_gui_qt.py @@ -601,7 +601,7 @@ def on_progress_set_max(self, test_count): def _completion_code(self, code): """This function is the first one called when the sequencer completes normally. - Set the exit code, and signal the main thread.""" + Set the exit code, and signal the main thread.""" self.status_code = code self.sig_finish.emit() diff --git a/test/core/map_data.py b/test/core/map_data.py index 2754bbc8..31586fb2 100644 --- a/test/core/map_data.py +++ b/test/core/map_data.py @@ -64,9 +64,7 @@ # RPJ version class MuxDmmInputHi(VirtualMux): - """ - - """ + """""" # LSB(index 0) to MSB(index -1) pin_list = [ diff --git a/test/core/test_jig_mapping.py b/test/core/test_jig_mapping.py index 9710bf04..ffcf8a4b 100644 --- a/test/core/test_jig_mapping.py +++ b/test/core/test_jig_mapping.py @@ -22,9 +22,7 @@ class TestVirtualAddressMap(TestCase): - """ - - """ + """""" def setUp(self): self.v_map = VirtualAddressMap() @@ -189,9 +187,7 @@ def test_pin_on_duplicate_mux(self): class TestAddressHandler(TestCase): - """ - - """ + """""" def setUp(self): self.address_handler = MagicMock(spec=AddressHandler) @@ -208,9 +204,7 @@ class MockedMux(VirtualMux): class TestVirtualMux(TestCase): - """ - - """ + """""" def setUp(self): MockedMux.pin_list = ["{}".format(x) for x in range(16)] diff --git a/test/drivers/test_bk_178x.py b/test/drivers/test_bk_178x.py index 31b02fb4..a9493006 100644 --- a/test/drivers/test_bk_178x.py +++ b/test/drivers/test_bk_178x.py @@ -42,8 +42,7 @@ def test_max_voltage_command(self): ) def test_checksum(self): - """Return the sum of the bytes in cmd modulo 256. - """ + """Return the sum of the bytes in cmd modulo 256.""" max_voltage_5_command = bytearray(26) max_voltage_5_command[0:5] = [0xAA, 0x00, 0x22, 0x88, 0x13] max_voltage_5_command[-1] = 0x67 @@ -197,8 +196,7 @@ def _build_byte_array(self, iterable): return "".join(chr(i) for i in iterable).encode() def _command_properly_formed(self, cmd): - """Return 1 if a command is properly formed; otherwise, return 0. - """ + """Return 1 if a command is properly formed; otherwise, return 0.""" commands = ( 0x20, 0x21, @@ -246,8 +244,7 @@ def _command_properly_formed(self, cmd): return 1 def _calculate_checksum(self, cmd): - """Return the sum of the bytes in cmd modulo 256. - """ + """Return the sum of the bytes in cmd modulo 256.""" assert (len(cmd) == self.length_packet - 1) or (len(cmd) == self.length_packet) return sum(cmd[:-1]) % 256 @@ -382,9 +379,9 @@ def Initialize(self, com_port, baudrate, address=0): def DumpCommand(self, bytes): """Print out the contents of a 26 byte command. Example: - aa .. 20 01 .. .. .. .. .. .. - .. .. .. .. .. .. .. .. .. .. - .. .. .. .. .. cb + aa .. 20 01 .. .. .. .. .. .. + .. .. .. .. .. .. .. .. .. .. + .. .. .. .. .. cb """ assert len(bytes) == self.length_packet header = " " * 3 @@ -406,8 +403,7 @@ def DumpCommand(self, bytes): out(nl) def CommandProperlyFormed(self, cmd): - """Return 1 if a command is properly formed; otherwise, return 0. - """ + """Return 1 if a command is properly formed; otherwise, return 0.""" commands = ( 0x20, 0x21, @@ -461,8 +457,7 @@ def CommandProperlyFormed(self, cmd): return 1 def CalculateChecksum(self, cmd): - """Return the sum of the bytes in cmd modulo 256. - """ + """Return the sum of the bytes in cmd modulo 256.""" assert (len(cmd) == self.length_packet - 1) or (len(cmd) == self.length_packet) checksum = 0 for i in range(self.length_packet - 1): @@ -536,8 +531,7 @@ def GetReserved(self, num_used): return chr(0) * num def PrintCommandAndResponse(self, cmd, response, cmd_name): - """Print the command and its response if debugging is on. - """ + """Print the command and its response if debugging is on.""" assert cmd_name if self.debug: out(cmd_name + " command:" + nl) @@ -560,8 +554,7 @@ def GetCommand(self, command, value, num_bytes=4): return cmd def GetData(self, data, num_bytes=4): - """Extract the little endian integer from the data and return it. - """ + """Extract the little endian integer from the data and return it.""" assert len(data) == self.length_packet if num_bytes == 1: return ord(data[3])