#!/usr/bin/python3
import os, os.path, tempfile, re, copy, subprocess, locale
titlePattern = re.compile(r"^== (.*) ==$")
varPattern = re.compile(r"^(.*) == (.*)$")
rulePattern = re.compile(r"^----.*$")
commentPattern = re.compile(r"^\s*#.*$")
codingPattern = re.compile(r"^# -\*- coding: (.*) -\*-$")
locale.setlocale(locale.LC_ALL, '')
codeset = locale.nl_langinfo(locale.CODESET) # coding used by the user's locale
def notAtest(f):
"""
consider some filenames as not useable to perfom tests.
@param f a filename
@return True if f seems to be the name of a backup file
"""
return re.match(r".*~", f) or re.match(r".*\.bak", f)
def mkTestList(lines, cur, title, coding="utf-8"):
"""
makes a list of objects for unitary tests of Wims modtool primitives
@param lines a list of lines coming from a file
@param cur a pointer on the current line to be read
@param title a title which has been read from a previous line
@param coding the coding of the .proc file used for tests
@return a list of Test objects
"""
result = []
proclines = []
variables = {}
currentVar = None
while cur < len(lines):
l = lines[cur].decode(coding).replace('\n', '')
t = titlePattern.match(l)
v = varPattern.match(l)
r = rulePattern.match(l)
c = commentPattern.match(l)
if t:
result.append(Test(title, proclines, variables, coding))
title = t.group(1)
proclines = []
variables = {}
currentVar = None
elif v:
currentVar = v.group(1)
variables[currentVar] = v.group(2)
elif currentVar is not None and len(l) > 0:
variables[currentVar] += '\n' + l
elif not r and not c:
# not a title, nor variable, nor a ruler, nor a comment:
# these are commands
proclines.append(lines[cur].strip())
cur = cur + 1
result.append(Test(title, proclines, variables, coding))
return result
class Test:
"""
This class implements data for a unitary test of Wims.
Each test has a title, a set of lines to be pasted into
a .proc file for Wims, and a set assertions about
variables and the values which they should be assigned
upon completion of the test.
"""
def __init__(self, title, proclines, variables, coding="utf-8"):
"""
The constructor.
@param title a title for the unitary test
@param proclines a list of lines to be processed by Wims in a
.proc file.
@param variables a dictionary associating variable names with
values they should have when the test finishes.
@param coding the coding used for the .proc file
"""
self.title = "" + title
self.proclines = copy.copy(proclines)
self.variables = copy.copy(variables)
self.coding = coding
self.gotResults = {}
self.errors = []
self.success = None
def __str__(self):
"""
Conversion to a str.
"""
result = "Test {title=«%s»," % (self.title,)
result += " coding=%s," % self.coding
result += " proclines=%s," % self.proclines
result += " variables=%s" % self.variables
result += "}"
return result
def __repr__(self):
"""
The representation is the same as the conversion to a str.
"""
return self.__str__()
def gotError(self):
"""
checks whether some error occurred
@return True if an error came out of the wims subprocess
"""
return len(self.errors) > 1 or (len(self.errors) == 1 and
self.errors[0] != "")
def run(self, path, fName="test.proc"):
"""
runs the unitary tests, and gather the results in
self.gotResults, self.errors and self.success
@param path a path to the .proc file
@param fName the name of the .proc file, defaults to "test.proc"
"""
self.success = True
self.failedVars = []
cmd = "./wims test %s %s '%s'" % (path,
fName,
' '.join([v for v in self.variables]))
with open(os.path.join(path, fName), "wb") as outfile:
for l in self.proclines:
outfile.write(l + b'\n')
outfile.close()
p = subprocess.Popen(cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = p.communicate()
currentVar = None
for l in out.decode(self.coding).split("\n"):
v = varPattern.match(l)
if v:
currentVar = v.group(1)
self.gotResults[currentVar] = v.group(2)
else:
if currentVar is not None and len(l) > 0:
# add an other line to the current variable
self.gotResults[currentVar] += '\n' + l
self.errors = err.decode(codeset).split("\n")
if self.gotError():
self.success = False
for v in self.variables:
if v not in self.gotResults or self.variables[v] != self.gotResults[v]:
self.failedVars.append(v)
self.success = False
def showResult(self, msg="", tmpDir="/tmp"):
"""
runs the test if self.success is undefined, and
pretty-prints the result
@param msg a message string to prepend to the result (for instance,
a test number)
@param tmpDir a directory to run the tests
"""
if self.success is None:
self.run(tmpDir)
if self.success:
print("[%s] %s: OK." % (msg, self.title))
else:
hrule = "[%s] ========< %s >==========" % (msg, self.title)
print(hrule)
for l in self.proclines:
print(l)
hrule = "=" * len(hrule)
print(hrule)
if self.gotError():
print ("ERROR: %s" % self.errors)
for v in self.failedVars:
if v in self.gotResults:
print("FAILED for variable %s, expected: «%s»; got: «%s»"
% (v, self.variables[v], self.gotResults[v]))
else:
print("FAILED for variable %s, expected: «%s»; got nothing"
% (v, self.variables[v]))
for v in self.variables:
if v not in self.failedVars:
print("OK for variable %s, expected: «%s»; got: «%s»"
% (v, self.variables[v], self.gotResults[v]))
print(hrule)
if __name__ == "__main__":
print ("Test suite for Wims.")
for dirpath, dirnames, filenames in os.walk("test"):
for f in filenames:
if notAtest(f):
continue
lines = open(os.path.join(dirpath, f), "rb").readlines()
cur = 0
t = titlePattern.match(lines[cur].decode("utf-8"))
coding = "utf-8" # default
while cur < len(lines) and not t:
# take in account coding declaration
c = codingPattern.match(lines[cur].decode("utf-8"))
if c:
coding = c.group(1)
cur += 1
t = titlePattern.match(lines[cur].decode(coding))
if cur < len(lines):
print("Running tests from {}/{} (coding: {})".format(dirpath, f, coding))
title = t.group(1)
tests = mkTestList(lines, cur, title, coding=coding)
with tempfile.TemporaryDirectory(prefix='wimstest-') as tmpDir:
i = 1
ok = 0
nok = 0
for t in tests:
t.showResult(tmpDir=tmpDir, msg=i)
i += 1
if t.success:
ok += 1
else:
nok += 1
print ("Ran {} tests; {} OK, {} WRONG.".format(i - 1, ok, nok))
else:
print("Error: the first line of the file should contain some == title ==")
os.exit(1)