diff --git a/bin/doxec b/bin/doxec index 99711a6757b61bdb70d7667dfe06697ee622a395..8eb8023d61024f0c6398ae859e236517e6440e27 100755 --- a/bin/doxec +++ b/bin/doxec @@ -35,16 +35,18 @@ if __name__ == "__main__": print("Doxec -- Copyright (c) 2017 Frank Sauerburger") + # Loop over documents for doc_path in args.documents: doc = doxec.Document(doc_path, syntax=parser) monitor = doxec.Monitor(doc_path, short=args.short) doc.run(monitor=monitor) - + # Print summary print("-"*80) color = "\033[31m" if monitor.fail_count > 0 else "\033[32m" print(color + "Failed: %5d\033[0m" % monitor.fail_count) print("Total: %5d" % monitor.total_count) + # Return code equals number of failed operations sys.exit(monitor.fail_count) diff --git a/doxec/__init__.py b/doxec/__init__.py index 1792ae586beb0f6b6b35ad09580072710257af8c..fb03d29d2167673f5aef9888335c7616c71db713 100644 --- a/doxec/__init__.py +++ b/doxec/__init__.py @@ -104,19 +104,37 @@ class OpConsole(Operation): def execute(self, log=None): if log is None: + # Set pseudo logger. log = lambda x: None + # Select lines with $-sign and trim it. script = [l[1:].lstrip() for l in self.content if l.startswith("$")] + + # Loop over commands for command in script: + # Call logger. log(["$ %s" % command]) + + # Execute in a new bash shell. job = subprocess.Popen("/bin/bash", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # Pass script as standard input to bash. (stdoutdata, stderrdata) = job.communicate(command.encode('utf8')) + + # Decode standard out and split into lines. output = stdoutdata.decode('utf8') output = re.split(r'\r?\n', output) + + # Remove trailing empty line. if len(output) > 0 and output[-1] == '': del output[-1] + + # Pass standard output of the command to logger. log(output) + + # Perform test, whether command failed. Continue loop on + # success. if job.returncode != 0: raise TestException("Script failed with return code %d:" % job.returncode, stdoutdata.decode('utf8'), stderrdata.decode('utf8')) @@ -134,31 +152,54 @@ class OpConsoleOutput(Operation): def execute(self, log=None): if log is None: + # Set pseudo logger. log = lambda x: None - commands = [] # items are (command, [output lines]) + # Split the operation body into a list of tuple. Each tuple consists + # of a command and a list of expected output lines. + commands = [] for line in self.content: if line.startswith("$"): + # Append a new tuple, with a blank list of expected output. commands.append((line[1:].lstrip(), [])) elif len(commands) == 0: - # no command yet + # There hasn't been a command, ignore expected output lines. continue else: + # Append output line to list of expected output for the most + # recent command. commands[-1][1].append(line) + # Loop over commands and their expected output. for command, lines in commands: + # Call logger. log(["$ %s" % command]) + + # Execute in a new bash shell. job = subprocess.Popen("/bin/bash", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # Pass script as standard input to bash. (stdoutdata, stderrdata) = job.communicate(command.encode('utf8')) - if job.returncode != 0: - raise TestException("Script failed with return code %d:" % job.returncode, - stdoutdata.decode('utf8'), stderrdata.decode('utf8')) + + # Pass script as standard input to bash. output = stdoutdata.decode('utf8') output = re.split(r'\r?\n', output) + + # Remove trailing empty line. if len(output) > 0 and output[-1] == '': del output[-1] + + # Pass standard output of the command to logger. log(output) + + # Perform test, whether command failed. Continue on success. + if job.returncode != 0: + raise TestException("Script failed with return code %d:" % job.returncode, + stdoutdata.decode('utf8'), stderrdata.decode('utf8')) + + # Perform test, whether the standard output matched the expected + # output. Continue loop on success. if lines != output: first_offending = None for l, o in zip(lines, output): @@ -359,7 +400,7 @@ class Document: fail_count = 0 total_operations = 0 - # set defaults + # set defaults / pseudo monitors before_method = lambda l, o: None log_method = lambda ls: None after_method = lambda e=None: None @@ -370,15 +411,21 @@ class Document: log_method = monitor.log after_method = monitor.after_execute + # Loop over operations in this document. for line, op in self.operations: + # Call (pseudo) monitor. before_method(line, op) total_operations += 1 try: + # Pass monitors log method to operation, to log standard + # output. op.execute(log=log_method) except TestException as e: + # Notify (pseudo) monitor about failure. after_method(e) fail_count += 1 else: + # Notify (pseudo) monitor about success. after_method() return (fail_count, total_operations)