~widelands-dev/widelands/trunk

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)