10790
by The Widelands Bunnybot
Python files require python3 explicitly (CB #4529 / GH #6187) |
1 |
#!/usr/bin/env python3
|
6689.1.1
by Holger Rapp
First unfinished version of a regression test suite. |
2 |
# encoding: utf-8
|
3 |
||
6689.1.4
by Holger Rapp
All tests now run and pass on my system. |
4 |
from glob import glob |
5 |
import argparse |
|
6 |
import os |
|
7 |
import re |
|
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
8 |
import shutil |
9 |
import subprocess |
|
6803
by Holger Rapp
regression_test.py returns an error to the system on failure. |
10 |
import sys |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
11 |
import tempfile |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
12 |
import concurrent.futures as cf |
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
13 |
import time |
14 |
import datetime |
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
15 |
import multiprocessing |
16 |
||
17 |
get_time = time.monotonic |
|
18 |
||
19 |
# psutil is not part of the standard library.
|
|
20 |
# It's only used to get the amount of memory for -j 0
|
|
7945.1.1
by Tino
allow regression tests to be run with python 3 |
21 |
try: |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
22 |
import psutil |
23 |
except: |
|
24 |
psutil = None |
|
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
25 |
|
7293.1.52
by Holger Rapp
Adds a --datadir_for_testing flag which works as an extra --datadir flag. That |
26 |
def datadir(): |
27 |
return os.path.join(os.path.dirname(__file__), "data") |
|
28 |
||
29 |
def datadir_for_testing(): |
|
7782.3.1
by fios at foramnagaidhlig
Fixed datadir_for_testing in regression_test.py. |
30 |
return os.path.relpath(".", os.path.dirname(__file__)) |
7293.1.52
by Holger Rapp
Adds a --datadir_for_testing flag which works as an extra --datadir flag. That |
31 |
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
32 |
ansi_colors = { |
33 |
'black': '\033[30m', |
|
34 |
'red': '\033[31m', |
|
35 |
'green': '\033[32m', |
|
36 |
'yellow': '\033[33m', |
|
37 |
'blue': '\033[34m', |
|
38 |
'purple': '\033[35m', |
|
39 |
'cyan': '\033[36m', |
|
40 |
'white': '\033[37m', |
|
41 |
'default': '\033[39m' |
|
42 |
}
|
|
43 |
||
44 |
info_color = 'cyan' |
|
45 |
success_color = 'green' |
|
46 |
warning_color = 'yellow' |
|
47 |
error_color = 'red' |
|
48 |
separator_color = 'purple' |
|
49 |
||
50 |
log_colors = { |
|
51 |
# order in decreasing priority
|
|
52 |
'ERROR': error_color, |
|
53 |
'WARNING': warning_color, |
|
54 |
}
|
|
55 |
||
56 |
use_colors = True |
|
57 |
||
58 |
def colorize(text, color): |
|
59 |
if not use_colors: |
|
60 |
return text |
|
61 |
return f'{ansi_colors[color]}{text}{ansi_colors["default"]}' |
|
62 |
||
63 |
def colorize_log(text): |
|
64 |
if not use_colors: |
|
65 |
return text |
|
66 |
for key,color in log_colors.items(): |
|
67 |
if key in text: |
|
68 |
return colorize(text, color) |
|
69 |
return text |
|
70 |
||
71 |
group_start = '\n' |
|
72 |
group_end = '' |
|
73 |
if os.getenv('GITHUB_ACTION'): |
|
74 |
group_start = '\n::group::' |
|
75 |
group_end = '\n::endgroup::\n' |
|
76 |
||
77 |
||
78 |
class WidelandsTestCase(): |
|
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
79 |
do_use_random_directory = True |
80 |
path_to_widelands_binary = None |
|
81 |
keep_output_around = False |
|
9573
by The Widelands Bunnybot
Improve testsuite error handling (#4326) |
82 |
ignore_error_code = False |
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
83 |
timeout = 600 |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
84 |
total_tests = 0 |
85 |
||
86 |
statuses = { |
|
87 |
'Starting': 'Start', |
|
88 |
'Loading': 'Load ', |
|
89 |
'Passed': 'Done ', |
|
90 |
'FAILED': 'FAIL ', |
|
91 |
'TIMED OUT': 'TMOUT', |
|
92 |
'SKIPPED': 'SKIP ', |
|
93 |
'IGNORED': ' IGN ', |
|
94 |
'Info': 'Info ' |
|
95 |
}
|
|
96 |
||
97 |
status_colors = { |
|
98 |
'Done ': success_color, |
|
99 |
'FAIL ': error_color, |
|
100 |
'TMOUT': error_color, |
|
101 |
'SKIP ': warning_color, |
|
102 |
' IGN ': warning_color, |
|
103 |
'Info ': info_color |
|
104 |
}
|
|
105 |
||
106 |
annotate = [ 'SKIP ', ' IGN ' ] |
|
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
107 |
|
108 |
def __init__(self, test_script, **wlargs): |
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
109 |
self.test_script = test_script |
110 |
self._shortname = os.path.basename(test_script) |
|
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
111 |
self._wlargs = wlargs |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
112 |
self.success = None |
113 |
self.result = 'NOT EXECUTED' |
|
114 |
self.report_header = None |
|
115 |
# 'which_time' is an integer defining the number of times Widelands has run this test
|
|
116 |
# case (i.e. because we might load a saved game from an earlier run). This will impact
|
|
117 |
# the filenames for stdout.txt.
|
|
118 |
self.which_time = 0 |
|
119 |
self.outputs = [] |
|
120 |
WidelandsTestCase.total_tests += 1 |
|
121 |
self._test_number = WidelandsTestCase.total_tests |
|
122 |
||
123 |
def run_widelands(self, wlargs): |
|
124 |
"""Run Widelands with the given 'wlargs'.
|
|
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
125 |
|
126 |
Returns the stdout filename."""
|
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
127 |
stdout_filename = os.path.join(self.run_dir, f'stdout_{self.which_time:02d}.txt') |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
128 |
if (os.path.exists(stdout_filename)): |
129 |
os.unlink(stdout_filename) |
|
130 |
||
131 |
with open(stdout_filename, 'a') as stdout_file: |
|
7293.1.52
by Holger Rapp
Adds a --datadir_for_testing flag which works as an extra --datadir flag. That |
132 |
args = [self.path_to_widelands_binary, |
10898
by The Widelands Bunnybot
Refactor commandline handling (CB #4471 / GH #6154) |
133 |
'--verbose', |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
134 |
f'--datadir={datadir()}', |
135 |
f'--datadir_for_testing={datadir_for_testing()}', |
|
136 |
f'--homedir={self.run_dir}', |
|
9015.2.19
by GunChleoc
Remove obsolete commandline options. |
137 |
'--nosound', |
9573
by The Widelands Bunnybot
Improve testsuite error handling (#4326) |
138 |
'--fail-on-lua-error', |
11221
by The Widelands Bunnybot
Tools for saveloading compatibility testing (CB #4859 / GH #6497) |
139 |
'--fail-on-errors', |
10988
by The Widelands Bunnybot
Validate environment locale settings (CB #4817 / GH #6455) |
140 |
'--language=en' ] |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
141 |
args += [ f'--{key}={value}' for key, value in wlargs.items() ] |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
142 |
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
143 |
stdout_file.write('Running widelands binary: ') |
9268
by The Widelands Bunnybot
Removing signal from warelist. (#3845) |
144 |
for anarg in args: |
145 |
stdout_file.write(anarg) |
|
146 |
stdout_file.write(" ") |
|
147 |
stdout_file.write("\n") |
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
148 |
stdout_file.flush() |
9268
by The Widelands Bunnybot
Removing signal from warelist. (#3845) |
149 |
|
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
150 |
start_time = get_time() |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
151 |
widelands = subprocess.Popen( |
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
152 |
args, shell=False, stdout=stdout_file, stderr=subprocess.STDOUT) |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
153 |
try: |
154 |
widelands.communicate(timeout = self.timeout) |
|
155 |
except subprocess.TimeoutExpired: |
|
156 |
widelands.kill() |
|
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
157 |
widelands.communicate() |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
158 |
self.wl_timed_out = True |
159 |
stdout_file.write('\n') |
|
160 |
stdout_file.write(colorize('Timed out.', error_color)) |
|
161 |
stdout_file.write('\n') |
|
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
162 |
end_time = get_time() |
163 |
stdout_file.flush() |
|
164 |
self.duration = datetime.timedelta(seconds = end_time - start_time) |
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
165 |
stdout_file.write('\n') |
166 |
stdout_file.write( |
|
167 |
colorize(f'{self.step_name()}: Returned from Widelands in {self.duration}, ' \ |
|
168 |
f'return code is {widelands.returncode:d}', info_color)) |
|
169 |
stdout_file.write('\n') |
|
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
170 |
self.widelands_returncode = widelands.returncode |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
171 |
self.outputs.append(stdout_filename) |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
172 |
return stdout_filename |
173 |
||
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
174 |
def run(self): |
175 |
num_w = len(str(self.total_tests)) |
|
176 |
self._progress = str(self._test_number).rjust(num_w) + '/' + str(self.total_tests) |
|
177 |
||
178 |
if self.do_use_random_directory: |
|
179 |
self.run_dir = tempfile.mkdtemp(prefix="widelands_regression_test_") |
|
180 |
else: |
|
181 |
self.run_dir = os.path.join(tempfile.gettempdir(), "widelands_regression_test", self.__class__.__name__) |
|
182 |
if os.path.exists(self.run_dir): |
|
183 |
if not self.keep_output_around: |
|
184 |
shutil.rmtree(self.run_dir) |
|
185 |
os.makedirs(self.run_dir) |
|
186 |
else: |
|
187 |
os.makedirs(self.run_dir) |
|
188 |
||
189 |
self.widelands_returncode = 0 |
|
190 |
self.wl_timed_out = False |
|
191 |
self.current_step = "" |
|
192 |
||
193 |
self.out_status('Start', f'{self.test_script} starting ...') |
|
194 |
stdout_filename = self.run_widelands(self._wlargs) |
|
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
195 |
stdout = open(stdout_filename, "r").read() |
196 |
self.verify_success(stdout, stdout_filename) |
|
197 |
||
11062
by The Widelands Bunnybot
Expose more functions to Lua Dropdown and Listselect API (CB #4848 / GH #6486) |
198 |
find_saves = lambda stdout: re.findall(r'Script requests save to: (\w+)$', stdout, re.M) |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
199 |
savegame_done = { fn: False for fn in find_saves(stdout) } |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
200 |
while self.success and not all(savegame_done.values()): |
201 |
self.which_time += 1 |
|
6820.2.11
by Holger Rapp
Added two more test that remove the ship when a ware should be transported. |
202 |
for savegame in sorted(savegame_done): |
203 |
if not savegame_done[savegame]: break |
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
204 |
self.current_step = f"{savegame}.wgf" |
205 |
self.out_status("Load ", "loading savegame ...") |
|
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
206 |
stdout_filename = self.run_widelands({ "loadgame": os.path.join( |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
207 |
self.run_dir, "save", f"{savegame}.wgf")}) |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
208 |
stdout = open(stdout_filename, "r").read() |
209 |
for new_save in find_saves(stdout): |
|
210 |
if new_save not in savegame_done: |
|
211 |
savegame_done[new_save] = False |
|
212 |
savegame_done[savegame] = True |
|
213 |
self.verify_success(stdout, stdout_filename) |
|
214 |
||
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
215 |
if self.report_header == None and not self.keep_output_around: |
216 |
shutil.rmtree(self.run_dir) |
|
217 |
||
218 |
def step_name(self): |
|
219 |
if self.which_time == 0: |
|
220 |
return self._shortname |
|
221 |
return f'{self._shortname} / {self.current_step}' |
|
222 |
||
223 |
def out_status(self, status, message): |
|
224 |
# Force writing to main test log immediately
|
|
225 |
||
226 |
# On github, also create an annotation for non-fatal problems that would
|
|
227 |
# otherwise go unnoticed
|
|
228 |
if (status in self.annotate) and os.getenv('GITHUB_ACTION'): |
|
229 |
sys.stdout.write(f'::warning::{self.step_name()}: {message}\n') |
|
230 |
sys.stdout.flush() |
|
231 |
||
232 |
if use_colors and status in self.status_colors.keys(): |
|
233 |
color = self.status_colors[status] |
|
234 |
status = colorize(status, color) |
|
235 |
message = colorize(message, color) |
|
236 |
sys.stdout.write(f'{self._progress} {status} {self.step_name()}: {message}\n') |
|
237 |
sys.stdout.flush() |
|
238 |
||
239 |
def get_result_color(self): |
|
240 |
if not use_colors: |
|
241 |
return 'default' |
|
242 |
status = self.statuses[self.result] |
|
243 |
if status in self.status_colors.keys(): |
|
244 |
return self.status_colors[status] |
|
245 |
return 'default' |
|
246 |
||
247 |
def out_result(self, stdout_filename): |
|
248 |
self.out_status(self.statuses[self.result], f'{self.result} in {self.duration}') |
|
249 |
if self.keep_output_around: |
|
250 |
self.out_status('Info ', f'stdout: {stdout_filename}') |
|
251 |
||
252 |
def fail(self, short, long, stdout_filename): |
|
253 |
self.success = False |
|
254 |
self.result = short |
|
255 |
self.out_result(stdout_filename) |
|
256 |
long = colorize(long, self.status_colors['FAIL ']) |
|
257 |
self.report_header = f'{long} Analyze the files in {self.run_dir} to see why this test case failed.\n' |
|
258 |
self.report_header += colorize(f'Stdout is: {stdout_filename}', info_color) |
|
259 |
||
260 |
def step_success(self, stdout_filename): |
|
261 |
old_result = self.result |
|
262 |
self.result = "Passed" |
|
263 |
self.out_result(stdout_filename) |
|
264 |
if self.which_time == 0: |
|
265 |
self.success = True |
|
266 |
else: |
|
267 |
self.result = old_result |
|
268 |
||
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
269 |
def verify_success(self, stdout, stdout_filename): |
9204
by The Widelands Bunnybot
Noordfrees: Launchpad mirror (#3766)\n\nMirrors all changes at widelands:master to lp:widelands (1d6b62c7619a182081e1c942ae533a95d6112044) |
270 |
# Catch instabilities with SDL in CI environment
|
271 |
if self.widelands_returncode == 2: |
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
272 |
# Print stdout in the final summary with this header
|
273 |
self.result = "SKIPPED" |
|
274 |
self.report_header = 'SDL initialization failed. TEST SKIPPED.' |
|
275 |
self.out_status('SKIP ', self.report_header) |
|
276 |
if use_colors: |
|
277 |
self.report_header = colorize(self.report_header, self.status_colors['SKIP ']) |
|
278 |
if self.which_time == 0: # must set it for the first run, later just ignore |
|
279 |
self.success = True |
|
9204
by The Widelands Bunnybot
Noordfrees: Launchpad mirror (#3766)\n\nMirrors all changes at widelands:master to lp:widelands (1d6b62c7619a182081e1c942ae533a95d6112044) |
280 |
else: |
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
281 |
if self.wl_timed_out: |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
282 |
self.fail("TIMED OUT", "The test timed out.", stdout_filename) |
283 |
return
|
|
284 |
if self.widelands_returncode != 0: |
|
285 |
if self.widelands_returncode == 1 and self.ignore_error_code: |
|
286 |
self.out_status(' IGN ', f'IGNORING error code 1') |
|
287 |
else: |
|
288 |
self.fail("FAILED", "Widelands exited abnormally.", stdout_filename) |
|
289 |
return
|
|
290 |
if not "All Tests passed" in stdout or "lua_errors.cc" in stdout: |
|
291 |
self.fail("FAILED", "Not all tests pass.", stdout_filename) |
|
292 |
return
|
|
293 |
self.step_success(stdout_filename) |
|
294 |
||
295 |
def print_report(self): |
|
296 |
print(f'{colorize(self.result, self.get_result_color())}: {self.test_script}\n') |
|
297 |
print(self.report_header) |
|
298 |
print(group_start, end='') |
|
299 |
print(colorize("stdout:", info_color)) |
|
300 |
for stdout_fn in self.outputs: |
|
301 |
with open(stdout_fn, "r") as stdout: |
|
302 |
for line in stdout: |
|
303 |
line = colorize_log(line) |
|
304 |
print(line, end='') |
|
305 |
print(group_end, end='') |
|
306 |
||
307 |
||
308 |
# For parallel execution of tests
|
|
309 |
def recommended_workers(binary): |
|
310 |
cpu_count = multiprocessing.cpu_count() |
|
311 |
||
312 |
# Widelands uses 2 threads, but the logic thread is much more heavy,
|
|
313 |
# so we give each Widelands instance one full CPU for the logic thread
|
|
314 |
# while we let the graphic threads of up to 3 instances share one CPU.
|
|
315 |
max_threads_cpu = 1 |
|
316 |
if cpu_count > 2 : |
|
317 |
max_threads_cpu = cpu_count - 1 - (cpu_count - 1) // 4 |
|
318 |
||
319 |
# Also test memory as a limiting factor
|
|
320 |
if psutil != None: |
|
321 |
mem_per_instance = 1500 # MB - default for debug builds |
|
322 |
||
323 |
widelands = subprocess.run([binary, '--version'], shell=False, encoding='utf-8', capture_output=True) |
|
324 |
firstline = widelands.stdout.splitlines(keepends=False)[0] |
|
325 |
firstline_OK = False |
|
326 |
if firstline.startswith('This is Widelands version'): |
|
327 |
if firstline.endswith('Release'): |
|
328 |
mem_per_instance = 800 # MB |
|
329 |
firstline_OK = True |
|
330 |
elif firstline.endswith('Debug'): |
|
331 |
firstline_OK = True |
|
332 |
if not firstline_OK: |
|
333 |
print('Cannot parse build type from stdout:', firstline) |
|
334 |
||
335 |
max_threads_mem = max(1, psutil.virtual_memory().available // (mem_per_instance * 1000 * 1000)) |
|
336 |
mem_avail = psutil.virtual_memory().available / 1000 / 1000 / 1000 # GB, not GiB :) |
|
337 |
mem_total = psutil.virtual_memory().total / 1000 / 1000 / 1000 |
|
338 |
print(f"{cpu_count} CPUs, {mem_avail:.1f}/{mem_total:.1f} gigabytes memory free") |
|
339 |
return min(max_threads_cpu, max_threads_mem) |
|
340 |
else: |
|
341 |
return max_threads_cpu |
|
342 |
||
343 |
def find_binary(): |
|
344 |
# Prefer binary from source directory
|
|
345 |
for potential_binary in ( |
|
346 |
glob(os.path.join(os.curdir, "widelands")) + |
|
347 |
glob(os.path.join(os.path.dirname(__file__), "widelands")) + |
|
348 |
glob(os.path.join("build", "src", "widelands")) + |
|
349 |
glob(os.path.join("src", "widelands")) + |
|
350 |
glob(os.path.join("..", "*", "src", "widelands")) |
|
351 |
):
|
|
352 |
if os.access(potential_binary, os.X_OK): |
|
353 |
return potential_binary |
|
354 |
||
355 |
# Fall back to binary in $PATH if possible
|
|
356 |
return shutil.which("widelands") |
|
357 |
||
358 |
return None |
|
359 |
||
360 |
def check_binary(binary): |
|
361 |
# Prefer binary from source directory instead of PATH
|
|
362 |
if os.path.dirname(binary) == '' and os.access(binary, os.X_OK): |
|
363 |
return os.path.join(os.curdir, binary) |
|
364 |
||
365 |
if shutil.which(binary) != None: |
|
366 |
return binary |
|
367 |
||
368 |
if os.path.dirname(binary) != '' and os.access(binary, os.X_OK): |
|
369 |
return binary |
|
370 |
||
371 |
for potential_path in [ os.curdir, os.path.dirname(__file__) ]: |
|
372 |
fullpath = os.path.join(potential_path, binary) |
|
373 |
if os.access(fullpath, os.X_OK): |
|
374 |
return fullpath |
|
375 |
||
376 |
return None |
|
6689.1.4
by Holger Rapp
All tests now run and pass on my system. |
377 |
|
378 |
def parse_args(): |
|
379 |
p = argparse.ArgumentParser(description= |
|
380 |
"Run the regression tests suite."
|
|
381 |
)
|
|
382 |
||
383 |
p.add_argument("-r", "--regexp", type=str, |
|
384 |
help = "Run only the tests from the files which filename matches." |
|
385 |
)
|
|
386 |
p.add_argument("-n", "--nonrandom", action="store_true", default = False, |
|
387 |
help = "Do not randomize the directories for the tests. This is useful " |
|
388 |
"if you want to run a test more often than once and not reopen stdout.txt "
|
|
389 |
"in your editor."
|
|
390 |
)
|
|
6801
by Holger Rapp
Fix more bugs around seafaring, especially related to persistence. |
391 |
p.add_argument("-k", "--keep-around", action="store_true", default = False, |
392 |
help = "Keep the output files around even when a test terminates successfully." |
|
393 |
)
|
|
6689.1.6
by Holger Rapp
Let the caller specify the widelands binary to use for regression testing. |
394 |
p.add_argument("-b", "--binary", type=str, |
395 |
help = "Run this binary as Widelands. Otherwise some default paths are searched." |
|
396 |
)
|
|
9573
by The Widelands Bunnybot
Improve testsuite error handling (#4326) |
397 |
p.add_argument("-i", "--ignore-error-code", action="store_true", default = False, |
398 |
help = "Assume success on return code 1, to allow running the tests " |
|
399 |
"without ASan reporting false positives."
|
|
400 |
)
|
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
401 |
p.add_argument("-j", "--workers", type=int, default = 0, |
402 |
help = "Use this many parallel workers." |
|
403 |
)
|
|
404 |
p.add_argument("-p", "--plain", "--nocolor", action="store_true", default = False, |
|
405 |
help = "Don't use ANSI color sequences in the output." |
|
406 |
)
|
|
407 |
p.add_argument("-t", "--timeout", type=float, default = "10", |
|
408 |
help = "Set the timeout duration for test cases in minutes. Default is 10 minutes." |
|
409 |
)
|
|
6689.1.6
by Holger Rapp
Let the caller specify the widelands binary to use for regression testing. |
410 |
|
411 |
args = p.parse_args() |
|
412 |
||
413 |
if args.binary is None: |
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
414 |
args.binary = find_binary() |
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
415 |
if args.binary is None: |
6689.1.6
by Holger Rapp
Let the caller specify the widelands binary to use for regression testing. |
416 |
p.error("No widelands binary found. Please specify with -b.") |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
417 |
else: |
418 |
args.binary = check_binary(args.binary) |
|
419 |
if args.binary is None: |
|
420 |
p.error("The specified widelands binary is not found.") |
|
421 |
||
422 |
if args.nonrandom: |
|
423 |
if args.workers != 1: |
|
424 |
args.workers = 1 |
|
425 |
print("Only one worker is possible with --nonrandom!") |
|
426 |
if args.keep_around: |
|
427 |
print("--nonrandom is not recommended with --keep-around, some files will be overwritten!") |
|
428 |
||
429 |
if args.workers == 0: |
|
430 |
args.workers = recommended_workers(args.binary) |
|
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
431 |
|
6689.1.6
by Holger Rapp
Let the caller specify the widelands binary to use for regression testing. |
432 |
return args |
433 |
||
6689.1.2
by Holger Rapp
Brought back original content of regression_test.py. |
434 |
|
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
435 |
def discover_loadgame_tests(regexp, suite): |
436 |
"""Add all tests using --loadgame to the 'suite'."""
|
|
10885
by The Widelands Bunnybot
Correct naval warfare rebasing artifacts (closes CB#4699) |
437 |
# Savegames with custom scripts
|
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
438 |
for fixture in sorted(glob(os.path.join("test", "save", "*"))): |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
439 |
if not os.path.isdir(fixture): |
440 |
continue
|
|
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
441 |
savegame = sorted(glob(os.path.join(fixture, "*.wgf")))[0] |
442 |
for test_script in sorted(glob(os.path.join(fixture, "test*.lua"))): |
|
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
443 |
if regexp is not None and not re.search(regexp, test_script): |
444 |
continue
|
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
445 |
suite.append( |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
446 |
WidelandsTestCase(test_script, |
447 |
loadgame=savegame, script=test_script)) |
|
10885
by The Widelands Bunnybot
Correct naval warfare rebasing artifacts (closes CB#4699) |
448 |
# Savegames without custom script, just test loading
|
449 |
test_script = os.path.join("test", "scripting", "load_and_quit.lua") |
|
450 |
for savegame in sorted(glob(os.path.join("test", "save", "*.wgf"))): |
|
451 |
if regexp is not None and not re.search(regexp, savegame): |
|
452 |
continue
|
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
453 |
suite.append(WidelandsTestCase(savegame, loadgame=savegame, script=test_script)) |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
454 |
|
455 |
def discover_scenario_tests(regexp, suite): |
|
456 |
"""Add all tests using --scenario to the 'suite'."""
|
|
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
457 |
for wlmap in sorted(glob(os.path.join("test", "maps", "*"))): |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
458 |
if not os.path.isdir(wlmap): |
459 |
continue
|
|
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
460 |
for test_script in sorted(glob(os.path.join(wlmap, "scripting", "test*.lua"))): |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
461 |
if regexp is not None and not re.search(regexp, test_script): |
462 |
continue
|
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
463 |
suite.append( |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
464 |
WidelandsTestCase(test_script, |
465 |
scenario=wlmap, script=test_script)) |
|
466 |
||
10908
by The Widelands Bunnybot
Regression tests for start and win conditions, fix race regressions (CB #4524 / GH #6182) |
467 |
def discover_game_template_tests(regexp, suite): |
468 |
"""Add all tests using --new_game_from_template to the 'suite'."""
|
|
469 |
for templ in sorted(glob(os.path.join("test", "templates", "test*.wgt"))): |
|
470 |
if not os.path.isfile(templ): |
|
471 |
continue
|
|
472 |
test_script = templ[:-3] + 'lua' |
|
473 |
if not os.path.isfile(test_script): |
|
474 |
print(f"WARNING: Game template test { templ }: corresponding script { test_script } not found - Skipping.") |
|
475 |
continue
|
|
476 |
if regexp is not None and not re.search(regexp, test_script): |
|
477 |
continue
|
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
478 |
suite.append( |
10908
by The Widelands Bunnybot
Regression tests for start and win conditions, fix race regressions (CB #4524 / GH #6182) |
479 |
WidelandsTestCase(test_script, |
480 |
new_game_from_template=templ, script=test_script)) |
|
481 |
||
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
482 |
def discover_editor_tests(regexp, suite): |
483 |
"""Add all tests needing --editor to the 'suite'."""
|
|
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
484 |
for wlmap in sorted(glob(os.path.join("test", "maps", "*"))): |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
485 |
if not os.path.isdir(wlmap): |
486 |
continue
|
|
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
487 |
for test_script in sorted(glob(os.path.join(wlmap, "scripting", "editor_test*.lua"))): |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
488 |
if regexp is not None and not re.search(regexp, test_script): |
489 |
continue
|
|
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
490 |
suite.append( |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
491 |
WidelandsTestCase(test_script, |
492 |
editor=wlmap, script=test_script)) |
|
493 |
||
6689.1.2
by Holger Rapp
Brought back original content of regression_test.py. |
494 |
def main(): |
6689.1.4
by Holger Rapp
All tests now run and pass on my system. |
495 |
args = parse_args() |
496 |
||
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
497 |
global use_colors |
498 |
use_colors = not args.plain |
|
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
499 |
WidelandsTestCase.path_to_widelands_binary = args.binary |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
500 |
print(f"Using '{args.binary}' binary.") |
6820.2.7
by Holger Rapp
Finally found a better way to define tests that do not need all the redundant python files. |
501 |
WidelandsTestCase.do_use_random_directory = not args.nonrandom |
502 |
WidelandsTestCase.keep_output_around = args.keep_around |
|
9573
by The Widelands Bunnybot
Improve testsuite error handling (#4326) |
503 |
WidelandsTestCase.ignore_error_code = args.ignore_error_code |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
504 |
WidelandsTestCase.timeout = args.timeout * 60 |
505 |
||
506 |
test_cases = [] |
|
507 |
discover_loadgame_tests(args.regexp, test_cases) |
|
508 |
discover_scenario_tests(args.regexp, test_cases) |
|
509 |
discover_game_template_tests(args.regexp, test_cases) |
|
510 |
discover_editor_tests(args.regexp, test_cases) |
|
511 |
||
512 |
print(f"Will run { len(test_cases) } tests with { args.workers } workers.\n") |
|
513 |
||
514 |
start_time = get_time() |
|
515 |
||
516 |
if args.workers == 1: |
|
517 |
# Single-threaded execution is special-cased for nicer grouping.
|
|
518 |
for test_case in test_cases: |
|
519 |
test_case.run() |
|
10547
by The Widelands Bunnybot
Add timeout support to regression_test.py and improve output (#5806) |
520 |
else: |
11100
by The Widelands Bunnybot
Regression test improvements (CB #4876 / GH #6515) |
521 |
# Parallel execution
|
522 |
with cf.ThreadPoolExecutor(max_workers = args.workers) as executor: |
|
523 |
futures = {executor.submit(test_case.run): test_case for test_case in test_cases} |
|
524 |
for future in cf.as_completed(futures): |
|
525 |
if future.exception(): |
|
526 |
raise future.exception() |
|
527 |
||
528 |
end_time = get_time() |
|
529 |
||
530 |
separator = '\n' + \ |
|
531 |
colorize('---------------------------------------------------------------------------', |
|
532 |
separator_color) + '\n' |
|
533 |
||
534 |
nr_errors = 0 |
|
535 |
results = dict() |
|
536 |
for test_case in test_cases: |
|
537 |
# Skipped test cases are logged, but don't count as failure
|
|
538 |
if test_case.report_header != None: |
|
539 |
print(separator) |
|
540 |
test_case.print_report() |
|
541 |
if test_case.result in results.keys(): |
|
542 |
results[test_case.result].append(test_case.test_script) |
|
543 |
else: |
|
544 |
results[test_case.result] = [ test_case.test_script ] |
|
545 |
if not test_case.success: |
|
546 |
nr_errors += 1 |
|
547 |
||
548 |
print(separator) |
|
549 |
||
550 |
summary_common = f'Ran {len(test_cases)} test cases in {(end_time - start_time):.3f} s,' |
|
551 |
||
552 |
if nr_errors == 0: |
|
553 |
print(summary_common, colorize('all tests passed.', success_color)) |
|
554 |
return True |
|
555 |
||
556 |
for result,tests in results.items(): |
|
557 |
print(f'{len(tests)} tests {result}:') |
|
558 |
for test_name in tests: |
|
559 |
print(f' {test_name}') |
|
560 |
print(separator) |
|
561 |
print(summary_common, f'{len(test_cases) - nr_errors} tests passed,', |
|
562 |
colorize(f'{nr_errors} tests failed!', error_color)) |
|
563 |
return False |
|
6689.1.2
by Holger Rapp
Brought back original content of regression_test.py. |
564 |
|
565 |
if __name__ == '__main__': |
|
6803
by Holger Rapp
regression_test.py returns an error to the system on failure. |
566 |
sys.exit(0 if main() else 1) |