#! py -3 # Requires Python 3.5 # Validates output from executable Java programs in "On Java 8." # Use chain of responsibility to successively try strategies until one matches from pathlib import Path import textwrap import re import sys import os import textwrap from collections import defaultdict WIDTH = 59 # Max line width #################### Phase 1: Basic formatting ##################### def adjust_lines(text): text = text.replace("\0", "NUL") lines = text.splitlines() slug = lines[0] if "(First and Last " in slug: num_of_lines = int(slug.split()[5]) adjusted = lines[:num_of_lines + 1] +\ ["...________...________...________...________..."] +\ lines[-num_of_lines:] return "\n".join(adjusted) elif "(First " in slug: num_of_lines = int(slug.split()[3]) adjusted = lines[:num_of_lines + 1] +\ [" ..."] return "\n".join(adjusted) else: return text def fill_to_width(text): result = "" for line in text.splitlines(): result += textwrap.fill(line, width = WIDTH) + "\n" return result.strip() def phase1(): """ (0) Do first/last lines before formatting to width (1) Combine output and error (if present) files (2) Format all output to width limit (3) Add closing '*/' """ for outfile in Path(".").rglob("*.out"): out_text = adjust_lines(outfile.read_text()) phase_1 = outfile.with_suffix(".p1") with phase_1.open('w') as phs1: phs1.write(fill_to_width(out_text) + "\n") errfile = outfile.with_suffix(".err") if errfile.exists(): phs1.write("___[ Error Output ]___\n") phs1.write(fill_to_width(errfile.read_text()) + "\n") phs1.write("*/\n") ########### Chain of Responsibility Match Finder ####################### def exact_match(text): return text memlocation = re.compile("@[0-9a-z]{5,7}") def ignore_memory_addresses(text): return memlocation.sub("", text) datestamp1 = re.compile( "(?:[MTWFS][a-z]{2} ){0,1}[JFMASOND][a-z]{2} \d{1,2} \d{2}:\d{2}:\d{2} [A-Z]{3} \d{4}") datestamp2 = re.compile( "[JFMASOND][a-z]{2} \d{1,2}, \d{4} \d{1,2}:\d{1,2}:\d{1,2} (:?AM|PM)") def ignore_dates(text): for pat in [ datestamp1, datestamp2 ]: text = pat.sub("", text) return text def ignore_digits(input_text): return re.sub("-?\d", "", input_text) def sort_lines(input_text): return "\n".join(sorted(input_text.splitlines())).strip() def sort_words(input_text): return "\n".join(sorted(input_text.split())).strip() def unique_lines(input_text): return "\n".join(sorted(list(set(input_text.splitlines())))) # Fairly extreme but will still reveal significant changes def unique_words(input_text): return "\n".join(sorted(set(input_text.split()))) # Fairly extreme but will still reveal significant changes word_only = re.compile("[A-Za-z]+") def words_only(input_text): return "\n".join( sorted([w for w in input_text.split() if word_only.fullmatch(w)])) def no_match(input_text): return True # Chain of responsibility: strategies = [ # Filter # Retain result # for rest of chain (exact_match, False), (ignore_dates, True), (ignore_memory_addresses, True), (sort_lines, False), (ignore_digits, False), (sort_words, False), (unique_lines, False), (unique_words, False), (words_only, False), (no_match, False), ] class Validator(defaultdict): # Map of lists compare_output = Path(".") / "compare_output.bat" def __init__(self): super().__init__(list) if Validator.compare_output.exists(): Validator.compare_output.unlink() for strategy, retain in strategies: strat_batch = Path(strategy.__name__ + ".bat") if strat_batch.exists(): strat_batch.unlink() def find_output_match(self, javafile, embedded_output, generated_output): for strategy, retain in strategies: filtered_embedded_output = strategy(embedded_output) filtered_generated_output = strategy(generated_output) if filtered_embedded_output == filtered_generated_output: strat_name = strategy.__name__ self[strat_name].append(str(javafile)) if strat_name is "exact_match": return tfile = javafile.with_suffix("." + strat_name) with Path(strat_name + ".bat").open('a') as strat_batch: strat_batch.write("subl " + str(tfile) + "\n") with Validator.compare_output.open('a') as batch: batch.write("subl " + str(tfile) + "\n") with tfile.open('w') as trace_file: trace_file.write(javafile.read_text() + "\n\n") trace_file.write("// === Actual ===\n\n") trace_file.write(str(generated_output)) return if retain: embedded_output = filtered_embedded_output generated_output = filtered_generated_output def display_results(self): log = open("verified_output.txt", 'w') for strategy, retain in strategies: key = strategy.__name__ if key is "exact_match": for java in self[key]: print(java) elif key in self: log.write("\n" + (" " + key + " ").center(45, "=") + "\n") for java in self[key]: log.write(java + "\n") log.close() if __name__ == '__main__': phase1() # Generates '.p1' files find_output = re.compile(r"/\* (Output:.*)\*/", re.DOTALL) validator = Validator() for outfile in Path(".").rglob("*.p1"): javafile = outfile.with_suffix(".java") if not javafile.exists(): print(str(outfile) + " has no javafile") sys.exit(1) javatext = javafile.read_text() if "/* Output:" not in javatext: print(str(outfile) + " has no /* Output:") sys.exit(1) validator.find_output_match(javafile, find_output.search(javatext).group(0).strip(), outfile.read_text().strip()) validator.display_results() os.system("more verified_output.txt")