Subversion Repositories wimsdev

Rev

Rev 8192 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. #!/usr/bin/python3
  2.  
  3. import os, os.path, tempfile, re, copy, subprocess, locale
  4.  
  5. titlePattern = re.compile(r"^== (.*) ==$")
  6. varPattern = re.compile(r"^(.*) == (.*)$")
  7. rulePattern = re.compile(r"^----.*$")
  8. commentPattern = re.compile(r"^\s*#.*$")
  9. codingPattern = re.compile(r"^# -\*- coding: (.*) -\*-$")
  10.  
  11. locale.setlocale(locale.LC_ALL, '')
  12. codeset = locale.nl_langinfo(locale.CODESET)  # coding used by the user's locale
  13.  
  14.  
  15. def notAtest(f):
  16.     """
  17.    consider some filenames as not useable to perfom tests.
  18.    @param f a filename
  19.    @return True if f seems to be the name of a backup file
  20.    """
  21.     return re.match(r".*~", f) or re.match(r".*\.bak", f)
  22.  
  23.  
  24. def mkTestList(lines, cur, title, coding="utf-8"):
  25.     """
  26.    makes a list of objects for unitary tests of Wims modtool primitives
  27.    @param lines a list of lines coming from a file
  28.    @param cur a pointer on the current line to be read
  29.    @param title a title which has been read from a previous line
  30.    @param coding the coding of the .proc file used for tests
  31.    @return a list of Test objects
  32.    """
  33.     result = []
  34.     proclines = []
  35.     variables = {}
  36.     currentVar = None
  37.     while cur < len(lines):
  38.         l = lines[cur].decode(coding).replace('\n', '')
  39.         t = titlePattern.match(l)
  40.         v = varPattern.match(l)
  41.         r = rulePattern.match(l)
  42.         c = commentPattern.match(l)
  43.         if t:
  44.             result.append(Test(title, proclines, variables, coding))
  45.             title = t.group(1)
  46.             proclines = []
  47.             variables = {}
  48.             currentVar = None
  49.         elif v:
  50.             currentVar = v.group(1)
  51.             variables[currentVar] = v.group(2)
  52.         elif currentVar is not None and len(l) > 0:
  53.             variables[currentVar] += '\n' + l
  54.         elif not r and not c:
  55.             # not a title, nor variable, nor a ruler, nor a comment:
  56.             # these are commands
  57.             proclines.append(lines[cur].strip())
  58.         cur = cur + 1
  59.     result.append(Test(title, proclines, variables, coding))
  60.     return result
  61.  
  62.  
  63. class Test:
  64.     """
  65.    This class implements data for a unitary test of Wims.
  66.    Each test has a title, a set of lines to be pasted into
  67.    a .proc file for Wims, and a set assertions about
  68.    variables and the values which they should be assigned
  69.    upon completion of the test.
  70.    """
  71.     def __init__(self, title, proclines, variables, coding="utf-8"):
  72.         """
  73.        The constructor.
  74.        @param title a title for the unitary test
  75.        @param proclines a list of lines to be processed by Wims in a
  76.        .proc file.
  77.        @param variables a dictionary associating variable names with
  78.        values they should have when the test finishes.
  79.        @param coding the coding used for the .proc file
  80.        """
  81.         self.title = "" + title
  82.         self.proclines = copy.copy(proclines)
  83.         self.variables = copy.copy(variables)
  84.         self.coding = coding
  85.         self.gotResults = {}
  86.         self.errors = []
  87.         self.success = None
  88.  
  89.     def __str__(self):
  90.         """
  91.        Conversion to a str.
  92.        """
  93.         result = "Test {title=«%s»," % (self.title,)
  94.         result += " coding=%s," % self.coding
  95.         result += " proclines=%s," % self.proclines
  96.         result += " variables=%s" % self.variables
  97.         result += "}"
  98.         return result
  99.  
  100.     def __repr__(self):
  101.         """
  102.        The representation is the same as the conversion to a str.
  103.        """
  104.         return self.__str__()
  105.  
  106.     def gotError(self):
  107.         """
  108.        checks whether some error occurred
  109.        @return True if an error came out of the wims subprocess
  110.        """
  111.         return len(self.errors) > 1 or (len(self.errors) == 1 and
  112.                                         self.errors[0] != "")
  113.  
  114.     def run(self, path, fName="test.proc"):
  115.         """
  116.        runs the unitary tests, and gather the results in
  117.        self.gotResults, self.errors and self.success
  118.        @param path a path to the .proc file
  119.        @param fName the name of the .proc file, defaults to "test.proc"
  120.        """
  121.         self.success = True
  122.         self.failedVars = []
  123.         cmd = "./wims test %s %s '%s'" % (path,
  124.                                           fName,
  125.                                           ' '.join([v for v in self.variables]))
  126.         with open(os.path.join(path, fName), "wb") as outfile:
  127.             for l in self.proclines:
  128.                 outfile.write(l + b'\n')
  129.             outfile.close()
  130.             p = subprocess.Popen(cmd,
  131.                                  shell=True,
  132.                                  stdout=subprocess.PIPE,
  133.                                  stderr=subprocess.PIPE)
  134.             out, err = p.communicate()
  135.             currentVar = None
  136.             for l in out.decode(self.coding).split("\n"):
  137.                 v = varPattern.match(l)
  138.                 if v:
  139.                     currentVar = v.group(1)
  140.                     self.gotResults[currentVar] = v.group(2)
  141.                 else:
  142.                     if currentVar is not None and len(l) > 0:
  143.                         # add an other line to the current variable
  144.                         self.gotResults[currentVar] += '\n' + l
  145.             self.errors = err.decode(codeset).split("\n")
  146.             if self.gotError():
  147.                 self.success = False
  148.             for v in self.variables:
  149.                 if v not in self.gotResults or self.variables[v] != self.gotResults[v]:
  150.                     self.failedVars.append(v)
  151.                     self.success = False
  152.  
  153.     def showResult(self, msg="", tmpDir="/tmp"):
  154.         """
  155.        runs the test if self.success is undefined, and
  156.        pretty-prints the result
  157.        @param msg a message string to prepend to the result (for instance,
  158.        a test number)
  159.        @param tmpDir a directory to run the tests
  160.        """
  161.         if self.success is None:
  162.             self.run(tmpDir)
  163.         if self.success:
  164.             print("[%s] %s: OK." % (msg, self.title))
  165.         else:
  166.             hrule = "[%s] ========< %s >==========" % (msg, self.title)
  167.             print(hrule)
  168.             for l in self.proclines:
  169.                 print(l)
  170.             hrule = "=" * len(hrule)
  171.             print(hrule)
  172.             if self.gotError():
  173.                 print ("ERROR: %s" % self.errors)
  174.             for v in self.failedVars:
  175.                 if v in self.gotResults:
  176.                     print("FAILED for variable %s, expected: «%s»; got: «%s»"
  177.                           % (v, self.variables[v], self.gotResults[v]))
  178.                 else:
  179.                     print("FAILED for variable %s, expected: «%s»; got nothing"
  180.                           % (v, self.variables[v]))
  181.                 for v in self.variables:
  182.                     if v not in self.failedVars:
  183.                         print("OK for variable %s,  expected: «%s»; got: «%s»"
  184.                               % (v, self.variables[v], self.gotResults[v]))
  185.                 print(hrule)
  186.  
  187.  
  188. if __name__ == "__main__":
  189.     print ("Test suite for Wims.")
  190.     for dirpath, dirnames, filenames in os.walk("test"):
  191.         for f in filenames:
  192.             if notAtest(f):
  193.                 continue
  194.             lines = open(os.path.join(dirpath, f), "rb").readlines()
  195.             cur = 0
  196.             t = titlePattern.match(lines[cur].decode("utf-8"))
  197.             coding = "utf-8"  # default
  198.             while cur < len(lines) and not t:
  199.                 # take in account coding declaration
  200.                 c = codingPattern.match(lines[cur].decode("utf-8"))
  201.                 if c:
  202.                     coding = c.group(1)
  203.                 cur += 1
  204.                 t = titlePattern.match(lines[cur].decode(coding))
  205.             if cur < len(lines):
  206.                 print("Running tests from {}/{} (coding: {})".format(dirpath, f, coding))
  207.                 title = t.group(1)
  208.                 tests = mkTestList(lines, cur, title, coding=coding)
  209.                 with tempfile.TemporaryDirectory(prefix='wimstest-') as tmpDir:
  210.                     i = 1
  211.                     ok = 0
  212.                     nok = 0
  213.                     for t in tests:
  214.                         t.showResult(tmpDir=tmpDir, msg=i)
  215.                         i += 1
  216.                         if t.success:
  217.                             ok += 1
  218.                         else:
  219.                             nok += 1
  220.                 print ("Ran {} tests; {} OK, {} WRONG.".format(i - 1, ok, nok))
  221.             else:
  222.                 print("Error: the first line of the file should contain some == title ==")
  223.                 os.exit(1)
  224.