Source code for smacha.util

from __future__ import unicode_literals

import os
import re
import difflib
import unittest2 as unittest
import stat

import smacha


[docs]class bcolors: """Colour terminal print strings. A helper class containing string codes that can be concatenated to strings in order to provide colour to terminal output. """ HEADER = '\033[95m' OKBLUE = '\033[94m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m'
[docs]class Tester(unittest.TestCase): """Utility class for running unit tests. The tests run by this class are performed by generating code using SMACHA scripts and templates and comparing the generated output code to the expected code. """ _write_output_files = False _output_py_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../test/smacha_generated_py') _output_yml_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../test/smacha_generated_scripts') _debug_level = 1 def __init__(self, *args, **kwargs): """ Constructor. :param script_dirs: A list of directories in which to search for SMACHA YAML scripts. :type script_dirs: list of str :param template_dirs: A list of directories in which to search for SMACHA templates. :type template_dirs: list of str :param include_introspection_server: A flag indicating whether a SMACH introspection server should be included in the generated output code. :type include_introspection_server: bool """ # Handle args if 'script_dirs' in kwargs.keys(): self._script_dirs = kwargs.pop('script_dirs') else: self._script_dirs = [] if 'template_dirs' in kwargs.keys(): self._template_dirs = kwargs.pop('template_dirs') else: self._template_dirs = [] if 'include_introspection_server' in kwargs.keys(): self._include_introspection_server = kwargs.pop('include_introspection_server') else: self._include_introspection_server = False super(Tester, self).__init__(*args, **kwargs) # Create the output directories if they don't exist if not os.path.exists(self._output_py_dir): os.makedirs(self._output_py_dir) if not os.path.exists(self._output_yml_dir): os.makedirs(self._output_yml_dir) # Load parser self._parser = smacha.Parser(script_dirs=self._script_dirs) # Load templater self._templater = smacha.Templater( self._template_dirs, include_introspection_server=self._include_introspection_server) # Load code generator self._generator = smacha.Generator(self._parser, self._templater, verbose=False)
[docs] def set_write_output_files(self, write_output_files): self._write_output_files = write_output_files
[docs] def set_output_py_dir(self, output_py_dir): self._output_py_dir = output_py_dir
[docs] def set_output_yml_dir(self, output_yml_dir): self._output_yml_dir = output_yml_dir
[docs] def set_debug_level(self, debug_level): self._debug_level = debug_level
def _generate(self, smacha_script_filename, output_file = None): """Generate smach code using smacha yaml script file and templates. :param smacha_script_filename: The SMACHA YAML script filename. :type smacha_script_filename: str :param output_file: An (optional) output filename for the generated output code. :type output_file: str or None """ # Load and parse SMACHA script script_str, _ = self._parser.load(smacha_script_filename) script = self._parser.parse(script_str) # Generate the SMACH code smach_code = self._generator.run(script) # Write the final output to a SMACH python file if self._write_output_files: if not output_file: output_file = os.path.splitext(os.path.basename(smacha_script_filename))[0] + '_generate_output.py' with open(os.path.join(self._output_py_dir, output_file), 'w') as smach_file: smach_file.write(smach_code) os.chmod(os.path.join(self._output_py_dir, output_file), stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP) return smach_code def _contain(self, smacha_script_filename, container_name, container_type, states, output_file = None): """Containerize a sequence of states in a script. :param smacha_script_filename: The SMACHA YAML script filename. :type smacha_script_filename: str :param container_name: The name of the container. :type container_name: str :param container_type: The type of container (e.g. 'StateMachine' or 'Concurrence'). :type container_type: str :param states: A sequence of states in the script file to containerize. :type states: list of str :param output_file: An (optional) output filename for the generated output code. :type output_file: str or None """ # Load and parse SMACHA script script_str, _ = self._parser.load(smacha_script_filename) script = self._parser.parse(script_str) # Use the contain method in the parser to do script conversion contained_script = self._parser.contain(script, container_name, container_type, states) # Dump the script to string contained_script_string = self._parser.dump(contained_script) # Write the final output to a SMACHA YAML file if self._write_output_files: if not output_file: output_file = os.path.splitext(os.path.basename(smacha_script_filename))[0] + '_contain_output.yml' with open(os.path.join(self._output_yml_dir, output_file), 'w') as contained_script_file: contained_script_file.write(contained_script_string) return contained_script_string def _extract(self, smacha_script_filename, container_state_name, sub_script_filename, output_sub_script_file=None, output_super_script_file=None): """Extract a container state in a script and export to sub-script and super-script. :param smacha_script_filename: The SMACHA YAML script filename. :type smacha_script_filename: str :param container_state_name: The name of the container state in the script. :type container_state_name: str :param sub_script_filename: Desired name for prospective sub-script file (this is the filename used in the super-script to refer to the sub-script). :type sub_script_filename: str :param output_sub_script_file: An (optional) output filename for the generated sub-script. :type output_sub_script_file: str or None :param output_super_script_file: An (optional) output filename for the generated super-script. :type output_super_script_file: str or None """ # Load and parse SMACHA script script_str, _ = self._parser.load(smacha_script_filename) script = self._parser.parse(script_str) # Use the extract() method in the parser to perform the extraction sub_script, super_script = self._parser.extract(script, container_state_name, sub_script_filename=sub_script_filename) # Dump the sub-script to a string extracted_sub_script_string = self._parser.dump([sub_script]) # Dump the super-script to a string extracted_super_script_string = self._parser.dump(super_script) # Write the final output to a SMACHA YAML files if self._write_output_files: if not output_sub_script_file: output_sub_script_file = os.path.splitext(os.path.basename(smacha_script_filename))[0] + '_sub_script_extract_output.yml' if not output_super_script_file: output_super_script_file = os.path.splitext(os.path.basename(smacha_script_filename))[0] + '_super_script_extract_output.yml' with open(os.path.join(self._output_yml_dir, output_sub_script_file), 'w') as extracted_sub_script_file: extracted_sub_script_file.write(extracted_sub_script_string) with open(os.path.join(self._output_yml_dir, output_super_script_file), 'w') as extracted_super_script_file: extracted_super_script_file.write(extracted_super_script_string) return extracted_sub_script_string, extracted_super_script_string def _strip_uuids(self, code): """Strip unique identifier strings (uuid's) from code string. :param code: Code string. :type code: str :return: Code string stripped of uuid's. :rtype: str """ # Remove uuid's from lambda callback defs in generated code # See: https://stackoverflow.com/questions/11384589/what-is-the-correct-regex-for-matching-values-generated-by-uuid-uuid4-hex code = re.sub(r'_[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}_', r'_', code) return code def _compare(self, code_a, code_b, file_a='code_a', file_b='code_b'): """Diff compare code_a with code_b. Run a diff comparison between two code strings and report whether or not they match. :param code_a: First code string. :type code_a: str :param code_b: Second code string. :type code_b: str :param file_a: Filename for first code string for reporting/debugging purposes. :type file_a: str :param file_b: Filename for second code string for reporting/debugging purposes. :type file_b: str :return: True if code strings match, False if not. :rtype: bool """ # Strip single-line comments code_a = re.sub('\s*#.*\n', '\n', code_a) code_b = re.sub('\s*#.*\n', '\n', code_b) # Squeeze whitespace after commas code_a = re.sub(',\s+', ',', code_a) code_b = re.sub(',\s+', ',', code_b) # Squeeze whitespace before and after colons code_a = re.sub('\s*\:\s*', ':', code_a) code_b = re.sub('\s*\:\s*', ':', code_b) # Strip (python agnostic!) whitespace from both code strings and convert to lists code_a_stripped = [line for line in code_a.strip().splitlines() if line != '' and re.match('^\s+$', line) is None] code_b_stripped = [line for line in code_b.strip().splitlines() if line != '' and re.match('^\s+$', line) is None] # Strip trailing whitespace from each line code_a_stripped = [line.rstrip() for line in code_a_stripped] code_b_stripped = [line.rstrip() for line in code_b_stripped] if self._debug_level > 1: print('\n' + file_a + ':\n') print(code_a_stripped) print('\n' + file_b + ':\n') print(code_b_stripped) # Use difflib to compare same = True for line in difflib.unified_diff(code_a_stripped, code_b_stripped, fromfile=file_a, tofile=file_b, lineterm=''): if line: if self._debug_level > 0: print(line) same = False return same