#!/usr/bin/env python import re import os.path import sys import subprocess class TestConfiguration(object): ''' Represents a test to run. ''' def __init__(self): ''' Initialized from program arguments. Exists with code 2 and prints usage message on invalid arguments. ''' if len(sys.argv) < 3 or \ (not os.path.isfile(sys.argv[1])) or \ (not os.path.isfile(sys.argv[2])): print(sys.argv) print(''' Usage: {} Return: Returns the exit code of LFSCC. Echos LFSCC's stdout and stderr if the exit code is non-zero Dependencies: The PLF file may contain lines like ; Deps: [ ...] Dependencies are recursively resolved''') sys.exit(2) self.lfscc = sys.argv[1] self.path = sys.argv[2] self.dep_graph = DepGraph(self.path) class DepGraph(object): ''' Represents a dependency graph of LFSC input files ''' def __init__(self, root_path): ''' Creates a dependency graph rooted a `root_path`. Computes a root-last topological sort. Exits with exitcode 1 on cyclic dependencies''' # Root of the graph self._r = root_path # Nodes (paths) that have been visited self._visited = set() # Nodes (paths) that have been ordered self._ordered_set = set() # The order of nodes (paths). Root is last. self._ordered_paths = [] # Start DFS topo-order self._visit(root_path) def _visit(self, p): ''' Puts the descendents of p in the order, parent-last ''' node = TestFile(p) self._visited.add(p) for n in node.dep_paths: if n not in self._ordered_set: if n in self._visited: # Our child is is an ancestor our ours!? print("{} and {} are in a dependency cycle".format(p, n)) sys.exit(1) else: self._visit(n) self._ordered_paths.append(p) self._ordered_set.add(p) def getPathsInOrder(self): return self._ordered_paths class TestFile(object): ''' Represents a testable input file to LFSC ''' def __init__(self, path): ''' Read the file at `path` and determine its immediate dependencies''' self.path = path self._get_config_map() self.deps = self.config_map['deps'].split() if ('deps' in self.config_map) else [] self.dir = os.path.dirname(self.path) self.dep_paths = [os.path.join(self.dir, d) for d in self.deps] def _get_comment_lines(self): ''' Return an iterator over comment lines, ;'s included ''' with open(self.path, 'r') as test_file: return (line for line in test_file.readlines() if \ re.match(r'^\s*;.*$', line) is not None) def _get_config_map(self): ''' Populate self.config_map. Config variables are set using the syntax ; Var Name Spaces Okay: space separated values''' m = {} for l in self._get_comment_lines(): match = re.match(r'^.*;\s*(\w+(?:\s+\w+)*)\s*:(.*)$', l) if match is not None: m[match.group(1).replace(' ','').lower()] = match.group(2) self.config_map = m def main(): configuration = TestConfiguration() cmd = [configuration.lfscc] + configuration.dep_graph.getPathsInOrder() #print('Command: ', cmd) result = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) code = result.wait() if 0 != code: stdout = result.stdout.read() if stdout: print(stdout.decode()) return code if __name__ == '__main__': sys.exit(main())