From e97fae59916fcb2bca915d6dc3ef741103bac108 Mon Sep 17 00:00:00 2001 From: Nick Deligiannis Date: Wed, 11 Sep 2024 11:31:07 +0300 Subject: [PATCH] Refactored docstrings for Sphinx compliance --- src/asm.py | 137 ++++++++++++---------- src/zoix.py | 321 +++++++++++++++++++++++++++++++++------------------- 2 files changed, 279 insertions(+), 179 deletions(-) diff --git a/src/asm.py b/src/asm.py index 39f0061..694901f 100755 --- a/src/asm.py +++ b/src/asm.py @@ -28,6 +28,7 @@ @dataclass class Codeline: """Represents a line of assembly code""" + lineno: int data: str valid_insn: bool @@ -131,7 +132,7 @@ def __call__(cls, *args, **kwargs): class ISA(metaclass=Singleton): - """This **Singleton** class provides utilities for the considered ISA.""" + """**Singleton** class to provide utilities for the considered ISA.""" def __init__(self, isa: pathlib.Path) -> "ISA": @@ -170,25 +171,28 @@ def __repr__(self): return f"ISA({str(self.source)})" def get_mnemonics(self) -> set: - """Returns a set with the ISA-lang mnemonics. + """ + Returns a set with the ISA-lang mnemonics. - - Parameters: - - None + Args: + None - - Returns: - - set: A set with all the ISA-lang mnemonics.""" + Returns: + set: A set with all the ISA-lang mnemonics.""" return self.mnemonics def is_instruction(self, assembly_line: str) -> bool: - """Checks if `assembly_line`'s first sub-string is present the class - `keywords` set. + """ + Checks if ``assembly_line``'s first sub-string is present the class + ``keywords`` set. - - Parameters: - - assembly_line (str): an assembly mnemonic. + Args: + assembly_line (str): an assembly mnemonic. - - Returns: - - bool: True if `assembly_line` is in `mnemonics`, False otherwise. + Returns: + bool: True if ``assembly_line`` is in ``mnemonics``, False + otherwise. """ potential_instruction = assembly_line.split()[0] @@ -196,8 +200,11 @@ def is_instruction(self, assembly_line: str) -> bool: class AssemblyHandler(): - """This class is responsible of managing **one** assembly file. - It operates on the `assembly_source` file and removes/restores lines.""" + """ + Manages **one** assembly file. + + It operates on the file by removing/restoring lines of code. + """ def __init__(self, isa: ISA, @@ -246,47 +253,51 @@ def __init__(self, for i in range(0, len(self.candidates), chunksize)] def set_test_application_time(self, time: int) -> None: - """Sets the test application time attribute + """ + Sets the test application time attribute - - Parameters: - - time (int): the time value in s/m/u/n/p seconds. + Args: + time (int): the time value in s/m/u/n/p seconds. - - Returns: - - None + Returns: + None """ self.test_application_time = time def get_asm_source(self) -> pathlib.Path: - """Returns the assembly source file `pathlib.Path`. - - - Parameters: - - None + """ + Returns the assembly source file ``pathlib.Path``. - - Returns: - - pathlib.Path: The assembly source `pathlib.Path`.""" + Returns: + pathlib.Path: The assembly source ``pathlib.Path``. + """ return self.asm_file def get_code(self) -> list[Codeline]: - """Returns the parsed code as a list of `Codelines`. - - Parameters: - - None + """ + Returns the parsed code as a list of ``Codelines``. Returns: - - list: A list of `Codeline` entries.""" + list: A list of ``Codeline`` entries. + """ return [codeline for chunk in self.candidates for codeline in chunk] def get_candidate(self, lineno: int) -> Codeline: - """Returns the Codeline in candidates with the specified lineno + """ + Returns the Codeline in candidates with the specified lineno + + Args: + lineno (int): the line number of the candidate to be found. - - Parameters: - - lineno (int): the line number of the candidate to be found. + Returns: + Codeline: the ```Codeline`` with ``Codeline.lineno == lineno``` + if found. Raises LookupError otherwise. - - Returns: - - Codeline: the `Codeline` with `Codeline.lineno == lineno` - if found. Raises LookupError otherwise.""" + Raises: + LookUpError: If the requested Codeline does not exist + """ for chunk in self.candidates: @@ -299,16 +310,18 @@ def get_candidate(self, lineno: int) -> Codeline: raise LookupError(f"Requested Codeline with {lineno=} not found!") def get_random_candidate(self, pop_candidate: bool = True) -> Codeline: - """In a uniform random manner selects one `Codeline` and returns it - while also optionally removing it from the `candidate` collection + """ + In a uniform random manner selects one ``Codeline`` and returns it + while also optionally removing it from the ``candidate`` collection - - Parameters: - - pop_candidate (bool): When True, deletes the `Codeline` from the + Args: + pop_candidate (bool): When True, deletes the ``Codeline`` from the collection after identifying it. - - Returns: - - Codeline: A random `Codeline` from a random `self.candidates` - chunk.""" + Returns: + Codeline: A random ``Codeline`` from a random ``self.candidates`` + chunk. + """ random_chunk = random.randint(0, len(self.candidates) - 1) random_codeline = \ @@ -328,16 +341,18 @@ def get_random_candidate(self, pop_candidate: bool = True) -> Codeline: return codeline def remove(self, codeline: Codeline) -> None: - """Creates a new assembly file by using the current `self.asm_code` - as a source and skips the the line which corresponds to `codeline`'s - `lineno` attribute. + """ + Creates a new assembly file by using the current ``self.asm_code`` + as a source and skips the the line which corresponds to ``codeline``'s + ``lineno`` attribute. - - Parameters: - - codeline (Codeline): The `Codeline` to be removed from the - assembly file. + Args: + codeline (Codeline): The ``Codeline`` to be removed from the + assembly file. - - Returns: - - None""" + Returns: + None + """ with open(self.asm_file) as source, \ tempfile.NamedTemporaryFile('w', delete=False) as new_source: @@ -370,14 +385,14 @@ def remove(self, codeline: Codeline) -> None: log.debug(f"Changelog entries are now {self.asm_file_changelog}") def restore(self) -> None: - """Re-enters the last `Codeline` from the changelog to the assembly - file. The `self.candidates` lineno fields are updated if >= than the + """ + Re-enters the last ``Codeline`` from the changelog to the assembly + file. The ``self.candidates`` lineno fields are updated if >= than the entry which is being restored. - Parameters: - - None Returns: - - None""" + None + """ if not self.asm_file_changelog: log.debug(f"{self.asm_file_changelog=} empty, nothing to restore") @@ -427,15 +442,15 @@ def restore(self) -> None: new_file.replace(self.asm_file) def save(self): - """Saves the current version of assembly file. The filename + """ + Saves the current version of assembly file. The filename will be the original stem plus all current changelog codelines' - linenos seperated with a dash. If `self.asm_file_changelog` is + linenos seperated with a dash. If ``self.asm_file_changelog`` is empty, it does nothing. - Parameters: - - None Returns: - - Nothing""" + None + """ if not self.asm_file_changelog: log.debug("No changes in changelog to be saved.") diff --git a/src/zoix.py b/src/zoix.py index f124d72..27556ff 100755 --- a/src/zoix.py +++ b/src/zoix.py @@ -8,6 +8,7 @@ import enum import pathlib import csv +from typing import Any # TEMPORARY log = logging.getLogger("testcrush logger") @@ -24,35 +25,34 @@ class Compilation(enum.Enum): - + """Statuses for the VCS compilation of HDL sources.""" ERROR = 0 # stderr contains text SUCCESS = 1 # None of the above class LogicSimulation(enum.Enum): - + """Statuses for the simv logic simulation of a given program.""" TIMEOUT = 0 # Endless loop SIM_ERROR = 1 # stderr contains text SUCCESS = 2 # None of the above class LogicSimulationException(BaseException): - + """Custom exception for the simv logic simulation.""" def __init__(self, message="Error during VC Logic Simulation"): self.message = message super().__init__(self.message) class FaultSimulation(enum.Enum): - + """Statuses for the Z01X fault simulation.""" TIMEOUT = 0 # Wall-clock FSIM_ERROR = 1 # stderr contains text SUCCESS = 2 # None of the above class Fault(): - """Generic representation of a fault. - All attributes are set as strings""" + """Generic representation of a fault. All attributes are set as strings.""" def __init__(self, **fault_attributes: dict[str, str]) -> 'Fault': for attribute, value in fault_attributes.items(): @@ -74,12 +74,38 @@ def __eq__(self, other): return False - def get(self, attribute: str, default: str | None = None) -> str: - """Generic getter method.""" + def get(self, attribute: str, default: str | None = None) -> str | Any: + """ + Generic getter method for arbitrary attribute. + + Args: + attribute (str): The requested attribute of the fault. + default (str | None): A default value to be used as a guard. + + Returns: + str | Any: The fault attribute. If no cast has been performed on + the attribute then the default type is ``str``. + """ + return getattr(self, attribute.replace(" ", "_"), default) def cast_attribute(self, attribute: str, func: callable) -> None: - """Casts the type of the internal attribute""" + """ + Casts the type of the internal attribute + + Args: + attribute (str): The requested attribute of the fault to be casted. + func (callable): A function to cast the fault attribute. + + Returns: + None + + Raises: + KeyError: If the requested attribute does not exist. + ValueError: If the cast cannot be performed e.g., when + ``int('a')``. + """ + attribute = attribute.replace(" ", "_") try: self.__dict__[attribute] = func(getattr(self, attribute)) @@ -95,9 +121,11 @@ def cast_attribute(self, attribute: str, func: callable) -> None: class CSVFaultReport(): - """Manipulates the VC-Z01X summary and report **CSV** files which are - generated after fault simulation. The `report` instruction specified in the - `fcm.tcl` file **MUST** be executed with the `-csv` option.""" + """Manipulates the VC-Z01X summary and report **CSV** files. + + These files are expected to be generated after fault simulation. + The ``report`` instruction specified in the``fcm.tcl`` file **MUST** be + executed with the ``-csv`` option.""" def __init__(self, fault_summary: pathlib.Path, fault_report: pathlib.Path) -> "CSVFaultReport": @@ -106,13 +134,18 @@ def __init__(self, fault_summary: pathlib.Path, self.fault_report: pathlib.Path = fault_report.absolute() def set_fault_summary(self, fault_summary: str) -> None: - """Setter method for fault summary. + """ + Setter method for fault summary. - - Parameters: - - fault_summary (str): The new fault summary filename. + Args: + fault_summary (str): The new fault summary filename. - - Returns: - - None. Raises FileExistsError if the file does not exist.""" + Returns: + None + + Raises: + FileExistsError: if the file does not exist. + """ if not pathlib.Path(fault_summary).exists(): raise FileExistsError(f"{fault_summary=} does not exist!") @@ -120,13 +153,17 @@ def set_fault_summary(self, fault_summary: str) -> None: self.fault_summary = pathlib.Path(fault_summary).absolute() def set_fault_report(self, fault_report: str) -> None: - """Setter method for fault report. + """ + Setter method for fault report. + + Args: + fault_report (str): The new fault report filename. - - Parameters: - - fault_report (str): The new fault report filename. + Returns: + None - - Returns: - - None. Raises FileExistsError if the file does not exist.""" + Raises: + FileExistsError: If the file does not exist.""" if not pathlib.Path(fault_report).exists(): raise FileExistsError(f"{fault_report=} does not exist!") @@ -136,15 +173,20 @@ def set_fault_report(self, fault_report: str) -> None: def extract_summary_cells_from_row(self, row: int, *cols: int) -> list[str]: - """Returns a sequence of cells from a row of the - `self.fault_summary` **CSV** file. + """ + Returns a sequence of cells from a row of the + ``self.fault_summary`` **CSV** file. - - Parameters: - - row (int): the row number (1-based indexing). - - *cols (ints): the columns' numbers (1-based indexing). + Args: + row (int): the row number (1-based indexing). + *cols (ints): the columns' numbers (1-based indexing). - - Returns: - - list[str]: The cells of the fault summary.""" + Returns: + list[str]: The cells of the fault summary. + + Raises: + SystemExit: If a requested column or row is out-of-bounds. + """ with open(self.fault_summary) as csv_source: @@ -167,14 +209,12 @@ def extract_summary_cells_from_row(self, exit(1) def parse_fault_report(self) -> list[Fault]: - """Parses the `self.fault_report` **CSV** file and returns a dictionary - with its contents, ommiting any column if specified. - - - Parameters: - - None: + """ + Parses the ``self.fault_report`` **CSV** file and returns + a dictionary with its contents, ommiting any column if specified. - - Returns: - - list[Fault]: A list with synopys fault format objects. + Returns: + list[Fault]: A list with `Fault` entries. """ with open(self.fault_report) as csv_source: @@ -191,24 +231,31 @@ def parse_fault_report(self) -> list[Fault]: def extract_summary_coverage(summary: pathlib.Path, regexp: re.Pattern, group_index: int) -> float: - """Extracts the coverage percentage from the summary text file - file via multilined regex matching - - - Parameters: - - summary (pathlib.Path): The location of the summary.txt report - - regexp (re.Pattern): The regular expression to match the intended - coverage line. Note that it must have at least one capture group, - which should precicely hold the coverage percentage. - - group_index (int): The capture group index of the `regexp` that - holds the coverage percentage - - - Returns: - - float: The coverage percentage which was captured as float. + """ + Extracts the coverage percentage from the summary text file + file via multilined regex matching. + + Args: + summary (pathlib.Path): The location of the ``summary.txt`` report. + regexp (re.Pattern): The regular expression to match the intended + coverage line. Note that it must have at least + one capture group, which should precicely hold + the coverage percentage. + group_index (int): The capture group index of the regexp that + holds the coverage percentage. + + Returns: + float: The coverage percentage which was captured as float. + + Raises: + ValueError: If the provided `regexp` does not match anything. + SystemExit: If the conversion of the matched capture group to float + fails. """ with open(summary) as source: data = source.read() - match = re.search(regexp, data) + match = re.search(regexp, data, re.DOTALL | re.MULTILINE) if not match: raise ValueError(f"Unable to match coverage percentage with\ @@ -216,7 +263,14 @@ def extract_summary_coverage(summary: pathlib.Path, log.debug(f"Match {match=}. Groups {match.groups()}") - return float(match.group(group_index)) + try: + coverage = float(match.group(group_index)) + + except BaseException: + log.critical(f"Unable to cast {match.group(group_index)} to float") + exit(1) + + return coverage @staticmethod def compute_flist_coverage(fault_list: list[Fault], @@ -224,23 +278,29 @@ def compute_flist_coverage(fault_list: list[Fault], formula: str, precision: int = 4, status_attribute: str = "Status") -> float: - """Computes the test coverage value as described by `formula`, + """ + Computes the test coverage value as described by `formula`, which must be comprised of mathematical operations of Z01X fault classes (i.e., 2 letter strings). - - Parameters: - - fault_list (list(Fault)): A fault-list generated after parsing - the Z01X fault report csv file. - - sff_file (pathlib.Path): The fault format configuration file. - - formula (str): A formula which computes the coverage e.g., - `"DD/(NA + DA + DN + DD)"`. - - precision (int): the number of decimals to consider for - the coverage. Default is `4`. - - status_attribute (str): The attribute of the `Fault` object - which represents its Z01X fault status/class. Default is "Status". - - Returns: + Args: + fault_list (list[Fault]): A fault-list generated after parsing + the Z01X fault report csv file. + sff_file (pathlib.Path): The fault format configuration file. + formula (str): A formula which computes the coverage e.g., + ``"DD/(NA + DA + DN + DD)"``. + precision (int): the number of decimals to consider for + the coverage. Default is ``4``. + status_attribute (str): The attribute of the ``Fault`` object + which represents its Z01X fault status. + Default value is ``"Status"``. + Returns: float: The coverage value in [0.0, 1.0] i.e., the evaluated - `formula`. Not the precentage!""" + ``formula``. Not as a precentage! + Raises: + SystemExit: If the "StatusGroups" segment is not found in the + configuration .sff file. + """ # Gather fault statuses numbers. fault_statuses = dict() @@ -308,14 +368,17 @@ def __init__(self) -> "ZoixInvoker": @staticmethod def execute(instruction: str, timeout: float = None) -> tuple[str, str]: - """Executes a bash command-string instruction and returns - the `stdout` and `stderr` responses as a tuple. + """ + Executes a **bash** instruction and returns + the ``stdout`` and ``stderr`` responses as a tuple. + + Args: + instruction (str): The bash instruction to be executed. - - Parameters: - - instruction (str): The bash instruction to be executed. - - Returns: - - tuple(str, str): The stdout (index 0) and the stderr (index 1) - as strings.""" + Returns: + tuple(str, str): The stdout (index 0) and the stderr (index 1) + as strings. + """ log.debug(f"Executing {instruction}...") try: @@ -337,14 +400,20 @@ def execute(instruction: str, timeout: float = None) -> tuple[str, str]: return "TimeoutExpired", "TimeoutExpired" def compile_sources(self, *instructions: str) -> Compilation: - """Performs compilation of HDL files + """ + Performs compilation of HDL files - - Parameters: - - *instructions (str): A variadic number of bash shell instructions + Args: + *instructions (str): A variadic number of bash shell instructions Returns: - - Compilation: A status Enum to signify the success or failure + Compilation: A status Enum to signify the success or failure of the compilation. + + - ERROR: if any text was found in the ``stderr`` stream + during the execution of an instruction. + - SUCCESS: otherwise. + """ compilation_status = Compilation.SUCCESS @@ -361,31 +430,40 @@ def compile_sources(self, *instructions: str) -> Compilation: return compilation_status def logic_simulate(self, *instructions: str, **kwargs) -> LogicSimulation: - """Performs logic simulation of user-defined firmware and captures the + """ + Performs logic simulation of user-defined firmware and captures the test application time - - Parameters: - - *instructions (str): A variadic number of bash instructions - - **kwargs: User-defined options needed for the - evaluation of the result of the logic simulation. These are: - - timeout (float): A timeout in **seconds** to be - used for **each** of the executed lsim instruction. - - success_regexp (re.Pattern): A regular expression which is - used for matching in every line of the `stdout` stream to - signify the sucessfull completion of the logic simulation. - - tat_regexp_capture_group (int): An integer which corresponds - to the index of the TaT value on the custom regexp - (if provided). By default is 1, mapping to the default - `success_regexp` group. - - tat_value (list): An **empty** list to store the value of the - tat after being successfully matched with `success_regexp`. - Pass-by-reference style. - - Returns: - - LogicSimulation (enum): + + Args: + *instructions (str): A variadic number of bash instructions + **kwargs: User-defined options needed for the evaluation of the + result of the logic simulation. These options are: + + - **timeout** (float): A timeout in **seconds** to be used for + **each** of the executed logic simulation instructions. + + - **success_regexp** (re.Pattern): A regular expression used + for matching in every line of the ``stdout`` stream to mark + the successful completion of the logic simulation. + + - **tat_regexp_capture_group** (int): The index of the capture + group in the custom regular expression for the TaT value. + Default is 1, corresponding to the ``success_regexp`` group. + + - **tat_value** (list): An **empty** list to store the TaT + valueafter being successfully matched with + ``success_regexp``. The list is passed by reference and + the result will be appended to it. + + Returns: + LogicSimulation: A status Enum which is: + - TIMEOUT: if user defined timeout has been triggered. - - SIM_ERROR: if any text was found in the `stderr` stream - during the execution of an instruction. + - SIM_ERROR: if any text was found in the ``stderr`` stream + during the execution of an instruction. - SUCCESS: if the halting regexp matched text from the - `stdout` stream.""" + ``stdout`` stream. + """ timeout: float = kwargs.get("timeout", None) @@ -467,20 +545,23 @@ def logic_simulate(self, *instructions: str, **kwargs) -> LogicSimulation: def create_fcm_script(self, fcm_file: pathlib.Path, **fcm_options) -> pathlib.Path: - """Generates and returns a fault campaign manager TCL script based on + """ + Generates and returns a fault campaign manager TCL script based on user-defined settings from the setup file. - - Parameters: - - fcm_file (pathlib.Path): The full path (absolute or relative) \ - of the fcm script. - - **fcm_options: Keyword arguments where each key is an fcm \ - command and the corresponding value the flags or options (if any).\ - The commands should adhere to the supported commands documented \ - in the VCS-Z01X user guide. No sanitization checks are performed \ - by the method. + Args: + fcm_file (pathlib.Path): The full path (absolute or relative) + of the fcm script. + **fcm_options: Keyword arguments where each key is an fcm + command and the corresponding value the flags or + options (if any). The commands should adhere to the + supported commands documented in the VCS-Z01X user + guide. No sanitization checks are performed by the + method. - - Returns: - - pathlib.Path: The absolute path of the file.""" + Returns: + pathlib.Path: The absolute path of the generated file. + """ log.debug(f"Generating fault campaign manager script \ {fcm_file.absolute()}.") @@ -496,20 +577,24 @@ def create_fcm_script(self, return fcm_file.absolute() def fault_simulate(self, *instructions: str, **kwargs) -> FaultSimulation: - """Performs fault simulation of a user-defined firmware. + """ + Performs fault simulation of a user-defined firmware. - - Parameters: - - *instructions (str): A variadic number of shell instructions + Args: + *instructions (str): A variadic number of shell instructions to invoke Z01X. - - **kwargs: User-defined options for fault simulation control + **kwargs: User-defined options for fault simulation control. + - timeout (float): A timeout in **seconds** for each fsim - instruction. - - Returns: - - FaultSimulation (enum): A status Enum which is + instruction. + + Returns: + FaultSimulation: A status Enum which is: + - TIMEOUT: if the timeout kwarg was provided and some - instruction iolated it. - - FSIM_ERROR: if the `stderr` stream contains text during the - execution of an instruction. + instruction exceeded it. + - FSIM_ERROR: if the ``stderr`` stream contains text during the + execution of an instruction. - SUCCESS: if none of the above. """