Rev 8192 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
8192 | georgesk | 1 | #!/usr/bin/python3 |
2 | |||
3 | import os, os.path, tempfile, re, copy, subprocess, locale |
||
4 | |||
10454 | obado | 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: (.*) -\*-$") |
||
8192 | georgesk | 10 | |
11 | locale.setlocale(locale.LC_ALL, '') |
||
10454 | obado | 12 | codeset = locale.nl_langinfo(locale.CODESET) # coding used by the user's locale |
8192 | georgesk | 13 | |
10454 | obado | 14 | |
8192 | georgesk | 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 | """ |
||
10454 | obado | 21 | return re.match(r".*~", f) or re.match(r".*\.bak", f) |
8192 | georgesk | 22 | |
10454 | obado | 23 | |
8192 | georgesk | 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 | """ |
||
10454 | obado | 33 | result = [] |
34 | proclines = [] |
||
35 | variables = {} |
||
36 | currentVar = None |
||
8192 | georgesk | 37 | while cur < len(lines): |
10454 | obado | 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) |
||
8192 | georgesk | 43 | if t: |
44 | result.append(Test(title, proclines, variables, coding)) |
||
10454 | obado | 45 | title = t.group(1) |
46 | proclines = [] |
||
47 | variables = {} |
||
48 | currentVar = None |
||
8192 | georgesk | 49 | elif v: |
10454 | obado | 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: |
||
8192 | georgesk | 55 | # not a title, nor variable, nor a ruler, nor a comment: |
56 | # these are commands |
||
57 | proclines.append(lines[cur].strip()) |
||
10454 | obado | 58 | cur = cur + 1 |
8192 | georgesk | 59 | result.append(Test(title, proclines, variables, coding)) |
60 | return result |
||
61 | |||
10454 | obado | 62 | |
8192 | georgesk | 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 | """ |
||
10454 | obado | 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 |
||
8192 | georgesk | 88 | |
89 | def __str__(self): |
||
90 | """ |
||
91 | Conversion to a str. |
||
92 | """ |
||
10454 | obado | 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 += "}" |
||
8192 | georgesk | 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__() |
||
10454 | obado | 105 | |
8192 | georgesk | 106 | def gotError(self): |
107 | """ |
||
108 | checks whether some error occurred |
||
109 | @return True if an error came out of the wims subprocess |
||
110 | """ |
||
10454 | obado | 111 | return len(self.errors) > 1 or (len(self.errors) == 1 and |
112 | self.errors[0] != "") |
||
8192 | georgesk | 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 | """ |
||
10454 | obado | 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: |
||
8192 | georgesk | 127 | for l in self.proclines: |
10454 | obado | 128 | outfile.write(l + b'\n') |
8192 | georgesk | 129 | outfile.close() |
10454 | obado | 130 | p = subprocess.Popen(cmd, |
131 | shell=True, |
||
132 | stdout=subprocess.PIPE, |
||
133 | stderr=subprocess.PIPE) |
||
8192 | georgesk | 134 | out, err = p.communicate() |
10454 | obado | 135 | currentVar = None |
8192 | georgesk | 136 | for l in out.decode(self.coding).split("\n"): |
10454 | obado | 137 | v = varPattern.match(l) |
8192 | georgesk | 138 | if v: |
10454 | obado | 139 | currentVar = v.group(1) |
140 | self.gotResults[currentVar] = v.group(2) |
||
8192 | georgesk | 141 | else: |
10454 | obado | 142 | if currentVar is not None and len(l) > 0: |
8192 | georgesk | 143 | # add an other line to the current variable |
10454 | obado | 144 | self.gotResults[currentVar] += '\n' + l |
145 | self.errors = err.decode(codeset).split("\n") |
||
8192 | georgesk | 146 | if self.gotError(): |
10454 | obado | 147 | self.success = False |
8192 | georgesk | 148 | for v in self.variables: |
10454 | obado | 149 | if v not in self.gotResults or self.variables[v] != self.gotResults[v]: |
8192 | georgesk | 150 | self.failedVars.append(v) |
10454 | obado | 151 | self.success = False |
8192 | georgesk | 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 | """ |
||
10454 | obado | 161 | if self.success is None: |
8192 | georgesk | 162 | self.run(tmpDir) |
163 | if self.success: |
||
10454 | obado | 164 | print("[%s] %s: OK." % (msg, self.title)) |
8192 | georgesk | 165 | else: |
10454 | obado | 166 | hrule = "[%s] ========< %s >==========" % (msg, self.title) |
8192 | georgesk | 167 | print(hrule) |
168 | for l in self.proclines: |
||
169 | print(l) |
||
10454 | obado | 170 | hrule = "=" * len(hrule) |
8192 | georgesk | 171 | print(hrule) |
172 | if self.gotError(): |
||
10454 | obado | 173 | print ("ERROR: %s" % self.errors) |
8192 | georgesk | 174 | for v in self.failedVars: |
175 | if v in self.gotResults: |
||
10454 | obado | 176 | print("FAILED for variable %s, expected: «%s»; got: «%s»" |
177 | % (v, self.variables[v], self.gotResults[v])) |
||
8192 | georgesk | 178 | else: |
10454 | obado | 179 | print("FAILED for variable %s, expected: «%s»; got nothing" |
180 | % (v, self.variables[v])) |
||
8192 | georgesk | 181 | for v in self.variables: |
182 | if v not in self.failedVars: |
||
10454 | obado | 183 | print("OK for variable %s, expected: «%s»; got: «%s»" |
184 | % (v, self.variables[v], self.gotResults[v])) |
||
8192 | georgesk | 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 |
||
10454 | obado | 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 |
||
8192 | georgesk | 198 | while cur < len(lines) and not t: |
199 | # take in account coding declaration |
||
10454 | obado | 200 | c = codingPattern.match(lines[cur].decode("utf-8")) |
8192 | georgesk | 201 | if c: |
10454 | obado | 202 | coding = c.group(1) |
203 | cur += 1 |
||
204 | t = titlePattern.match(lines[cur].decode(coding)) |
||
8192 | georgesk | 205 | if cur < len(lines): |
206 | print("Running tests from {}/{} (coding: {})".format(dirpath, f, coding)) |
||
10454 | obado | 207 | title = t.group(1) |
208 | tests = mkTestList(lines, cur, title, coding=coding) |
||
8192 | georgesk | 209 | with tempfile.TemporaryDirectory(prefix='wimstest-') as tmpDir: |
10454 | obado | 210 | i = 1 |
211 | ok = 0 |
||
212 | nok = 0 |
||
8192 | georgesk | 213 | for t in tests: |
214 | t.showResult(tmpDir=tmpDir, msg=i) |
||
215 | i += 1 |
||
216 | if t.success: |
||
10454 | obado | 217 | ok += 1 |
8192 | georgesk | 218 | else: |
10454 | obado | 219 | nok += 1 |
220 | print ("Ran {} tests; {} OK, {} WRONG.".format(i - 1, ok, nok)) |
||
8192 | georgesk | 221 | else: |
10454 | obado | 222 | print("Error: the first line of the file should contain some == title ==") |
8192 | georgesk | 223 | os.exit(1) |