OnJava8-Examples/Validate.py
2015-05-09 14:56:40 -07:00

385 lines
12 KiB
Python

#! py -3
"""
Run all (possible) java files and capture output and errors
"""
from pathlib import Path
import pprint
import textwrap
import os, sys, re
from contextlib import contextmanager
import argparse
import difflib
from collections import defaultdict
class CmdLine:
"""
Decorator to add a new command-line option
See http://www.artima.com/weblogs/viewpost.jsp?thread=240845
"""
parser = argparse.ArgumentParser()
commands = dict()
def __init__(self, letterFlag, wordFlag):
self.letterFlag = letterFlag
self.wordFlag = wordFlag
def __call__(self, func):
CmdLine.parser.add_argument("-" + self.letterFlag, "--" + self.wordFlag, action='store_true', help="{}".format(func.__doc__))
CmdLine.commands[self.wordFlag] = func # But what if func has arguments?
return func # No wrapping needed
@staticmethod
def run():
args = vars(CmdLine.parser.parse_args())
for wordFlag, func in CmdLine.commands.items():
if args[wordFlag]:
func()
else:
CmdLine.parser.print_help()
###############################################################################
# Create Powershell Script to run all programs and capture the output
###############################################################################
# Powershell: https://gist.github.com/diyan/2850866
# http://marxsoftware.blogspot.com/2008/02/windows-powershell-and-java.html
class Flags:
discard = ["{Requires:"]
def __init__(self, lines):
self.flaglines = []
for line in lines:
if line.startswith("//"):
self.flaglines.append(line)
else:
break # Only capture top block
self.flaglines = [line for line in self.flaglines if line.startswith("// {")]
self.flaglines = [line for line in self.flaglines if not [d for d in Flags.discard if d in line]]
self.flags = dict()
for flag in self.flaglines:
flag = flag[flag.index("{") + 1 : flag.index("}")].strip()
if ":" in flag:
fl, arg = flag.split(":")
fl = fl.strip()
arg = arg.strip()
self.flags[fl] = arg
else:
self.flags[flag] = None # Make an entry, but no arg
def __contains__(self, elt):
return elt in self.flags
def __repr__(self):
return pprint.pformat(self.flags)
def __len__(self):
return len(self.flaglines)
def keys(self):
return {key for key in self.flags.keys()}
def values(self):
return str(self.flags.values())
def jvm_args(self):
return self.flags["JVMArgs"] if "JVMArgs" in self.flags else ""
def cmd_args(self):
return " " + self.flags["Args"] if "Args" in self.flags else ""
class RunnableFile:
def __init__(self, path, body):
self.path = path
self.name = path.stem
self.relative = path.relative_to(RunFiles.base)
self.body = body
self.lines = body.splitlines()
self.flags = Flags(self.lines)
self._package = ""
for line in self.lines:
if line.startswith("package "):
self._package = line.split("package ")[1].strip()[:-1]
if self._package.replace('.', '/') not in self.lines[0]:
self._package = ""
def __contains__(self, elt):
return elt in self.flags
def __repr__(self):
return str(self.relative) #+ ": " + self.name
def package(self):
return self._package + '.' if self._package else ''
def rundir(self):
"Directory to change to before running the command"
return self.path.parent
def javaArguments(self):
return self.flags.jvm_args() + self.package() + self.name + self.flags.cmd_args()
def runCommand(self):
return "java " + self.javaArguments()
class RunFiles:
# RunFirst is temporary?
# Probably don't need ValidateByHand, can just use /* Output:
# Except ValidateByHand makes it clear this isn't a mistake. Leave it in.
not_runnable = ["RunByHand", "TimeOutDuringTesting", "CompileTimeError", 'TimeOut', 'RunFirst', "ValidateByHand"]
skip_dirs = ["gui", "swt"]
base = Path(".")
def __init__(self):
self.runFiles = []
for java in RunFiles.base.rglob("*.java"):
with java.open() as code:
body = code.read()
if "static void main(String[] args)" in body:
self.runFiles.append(RunnableFile(java, body))
allMains = set(self.runFiles)
self.runFiles = [f for f in self.runFiles if not [nr for nr in self.not_runnable if nr in f]]
self.runFiles = [f for f in self.runFiles if not [nd for nd in self.skip_dirs if nd in f.path.parts[0]]]
testedMains = set(self.runFiles)
self.untested = allMains.difference(testedMains)
with (RunFiles.base / "Untested.txt").open('w') as utf:
utf.write(pprint.pformat(self.untested))
def allFlagKeys(self):
flagkeys = set()
for f in [f for f in self.runFiles if f.flags]:
[flagkeys.add(key) for key in f.flags.keys()]
return flagkeys
def allFlagValues(self):
return [f.flags.values() for f in self.runFiles if f.flags if f.flags.values()]
def allFlags(self):
return [f.flags for f in self.runFiles if f.flags]
def runCommands(self):
return [f.runCommand() for f in self.runFiles]
def runData(self):
return "\n".join(["[{}] {}".format(f.rundir(), f.runCommand()) for f in self.runFiles])
def __iter__(self):
return iter(self.runFiles)
@contextmanager
def visitDir(d):
d = str(d)
old = os.getcwd()
os.chdir(d)
yield d
os.chdir(old)
@CmdLine("p", "powershell")
def createPowershellScript():
"""
Create Powershell Script to run all programs and capture the output
"""
assert Path.cwd().stem is "ExtractedExamples"
runFiles = RunFiles()
startDir = os.getcwd()
with open("runall.ps1", 'w') as ps:
ps.write('''Start-Process -FilePath "ant" -ArgumentList "build" -NoNewWindow -Wait \n\n''')
for rf in runFiles:
with visitDir(rf.rundir()):
pstext = """\
Start-Process
-FilePath "java.exe"
-ArgumentList "{}"
-NoNewWindow
-RedirectStandardOutput {}-output.txt
-RedirectStandardError {}-erroroutput.txt
""".format(rf.javaArguments(), rf.name, rf.name)
pstext = textwrap.dedent(pstext).replace('\n', ' ')
ps.write("cd {}\n".format(os.getcwd()))
ps.write(pstext + "\n")
ps.write('Write-Host [{}] {}\n'.format(rf.relative, rf.name))
ps.write("cd {}\n\n".format(startDir))
###############################################################################
# Attach Output to Java Files
###############################################################################
@CmdLine("d", "discover")
def discoverOutputTags():
"""
Discover 'Output:' tags
"""
all = set()
for jf in Path(".").rglob("*.java"):
with jf.open() as java:
lines = java.readlines()
for line in lines:
if "Output:" in line:
# print(lines[0].rstrip())
# print(line + "\n")
all.add(line)
continue
pprint.pprint(all)
# Tags:
# (XX% Match)
# (Sample)
# (First XX Lines)
def showAllSampleFiles():
for jf in Path(".").rglob("*.java"):
with jf.open() as java:
lines = java.readlines()
for line in lines:
if "(Sample)" in line:
print(lines[0].rstrip())
print(line + "\n")
continue
pprint.pprint(all)
class OutputTags:
tagfind = re.compile("\(.*?\)")
def __init__(self, javaFilePath):
self.javaFilePath = javaFilePath
self.has_output = False
self.tags = []
with self.javaFilePath.open() as code:
for line in code.readlines():
if "/* Output:" in line:
self.has_output = True
for tag in self.tagfind.findall(line):
self.tags.append(tag[1:-1])
if self.tags:
assert self.has_output
def __repr__(self):
return "{}\n{}\n".format(self.javaFilePath, pprint.pformat(self.tags))
def __bool__(self):
return bool(self.has_output and self.tags)
def __iter__(self):
return iter(self.tags)
class Result:
"""
Finds result files, compares to output stored in comments at ends of Java files.
If there's output, and no flag that says otherwise, add /* Output:
"""
oldOutput = re.compile("/* Output:.*?\n(.*)\n\*///:~(?s)")
@staticmethod
def create(javaFilePath):
"Factory: If the output files exist and are not both empty, produce Result object"
outfile = javaFilePath.with_name(javaFilePath.stem + "-output.txt")
errfile = javaFilePath.with_name(javaFilePath.stem + "-erroroutput.txt")
if outfile.exists():
assert errfile.exists()
else:
return None
if outfile.stat().st_size or errfile.stat().st_size:
return Result(javaFilePath, outfile, errfile)
else:
return None
def __init__(self, javaFilePath, outfile, errfile):
self.javaFilePath = javaFilePath
self.outFilePath = outfile
self.errFilePath = errfile
self.outFileSize = self.outFilePath.stat().st_size
self.errFileSize = self.errFilePath.stat().st_size
self.output_tags = OutputTags(javaFilePath)
self.old_output = self.__oldOutput()
self.new_output = self.__newOutput()
self.difference = difflib.SequenceMatcher(None, self.old_output, self.new_output).ratio()
def __oldOutput(self):
with self.javaFilePath.open() as code:
body = code.read()
result = self.oldOutput.findall(body)
return "\n".join(result).rstrip()
def __newOutput(self):
result =""
with self.outFilePath.open() as f:
result += f.read() + "\n"
with self.errFilePath.open() as f:
result += f.read()
return result.rstrip()
def __repr__(self):
def center(arg, sep="_"):
return "[ {} ]".format(str(arg)).center(50, sep) + "\n"
result = "\n" + center(self.javaFilePath, "=") +"\n"
result += "Output tag: {}\n".format(self.output_tag)
if self.old_output:
result += center("Previous Output")
result += self.old_output + "\n\n"
else:
result += center("No Previous Output")
result += center("New Output")
result += self.new_output + "\n\n"
result += center("Difference: {}".format(self.difference), '+') + "\n"
if self.difference == 1.0: return '.'
return result
def checkValidateByHands(self):
pass
@CmdLine("c", "cleanoutput")
def checkAndCleanResults():
"""
Clean output files by removing empty ones, and reporting file sizes
"""
print("checkAndCleanResults()")
assert Path.cwd().stem is "ExtractedExamples"
results = [r for r in [Result.create(jfp) for jfp in RunFiles.base.rglob("*.java")] if r]
pprint.pprint(results)
@CmdLine("s", "sample")
def insertSampleOutput():
"""
Show '(Sample)' tags
"""
results = [r for r in [Result.create(jfp) for jfp in RunFiles.base.rglob("*.java")] if r]
assert len(results), "Must run runall.ps1 first"
for r in results:
if r.output_tags:
print(r.output_tags)
tagd = defaultdict(list)
for tagged in [r for r in [Result.create(jfp) for jfp in RunFiles.base.rglob("*.java")] if r and r.output_tags]:
for tag in tagged.output_tags:
tagd[tag].append(str(tagged.javaFilePath))
pprint.pprint(tagd)
###############################################################################
# Main execution logic
###############################################################################
if __name__ == '__main__':
CmdLine.run()