clickhouse-test 39.7 KB
Newer Older
A
Azat Khuzhin 已提交
1 2
#!/usr/bin/env python3

3
import sys
4 5 6
import os
import os.path
import re
A
alesapin 已提交
7
import json
8 9 10 11

from argparse import ArgumentParser
from argparse import FileType
from pprint import pprint
12
import shlex
13
import subprocess
14 15 16 17
from subprocess import check_call
from subprocess import Popen
from subprocess import PIPE
from subprocess import CalledProcessError
18
from subprocess import TimeoutExpired
19
from datetime import datetime
A
Alexey Milovidov 已提交
20
from time import time, sleep
21
from errno import ESRCH
22 23 24 25
try:
    import termcolor
except ImportError:
    termcolor = None
26
from random import random
A
Azat Khuzhin 已提交
27
import subprocess
P
proller 已提交
28
import multiprocessing
P
proller 已提交
29
from contextlib import closing
30

31

A
alesapin 已提交
32
MESSAGES_TO_RETRY = [
33
    "DB::Exception: ZooKeeper session has been expired",
A
alesapin 已提交
34
    "Coordination::Exception: Connection loss",
A
Alexey Milovidov 已提交
35
    "Operation timed out",
A
Alexey Milovidov 已提交
36
    "ConnectionPoolWithFailover: Connection failed at try",
A
alesapin 已提交
37 38
]

39

40 41 42
def json_minify(string):
    """
    Removes all js-style comments from json string. Allows to have comments in skip_list.json.
A
alexey-milovidov 已提交
43
    The code taken from https://github.com/getify/JSON.minify/tree/python under the MIT license.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
    """

    tokenizer = re.compile('"|(/\*)|(\*/)|(//)|\n|\r')
    end_slashes_re = re.compile(r'(\\)*$')

    in_string = False
    in_multi = False
    in_single = False

    new_str = []
    index = 0

    for match in re.finditer(tokenizer, string):
        if not (in_multi or in_single):
            tmp = string[index:match.start()]
            new_str.append(tmp)
        else:
            # Replace comments with white space so that the JSON parser reports
            # the correct column numbers on parsing errors.
            new_str.append(' ' * (match.start() - index))

        index = match.end()
        val = match.group()

        if val == '"' and not (in_multi or in_single):
            escaped = end_slashes_re.search(string, 0, match.start())

            # start of string or unescaped quote character to end string
            if not in_string or (escaped is None or len(escaped.group()) % 2 == 0):  # noqa
                in_string = not in_string
            index -= 1  # include " character in next catch
        elif not (in_string or in_multi or in_single):
            if val == '/*':
                in_multi = True
            elif val == '//':
                in_single = True
        elif val == '*/' and in_multi and not (in_string or in_single):
            in_multi = False
            new_str.append(' ' * len(val))
        elif val in '\r\n' and not (in_multi or in_string) and in_single:
            in_single = False
        elif not in_multi or in_single:  # noqa
            new_str.append(val)

        if val in '\r\n':
            new_str.append(val)
        elif in_multi or in_single:
            new_str.append(' ' * len(val))

    new_str.append(string[index:])
    return ''.join(new_str)


97 98 99 100 101 102
def remove_control_characters(s):
    """
    https://github.com/html5lib/html5lib-python/issues/96#issuecomment-43438438
    """
    def str_to_int(s, default, base=10):
        if int(s, base) < 0x10000:
A
Azat Khuzhin 已提交
103
            return chr(int(s, base))
104
        return default
105 106 107
    s = re.sub(r"&#(\d+);?", lambda c: str_to_int(c.group(1), c.group(0)), s)
    s = re.sub(r"&#[xX]([0-9a-fA-F]+);?", lambda c: str_to_int(c.group(1), c.group(0), base=16), s)
    s = re.sub(r"[\x00-\x08\x0b\x0e-\x1f\x7f]", "", s)
108 109
    return s

110
def get_db_engine(args):
111 112 113
    if args.db_engine:
        return " ENGINE=" + args.db_engine
    return ""   # Will use default engine
114 115

def run_single_test(args, ext, server_logs_level, client_options, case_file, stdout_file, stderr_file):
116 117
    # print(client_options)

118
    start_time = datetime.now()
119 120 121 122 123 124 125 126 127 128 129 130 131 132
    if args.database:
        database = args.database
        os.environ.setdefault("CLICKHOUSE_DATABASE", database)

    else:
        # If --database is not specified, we will create temporary database with unique name
        # And we will recreate and drop it for each test
        def random_str(length=6):
            import random
            import string
            alphabet = string.ascii_lowercase + string.digits
            return ''.join(random.choice(alphabet) for _ in range(length))
        database = 'test_{suffix}'.format(suffix=random_str())

A
Azat Khuzhin 已提交
133
        clickhouse_proc_create = Popen(shlex.split(args.client), stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True)
134 135 136 137 138
        try:
            clickhouse_proc_create.communicate(("CREATE DATABASE " + database + get_db_engine(args)), timeout=args.timeout)
        except TimeoutExpired:
            total_time = (datetime.now() - start_time).total_seconds()
            return clickhouse_proc_create, "", "Timeout creating database {} before test".format(database), total_time
139 140 141

        os.environ["CLICKHOUSE_DATABASE"] = database

142
    params = {
143
        'client': args.client + ' --database=' + database,
144 145 146 147 148 149 150
        'logs_level': server_logs_level,
        'options': client_options,
        'test': case_file,
        'stdout': stdout_file,
        'stderr': stderr_file,
    }

151 152
    pattern = '{test} > {stdout} 2> {stderr}'

A
alesapin 已提交
153
    if ext == '.sql':
154 155 156
        pattern = "{client} --send_logs_level={logs_level} --testmode --multiquery {options} < " + pattern

    command = pattern.format(**params)
M
typo  
myrrc 已提交
157

M
myrrc 已提交
158
    # print(command)
A
alesapin 已提交
159

160
    proc = Popen(command, shell=True, env=os.environ)
M
typo  
myrrc 已提交
161

A
alesapin 已提交
162 163 164
    while (datetime.now() - start_time).total_seconds() < args.timeout and proc.poll() is None:
        sleep(0.01)

165
    if not args.database:
A
Azat Khuzhin 已提交
166
        clickhouse_proc_create = Popen(shlex.split(args.client), stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True)
A
Better  
alesapin 已提交
167
        seconds_left = max(args.timeout - (datetime.now() - start_time).total_seconds(), 10)
168 169 170
        try:
            clickhouse_proc_create.communicate(("DROP DATABASE " + database), timeout=seconds_left)
        except TimeoutExpired:
A
alesapin 已提交
171 172 173 174 175 176 177
            # kill test process because it can also hung
            if proc.returncode is None:
                try:
                    proc.kill()
                except OSError as e:
                    if e.errno != ESRCH:
                        raise
A
alesapin 已提交
178 179

            total_time = (datetime.now() - start_time).total_seconds()
180
            return clickhouse_proc_create, "", "Timeout dropping database {} after test".format(database), total_time
181

182 183
    total_time = (datetime.now() - start_time).total_seconds()

A
alexey-milovidov 已提交
184
    # Normalize randomized database names in stdout, stderr files.
185 186
    os.system("LC_ALL=C sed -i -e 's/{test_db}/default/g' {file}".format(test_db=database, file=stdout_file))
    os.system("LC_ALL=C sed -i -e 's/{test_db}/default/g' {file}".format(test_db=database, file=stderr_file))
187

A
Azat Khuzhin 已提交
188 189 190 191
    stdout = open(stdout_file, 'rb').read() if os.path.exists(stdout_file) else b''
    stdout = str(stdout, errors='replace', encoding='utf-8')
    stderr = open(stderr_file, 'rb').read() if os.path.exists(stderr_file) else b''
    stderr = str(stderr, errors='replace', encoding='utf-8')
A
alesapin 已提交
192

193
    return proc, stdout, stderr, total_time
A
alesapin 已提交
194

195

A
alesapin 已提交
196 197 198
def need_retry(stderr):
    return any(msg in stderr for msg in MESSAGES_TO_RETRY)

199

200 201
def get_processlist(client_cmd):
    try:
T
tavplubix 已提交
202
        return subprocess.check_output("{} --query 'SHOW PROCESSLIST FORMAT Vertical'".format(client_cmd), shell=True).decode('utf-8')
203 204 205
    except:
        return "" #  server seems dead

206

A
alesapin 已提交
207
# collect server stacktraces using gdb
208
def get_stacktraces_from_gdb(server_pid):
209
    cmd = "gdb -batch -ex 'thread apply all backtrace' -p {}".format(server_pid)
210
    try:
T
tavplubix 已提交
211
        return subprocess.check_output(cmd, shell=True).decode('utf-8')
212
    except Exception as ex:
213 214 215
        return "Error occured while receiving stack traces from gdb: {}".format(str(ex))


A
alesapin 已提交
216
# collect server stacktraces from system.stack_trace table
217
# it does not work in Sandbox
218 219
def get_stacktraces_from_clickhouse(client):
    try:
T
tavplubix 已提交
220 221 222 223
        return subprocess.check_output("{} --allow_introspection_functions=1 --query "
               "\"SELECT arrayStringConcat(arrayMap(x, y -> concat(x, ': ', y), arrayMap(x -> addressToLine(x), trace), "
               "arrayMap(x -> demangle(addressToSymbol(x)), trace)), '\n') as trace "
               "FROM system.stack_trace format Vertical\"".format(client), shell=True).decode('utf-8')
224 225
    except Exception as ex:
        return "Error occured while receiving stack traces from client: {}".format(str(ex))
226

227

228
def get_server_pid(server_tcp_port):
229
    cmd = "lsof -i tcp:{port} -s tcp:LISTEN -Fp | awk '/^p[0-9]+$/{{print substr($0, 2)}}'".format(port=server_tcp_port)
230 231 232
    try:
        output = subprocess.check_output(cmd, shell=True)
        if output:
233
            return int(output)
234 235 236 237 238
        else:
            return None # server dead
    except Exception as ex:
        return None

239

P
proller 已提交
240
def colored(text, args, color=None, on_color=None, attrs=None):
241
       if termcolor and (sys.stdout.isatty() or args.force_color):
P
proller 已提交
242 243 244 245
           return termcolor.colored(text, color, on_color, attrs)
       else:
           return text

246

P
proller 已提交
247 248
SERVER_DIED = False
exit_code = 0
A
Alexey Milovidov 已提交
249
stop_time = None
P
proller 已提交
250

251

252
# def run_tests_array(all_tests, suite, suite_dir, suite_tmp_dir, run_total):
253
def run_tests_array(all_tests_with_params):
A
alesapin 已提交
254
    all_tests, suite, suite_dir, suite_tmp_dir = all_tests_with_params
P
proller 已提交
255
    global exit_code
P
proller 已提交
256
    global SERVER_DIED
A
Alexey Milovidov 已提交
257
    global stop_time
258

P
proller 已提交
259 260 261 262 263 264 265 266 267 268 269 270 271
    OP_SQUARE_BRACKET = colored("[", args, attrs=['bold'])
    CL_SQUARE_BRACKET = colored("]", args, attrs=['bold'])

    MSG_FAIL = OP_SQUARE_BRACKET + colored(" FAIL ", args, "red", attrs=['bold']) + CL_SQUARE_BRACKET
    MSG_UNKNOWN = OP_SQUARE_BRACKET + colored(" UNKNOWN ", args, "yellow", attrs=['bold']) + CL_SQUARE_BRACKET
    MSG_OK = OP_SQUARE_BRACKET + colored(" OK ", args, "green", attrs=['bold']) + CL_SQUARE_BRACKET
    MSG_SKIPPED = OP_SQUARE_BRACKET + colored(" SKIPPED ", args, "cyan", attrs=['bold']) + CL_SQUARE_BRACKET

    passed_total = 0
    skipped_total = 0
    failures_total = 0
    failures = 0
    failures_chain = 0
272

273 274
    client_options = get_additional_client_options(args)

275 276 277 278
    def print_test_time(test_time):
        if args.print_time:
            print(" {0:.2f} sec.".format(test_time), end='')

279 280
    if len(all_tests):
        print("\nRunning {} {} tests.".format(len(all_tests), suite) + "\n")
A
alesapin 已提交
281

282
    for case in all_tests:
P
proller 已提交
283 284
        if SERVER_DIED:
            break
A
alesapin 已提交
285

A
Alexey Milovidov 已提交
286 287 288 289
        if stop_time and time() > stop_time:
            print("\nStop tests run because global time limit is exceeded.\n")
            break

P
proller 已提交
290 291 292 293
        case_file = os.path.join(suite_dir, case)
        (name, ext) = os.path.splitext(case)

        try:
A
alesapin 已提交
294
            sys.stdout.flush()
P
proller 已提交
295 296 297 298 299
            sys.stdout.write("{0:72}".format(name + ": "))

            if args.skip and any(s in name for s in args.skip):
                print(MSG_SKIPPED + " - skip")
                skipped_total += 1
300 301
            elif not args.zookeeper and ('zookeeper' in name
                    or 'replica' in name):
P
proller 已提交
302 303
                print(MSG_SKIPPED + " - no zookeeper")
                skipped_total += 1
A
akuzm 已提交
304 305 306
            elif not args.shard and ('shard' in name
                    or 'distributed' in name
                    or 'global' in name):
P
proller 已提交
307 308
                print(MSG_SKIPPED + " - no shard")
                skipped_total += 1
C
CurtizJ 已提交
309 310 311 312 313
            elif not args.no_long and ('long' in name
                    # Tests for races and deadlocks usually are runned in loop
                    #  for significant amount of time
                    or 'deadlock' in name
                    or 'race' in name):
P
proller 已提交
314 315 316 317 318 319 320 321 322 323 324
                print(MSG_SKIPPED + " - no long")
                skipped_total += 1
            else:
                disabled_file = os.path.join(suite_dir, name) + '.disabled'

                if os.path.exists(disabled_file) and not args.disabled:
                    message = open(disabled_file, 'r').read()
                    print(MSG_SKIPPED + " - " + message)
                else:

                    if args.testname:
A
Azat Khuzhin 已提交
325
                        clickhouse_proc = Popen(shlex.split(args.client), stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True)
326
                        clickhouse_proc.communicate(("SELECT 'Running test {suite}/{case} from pid={pid}';".format(pid = os.getpid(), case = case, suite = suite)), timeout=10)
P
proller 已提交
327

328 329
                        if clickhouse_proc.returncode != 0:
                            failures += 1
A
alexey-milovidov 已提交
330
                            print("Server does not respond to health check")
331 332 333
                            SERVER_DIED = True
                            break

P
proller 已提交
334 335 336 337
                    reference_file = os.path.join(suite_dir, name) + '.reference'
                    stdout_file = os.path.join(suite_tmp_dir, name) + '.stdout'
                    stderr_file = os.path.join(suite_tmp_dir, name) + '.stderr'

338
                    proc, stdout, stderr, total_time = run_single_test(args, ext, server_logs_level, client_options, case_file, stdout_file, stderr_file)
M
typo  
myrrc 已提交
339

P
proller 已提交
340 341 342 343 344 345 346 347
                    if proc.returncode is None:
                        try:
                            proc.kill()
                        except OSError as e:
                            if e.errno != ESRCH:
                                raise

                        failures += 1
348 349 350
                        print(MSG_FAIL, end='')
                        print_test_time(total_time)
                        print(" - Timeout!")
351 352
                        if stderr:
                            print(stderr)
P
proller 已提交
353 354 355
                    else:
                        counter = 1
                        while proc.returncode != 0 and need_retry(stderr):
356
                            proc, stdout, stderr, total_time = run_single_test(args, ext, server_logs_level, client_options, case_file, stdout_file, stderr_file)
P
proller 已提交
357 358 359 360 361 362 363 364
                            sleep(2**counter)
                            counter += 1
                            if counter > 6:
                                break

                        if proc.returncode != 0:
                            failures += 1
                            failures_chain += 1
365 366 367
                            print(MSG_FAIL, end='')
                            print_test_time(total_time)
                            print(" - return code {}".format(proc.returncode))
P
proller 已提交
368 369

                            if stderr:
A
Azat Khuzhin 已提交
370
                                print(stderr)
P
proller 已提交
371

M
typo  
myrrc 已提交
372
                            # Stop on fatal errors like segmentation fault. They are sent to client via logs.
A
Alexey Milovidov 已提交
373 374 375
                            if ' <Fatal> ' in stderr:
                                SERVER_DIED = True

P
proller 已提交
376 377 378
                            if args.stop and ('Connection refused' in stderr or 'Attempt to read after eof' in stderr) and not 'Received exception from server' in stderr:
                                SERVER_DIED = True

379 380
                            if os.path.isfile(stdout_file):
                                print(", result:\n")
A
Alexander Kuzmenkov 已提交
381
                                print('\n'.join(open(stdout_file).read().split('\n')[:100]))
382

P
proller 已提交
383 384 385
                        elif stderr:
                            failures += 1
                            failures_chain += 1
386 387
                            print(MSG_FAIL, end='')
                            print_test_time(total_time)
A
Alexander Kuzmenkov 已提交
388 389
                            print(" - having stderror:\n{}".format(
                                '\n'.join(stderr.split('\n')[:100])))
P
proller 已提交
390 391 392
                        elif 'Exception' in stdout:
                            failures += 1
                            failures_chain += 1
393 394
                            print(MSG_FAIL, end='')
                            print_test_time(total_time)
A
Alexander Kuzmenkov 已提交
395 396
                            print(" - having exception:\n{}".format(
                                '\n'.join(stdout.split('\n')[:100])))
P
proller 已提交
397
                        elif not os.path.isfile(reference_file):
398 399 400
                            print(MSG_UNKNOWN, end='')
                            print_test_time(total_time)
                            print(" - no reference file")
P
proller 已提交
401
                        else:
A
Azat Khuzhin 已提交
402
                            result_is_different = subprocess.call(['diff', '-q', reference_file, stdout_file], stdout=PIPE)
P
proller 已提交
403 404

                            if result_is_different:
A
Azat Khuzhin 已提交
405
                                diff = Popen(['diff', '-U', str(args.unified), reference_file, stdout_file], stdout=PIPE, universal_newlines=True).communicate()[0]
P
proller 已提交
406
                                failures += 1
407 408 409
                                print(MSG_FAIL, end='')
                                print_test_time(total_time)
                                print(" - result differs with reference:\n{}".format(diff))
P
proller 已提交
410 411 412
                            else:
                                passed_total += 1
                                failures_chain = 0
413 414 415
                                print(MSG_OK, end='')
                                print_test_time(total_time)
                                print()
P
proller 已提交
416 417 418 419
                                if os.path.exists(stdout_file):
                                    os.remove(stdout_file)
                                if os.path.exists(stderr_file):
                                    os.remove(stderr_file)
420
        except KeyboardInterrupt as e:
P
proller 已提交
421
            print(colored("Break tests execution", args, "red"))
422
            raise e
P
proller 已提交
423 424 425 426
        except:
            import traceback
            exc_type, exc_value, tb = sys.exc_info()
            failures += 1
427
            print("{0} - Test internal error: {1}\n{2}\n{3}".format(MSG_FAIL, exc_type.__name__, exc_value, "\n".join(traceback.format_tb(tb, 10))))
P
proller 已提交
428 429 430 431 432 433 434

        if failures_chain >= 20:
            break

    failures_total = failures_total + failures

    if failures_total > 0:
435
        print(colored("\nHaving {failures_total} errors! {passed_total} tests passed. {skipped_total} tests skipped.".format(passed_total = passed_total, skipped_total = skipped_total, failures_total = failures_total), args, "red", attrs=["bold"]))
P
proller 已提交
436 437 438
        exit_code = 1
    else:
        print(colored("\n{passed_total} tests passed. {skipped_total} tests skipped.".format(passed_total = passed_total, skipped_total = skipped_total), args, "green", attrs=["bold"]))
A
alesapin 已提交
439

440

P
proller 已提交
441 442
server_logs_level = "warning"

443

444
def check_server_started(client, retry_count):
445 446
    print("Connecting to ClickHouse server...", end='')
    sys.stdout.flush()
447 448
    while retry_count > 0:
        clickhouse_proc = Popen(shlex.split(client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
A
Azat Khuzhin 已提交
449
        (stdout, stderr) = clickhouse_proc.communicate(b"SELECT 1")
450

A
Azat Khuzhin 已提交
451
        if clickhouse_proc.returncode == 0 and stdout.startswith(b"1"):
452 453 454 455 456 457 458 459
            print(" OK")
            sys.stdout.flush()
            return True

        if clickhouse_proc.returncode == 210:
            # Connection refused, retry
            print('.', end = '')
            sys.stdout.flush()
460 461
            retry_count -= 1
            sleep(0.5)
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
            continue

        # Other kind of error, fail.
        print('')
        print("Client invocation failed with code ", clickhouse_proc.returncode, ": ")
        # We can't print this, because for some reason this is python 2,
        # and args appeared in 3.3. To hell with it.
        # print(''.join(clickhouse_proc.args))
        print("stdout: ")
        print(stdout)
        print("stderr: ")
        print(stderr)
        sys.stdout.flush()
        return False

    print('')
    print('All connection tries failed')
    sys.stdout.flush()
480 481 482 483

    return False


A
alesapin 已提交
484 485 486 487 488 489 490 491
class BuildFlags(object):
    THREAD = 'thread-sanitizer'
    ADDRESS = 'address-sanitizer'
    UNDEFINED = 'ub-sanitizer'
    MEMORY = 'memory-sanitizer'
    DEBUG = 'debug-build'
    UNBUNDLED = 'unbundled-build'
    RELEASE = 'release-build'
492
    DATABASE_ORDINARY = 'database-ordinary'
A
alesapin 已提交
493 494 495 496 497
    POLYMORPHIC_PARTS = 'polymorphic-parts'


def collect_build_flags(client):
    clickhouse_proc = Popen(shlex.split(client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
A
Azat Khuzhin 已提交
498
    (stdout, stderr) = clickhouse_proc.communicate(b"SELECT value FROM system.build_options WHERE name = 'CXX_FLAGS'")
A
alesapin 已提交
499 500 501
    result = []

    if clickhouse_proc.returncode == 0:
A
Azat Khuzhin 已提交
502
        if b'-fsanitize=thread' in stdout:
A
alesapin 已提交
503
            result.append(BuildFlags.THREAD)
A
Azat Khuzhin 已提交
504
        elif b'-fsanitize=address' in stdout:
A
alesapin 已提交
505
            result.append(BuildFlags.ADDRESS)
A
Azat Khuzhin 已提交
506
        elif b'-fsanitize=undefined' in stdout:
A
alesapin 已提交
507
            result.append(BuildFlags.UNDEFINED)
A
Azat Khuzhin 已提交
508
        elif b'-fsanitize=memory' in stdout:
A
alesapin 已提交
509 510
            result.append(BuildFlags.MEMORY)
    else:
M
typo  
myrrc 已提交
511
        raise Exception("Cannot get information about build from server errorcode {}, stderr {}".format(clickhouse_proc.returncode, stderr))
A
alesapin 已提交
512 513

    clickhouse_proc = Popen(shlex.split(client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
A
Azat Khuzhin 已提交
514
    (stdout, stderr) = clickhouse_proc.communicate(b"SELECT value FROM system.build_options WHERE name = 'BUILD_TYPE'")
A
alesapin 已提交
515 516

    if clickhouse_proc.returncode == 0:
A
Azat Khuzhin 已提交
517
        if b'Debug' in stdout:
A
alesapin 已提交
518
            result.append(BuildFlags.DEBUG)
A
Azat Khuzhin 已提交
519
        elif b'RelWithDebInfo' in stdout or b'Release' in stdout:
A
alesapin 已提交
520 521
            result.append(BuildFlags.RELEASE)
    else:
M
typo  
myrrc 已提交
522
        raise Exception("Cannot get information about build from server errorcode {}, stderr {}".format(clickhouse_proc.returncode, stderr))
A
alesapin 已提交
523 524

    clickhouse_proc = Popen(shlex.split(client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
A
Azat Khuzhin 已提交
525
    (stdout, stderr) = clickhouse_proc.communicate(b"SELECT value FROM system.build_options WHERE name = 'UNBUNDLED'")
A
alesapin 已提交
526 527

    if clickhouse_proc.returncode == 0:
A
Azat Khuzhin 已提交
528
        if b'ON' in stdout or b'1' in stdout:
A
alesapin 已提交
529 530
            result.append(BuildFlags.UNBUNDLED)
    else:
M
typo  
myrrc 已提交
531
        raise Exception("Cannot get information about build from server errorcode {}, stderr {}".format(clickhouse_proc.returncode, stderr))
A
alesapin 已提交
532 533

    clickhouse_proc = Popen(shlex.split(client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
A
Azat Khuzhin 已提交
534
    (stdout, stderr) = clickhouse_proc.communicate(b"SELECT value FROM system.settings WHERE name = 'default_database_engine'")
A
alesapin 已提交
535 536

    if clickhouse_proc.returncode == 0:
A
Azat Khuzhin 已提交
537
        if b'Ordinary' in stdout:
538
            result.append(BuildFlags.DATABASE_ORDINARY)
A
alesapin 已提交
539
    else:
M
typo  
myrrc 已提交
540
        raise Exception("Cannot get information about build from server errorcode {}, stderr {}".format(clickhouse_proc.returncode, stderr))
A
alesapin 已提交
541

A
Anton Popov 已提交
542 543 544 545 546 547 548 549 550
    clickhouse_proc = Popen(shlex.split(client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
    (stdout, stderr) = clickhouse_proc.communicate(b"SELECT value FROM system.merge_tree_settings WHERE name = 'min_bytes_for_wide_part'")

    if clickhouse_proc.returncode == 0:
        if stdout == b'0\n':
            result.append(BuildFlags.POLYMORPHIC_PARTS)
    else:
        raise Exception("Cannot get inforamtion about build from server errorcode {}, stderr {}".format(clickhouse_proc.returncode, stderr))

A
alesapin 已提交
551 552 553
    return result


P
proller 已提交
554 555
def main(args):
    global SERVER_DIED
A
Alexey Milovidov 已提交
556
    global stop_time
P
proller 已提交
557 558
    global exit_code
    global server_logs_level
A
alesapin 已提交
559

560
    def is_data_present():
P
proller 已提交
561
        clickhouse_proc = Popen(shlex.split(args.client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
A
Azat Khuzhin 已提交
562
        (stdout, stderr) = clickhouse_proc.communicate(b"EXISTS TABLE test.hits")
P
proller 已提交
563
        if clickhouse_proc.returncode != 0:
P
proller 已提交
564
            raise CalledProcessError(clickhouse_proc.returncode, args.client, stderr)
565

A
Azat Khuzhin 已提交
566
        return stdout.startswith(b'1')
567

568
    if not check_server_started(args.client, args.server_check_retries):
M
typo  
myrrc 已提交
569 570 571 572
        raise Exception(
            "Server is not responding. Cannot execute 'SELECT 1' query. \
            Note: if you are using unbundled mode, you also have to specify -c option.")

A
alesapin 已提交
573
    build_flags = collect_build_flags(args.client)
M
typo  
myrrc 已提交
574

A
alesapin 已提交
575 576 577
    if args.use_skip_list:
        tests_to_skip_from_list = collect_tests_to_skip(args.skip_list_path, build_flags)
    else:
A
Fixes  
alesapin 已提交
578
        tests_to_skip_from_list = set([])
A
alesapin 已提交
579

A
alesapin 已提交
580 581 582 583
    if args.skip:
        args.skip = set(args.skip) | tests_to_skip_from_list
    else:
        args.skip = tests_to_skip_from_list
584

585 586 587
    if args.use_skip_list and not args.sequential:
        args.sequential = collect_sequential_list(args.skip_list_path)

P
proller 已提交
588 589 590
    base_dir = os.path.abspath(args.queries)
    tmp_dir = os.path.abspath(args.tmp)

591
    # Keep same default values as in queries/shell_config.sh
P
proller 已提交
592
    os.environ.setdefault("CLICKHOUSE_BINARY", args.binary)
593
    #os.environ.setdefault("CLICKHOUSE_CLIENT", args.client)
594
    os.environ.setdefault("CLICKHOUSE_CONFIG", args.configserver)
P
proller 已提交
595 596
    if args.configclient:
        os.environ.setdefault("CLICKHOUSE_CONFIG_CLIENT", args.configclient)
P
proller 已提交
597
    os.environ.setdefault("CLICKHOUSE_TMP", tmp_dir)
598

599
    # Force to print server warnings in stderr
600
    # Shell scripts could change logging level
601 602
    os.environ.setdefault("CLICKHOUSE_CLIENT_SERVER_LOGS_LEVEL", server_logs_level)

A
Alexey Milovidov 已提交
603 604 605 606
    # This code is bad as the time is not monotonic
    if args.global_time_limit:
        stop_time = time() + args.global_time_limit

607
    if args.zookeeper is None:
A
Azat Khuzhin 已提交
608
        code, out = subprocess.getstatusoutput(args.extract_from_config + " --try --config " + args.configserver + ' --key zookeeper | grep . | wc -l')
609 610 611 612 613 614
        try:
            if int(out) > 0:
                args.zookeeper = True
            else:
                args.zookeeper = False
        except ValueError:
615 616 617
            args.zookeeper = False

    if args.shard is None:
A
Azat Khuzhin 已提交
618
        code, out = subprocess.getstatusoutput(args.extract_from_config + " --try --config " + args.configserver + ' --key listen_host | grep -E "127.0.0.2|::"')
P
proller 已提交
619
        if out:
P
proller 已提交
620
            args.shard = True
P
proller 已提交
621 622
        else:
            args.shard = False
623

624
    if args.database and args.database != "test":
A
Azat Khuzhin 已提交
625 626
        clickhouse_proc_create = Popen(shlex.split(args.client), stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True)
        clickhouse_proc_create.communicate(("CREATE DATABASE IF NOT EXISTS " + args.database + get_db_engine(args)))
627

A
Azat Khuzhin 已提交
628 629
    clickhouse_proc_create = Popen(shlex.split(args.client), stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True)
    clickhouse_proc_create.communicate(("CREATE DATABASE IF NOT EXISTS test" + get_db_engine(args)))
630 631 632 633 634

    def is_test_from_dir(suite_dir, case):
        case_file = os.path.join(suite_dir, case)
        (name, ext) = os.path.splitext(case)
        return os.path.isfile(case_file) and (ext == '.sql' or ext == '.sh' or ext == '.py')
P
proller 已提交
635

636 637 638 639 640
    def sute_key_func(item):
       if args.order == 'random':
             return random()

       if -1 == item.find('_'):
A
Azat Khuzhin 已提交
641
           return 99998, ''
642 643 644 645 646 647

       prefix, suffix = item.split('_', 1)

       try:
           return int(prefix), suffix
       except ValueError:
A
Azat Khuzhin 已提交
648
           return 99997, ''
649

650
    total_tests_run = 0
651
    for suite in sorted(os.listdir(base_dir), key=sute_key_func):
652 653 654 655 656 657 658
        if SERVER_DIED:
            break

        suite_dir = os.path.join(base_dir, suite)
        suite_re_obj = re.search('^[0-9]+_(.*)$', suite)
        if not suite_re_obj: #skip .gitignore and so on
            continue
659 660 661 662 663

        suite_tmp_dir = os.path.join(tmp_dir, suite)
        if not os.path.exists(suite_tmp_dir):
            os.makedirs(suite_tmp_dir)

664 665 666
        suite = suite_re_obj.group(1)
        if os.path.isdir(suite_dir):

P
proller 已提交
667
            if 'stateful' in suite and not args.no_stateful and not is_data_present():
668
                print("Won't run stateful tests because test data wasn't loaded.")
669
                continue
670 671 672
            if 'stateless' in suite and args.no_stateless:
                print("Won't run stateless tests because they were manually disabled.")
                continue
673 674 675
            if 'stateful' in suite and args.no_stateful:
                print("Won't run stateful tests because they were manually disabled.")
                continue
676

P
proller 已提交
677 678 679
            # Reverse sort order: we want run newest test first.
            # And not reverse subtests
            def key_func(item):
680
                if args.order == 'random':
681
                    return random()
682 683 684 685 686 687

                reverse = 1 if args.order == 'asc' else -1

                if -1 == item.find('_'):
                    return 99998

P
proller 已提交
688
                prefix, suffix = item.split('_', 1)
689 690 691 692 693 694

                try:
                    return reverse * int(prefix), suffix
                except ValueError:
                    return 99997

695
            all_tests = os.listdir(suite_dir)
A
Azat Khuzhin 已提交
696
            all_tests = [case for case in all_tests if is_test_from_dir(suite_dir, case)]
697 698
            if args.test:
                all_tests = [t for t in all_tests if any([re.search(r, t) for r in args.test])]
699
            all_tests.sort(key=key_func)
700

A
alesapin 已提交
701
            jobs = args.jobs
702 703 704 705 706 707 708 709
            parallel_tests = []
            sequential_tests = []
            for test in all_tests:
                if any(s in test for s in args.sequential):
                    sequential_tests.append(test)
                else:
                    parallel_tests.append(test)

A
alesapin 已提交
710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727
            if jobs > 1 and len(parallel_tests) > 0:
                print("Found", len(parallel_tests), "parallel tests and", len(sequential_tests), "sequential tests")
                run_n, run_total = args.parallel.split('/')
                run_n = float(run_n)
                run_total = float(run_total)
                tests_n = len(parallel_tests)
                if run_total > tests_n:
                    run_total = tests_n

                if jobs > tests_n:
                    jobs = tests_n
                if jobs > run_total:
                    run_total = jobs

                batch_size = len(parallel_tests) // jobs
                parallel_tests_array = []
                for i in range(0, len(parallel_tests), batch_size):
                    parallel_tests_array.append((parallel_tests[i:i+batch_size], suite, suite_dir, suite_tmp_dir))
P
proller 已提交
728

729
                with closing(multiprocessing.Pool(processes=jobs)) as pool:
730
                    pool.map(run_tests_array, parallel_tests_array)
731

A
alesapin 已提交
732
                run_tests_array((sequential_tests, suite, suite_dir, suite_tmp_dir))
733 734
                total_tests_run += len(sequential_tests) + len(parallel_tests)
            else:
A
alesapin 已提交
735
                run_tests_array((all_tests, suite, suite_dir, suite_tmp_dir))
736
                total_tests_run += len(all_tests)
737

738
    if args.hung_check:
739 740 741 742 743 744 745 746

        # Some queries may execute in background for some time after test was finished. This is normal.
        for n in range(1, 60):
            processlist = get_processlist(args.client)
            if not processlist:
                break
            sleep(1)

747 748 749 750 751 752 753 754
        if processlist:
            print(colored("\nFound hung queries in processlist:", args, "red", attrs=["bold"]))
            print(processlist)

            clickhouse_tcp_port = os.getenv("CLICKHOUSE_PORT_TCP", '9000')
            server_pid = get_server_pid(clickhouse_tcp_port)
            if server_pid:
                print("\nLocated ClickHouse server process {} listening at TCP port {}".format(server_pid, clickhouse_tcp_port))
755 756 757 758 759

                # It does not work in Sandbox
                #print("\nCollecting stacktraces from system.stacktraces table:")
                #print(get_stacktraces_from_clickhouse(args.client))

760 761
                print("\nCollecting stacktraces from all running threads with gdb:")
                print(get_stacktraces_from_gdb(server_pid))
762
            else:
763 764 765 766 767 768 769
                print(
                    colored(
                        "\nUnable to locate ClickHouse server process listening at TCP port {}. "
                        "It must have crashed or exited prematurely!".format(clickhouse_tcp_port),
                        args, "red", attrs=["bold"]))

            exit_code = 1
770
        else:
771
            print(colored("\nNo queries hung.", args, "green", attrs=["bold"]))
772

773 774 775 776
    if total_tests_run == 0:
        print("No tests were run.")
        sys.exit(1)

777 778
    sys.exit(exit_code)

779

A
alesapin 已提交
780
def find_binary(name):
781 782
    if os.path.exists(name) and os.access(name, os.X_OK):
        return True
A
alesapin 已提交
783 784 785 786 787 788
    paths = os.environ.get("PATH").split(':')
    for path in paths:
        if os.access(os.path.join(path, name), os.X_OK):
            return True

    # maybe it wasn't in PATH
P
proller 已提交
789 790 791 792 793 794
    if os.access(os.path.join('/usr/local/bin', name), os.X_OK):
        return True
    if os.access(os.path.join('/usr/bin', name), os.X_OK):
        return True
    return False

795

796
def get_additional_client_options(args):
797 798 799 800 801 802 803 804 805 806 807
    if args.client_option:
        return ' '.join('--' + option for option in args.client_option)

    return ''


def get_additional_client_options_url(args):
    if args.client_option:
        return '&'.join(args.client_option)

    return ''
808 809


A
alesapin 已提交
810 811 812 813 814 815
def collect_tests_to_skip(skip_list_path, build_flags):
    result = set([])
    if not os.path.exists(skip_list_path):
        return result

    with open(skip_list_path, 'r') as skip_list_file:
816 817 818
        content = skip_list_file.read()
        # allows to have comments in skip_list.json
        skip_dict = json.loads(json_minify(content))
A
alesapin 已提交
819 820
        for build_flag in build_flags:
            result |= set(skip_dict[build_flag])
A
alesapin 已提交
821 822 823 824

    if len(result) > 0:
        print("Found file with skip-list {}, {} test will be skipped".format(skip_list_path, len(result)))

A
alesapin 已提交
825 826
    return result

827 828 829 830 831 832 833 834 835

def collect_sequential_list(skip_list_path):
    if not os.path.exists(skip_list_path):
        return set([])

    with open(skip_list_path, 'r') as skip_list_file:
        content = skip_list_file.read()
        # allows to have comments in skip_list.json
        skip_dict = json.loads(json_minify(content))
A
alesapin 已提交
836 837
        if 'parallel' in skip_dict:
            return skip_dict['parallel']
838 839 840
        return set([])


841
if __name__ == '__main__':
842 843 844
    parser=ArgumentParser(description='ClickHouse functional tests')
    parser.add_argument('-q', '--queries', help='Path to queries dir')
    parser.add_argument('--tmp', help='Path to tmp dir')
M
typo  
myrrc 已提交
845 846 847 848 849 850 851

    parser.add_argument('-b', '--binary', default='clickhouse',
        help='Path to clickhouse (if bundled, clickhouse-server otherwise) binary or name of binary in PATH')

    parser.add_argument('-c', '--client',
        help='Path to clickhouse-client (if unbundled, useless otherwise) binary of name of binary in PATH')

852 853 854 855 856
    parser.add_argument('--extract_from_config', help='extract-from-config program')
    parser.add_argument('--configclient', help='Client config (if you use not default ports)')
    parser.add_argument('--configserver', default= '/etc/clickhouse-server/config.xml', help='Preprocessed server config')
    parser.add_argument('-o', '--output', help='Output xUnit compliant test report directory')
    parser.add_argument('-t', '--timeout', type=int, default=600, help='Timeout for each test case in seconds')
A
Alexey Milovidov 已提交
857
    parser.add_argument('--global_time_limit', type=int, help='Stop if executing more than specified time (after current test finished)')
858
    parser.add_argument('test', nargs='*', help='Optional test case name regex')
859 860
    parser.add_argument('-d', '--disabled', action='store_true', default=False, help='Also run disabled tests')
    parser.add_argument('--stop', action='store_true', default=None, dest='stop', help='Stop on network errors')
861
    parser.add_argument('--order', default='desc', choices=['asc', 'desc', 'random'], help='Run order')
862 863
    parser.add_argument('--testname', action='store_true', default=None, dest='testname', help='Make query with test name before test run')
    parser.add_argument('--hung-check', action='store_true', default=False)
A
alesapin 已提交
864
    parser.add_argument('--force-color', action='store_true', default=False)
865
    parser.add_argument('--database', help='Database for tests (random name test_XXXXXX by default)')
P
proller 已提交
866
    parser.add_argument('--parallel', default='1/1', help='One parallel test run number/total')
P
proller 已提交
867
    parser.add_argument('-j', '--jobs', default=1, nargs='?', type=int, help='Run all tests in parallel')
868
    parser.add_argument('-U', '--unified', default=3, type=int, help='output NUM lines of unified context')
869
    parser.add_argument('-r', '--server-check-retries', default=30, type=int, help='Num of tries to execute SELECT 1 before tests started')
A
alesapin 已提交
870 871
    parser.add_argument('--skip-list-path', help="Path to skip-list file")
    parser.add_argument('--use-skip-list', action='store_true', default=False, help="Use skip list to skip tests if found")
872
    parser.add_argument('--db-engine', help='Database engine name')
873 874

    parser.add_argument('--no-stateless', action='store_true', help='Disable all stateless tests')
875
    parser.add_argument('--no-stateful', action='store_true', help='Disable all stateful tests')
876
    parser.add_argument('--skip', nargs='+', help="Skip these tests")
877
    parser.add_argument('--sequential', nargs='+', help="Run these tests sequentially even if --parallel specified")
878
    parser.add_argument('--no-long', action='store_false', dest='no_long', help='Do not run long tests')
879
    parser.add_argument('--client-option', nargs='+', help='Specify additional client argument')
880
    parser.add_argument('--print-time', action='store_true', dest='print_time', help='Print test time')
881 882 883 884 885 886
    group=parser.add_mutually_exclusive_group(required=False)
    group.add_argument('--zookeeper', action='store_true', default=None, dest='zookeeper', help='Run zookeeper related tests')
    group.add_argument('--no-zookeeper', action='store_false', default=None, dest='zookeeper', help='Do not run zookeeper related tests')
    group=parser.add_mutually_exclusive_group(required=False)
    group.add_argument('--shard', action='store_true', default=None, dest='shard', help='Run sharding related tests (required to clickhouse-server listen 127.0.0.2 127.0.0.3)')
    group.add_argument('--no-shard', action='store_false', default=None, dest='shard', help='Do not run shard related tests')
887 888

    args = parser.parse_args()
889

A
Alexander Kuzmenkov 已提交
890
    if args.queries and not os.path.isdir(args.queries):
A
Alexander Kuzmenkov 已提交
891
        print("Cannot access the specified directory with queries (" + args.queries + ")", file=sys.stderr)
A
Alexander Kuzmenkov 已提交
892 893 894 895
        exit(1)

    # Autodetect the directory with queries if not specified
    if args.queries is None:
896
        args.queries = 'queries'
A
Alexander Kuzmenkov 已提交
897 898 899 900 901 902 903

    if not os.path.isdir(args.queries):
        # If we're running from the repo
        args.queries = os.path.join(os.path.dirname(os.path.abspath( __file__ )), 'queries')

    if not os.path.isdir(args.queries):
        # Next we're going to try some system directories, don't write 'stdout' files into them.
904 905
        if args.tmp is None:
            args.tmp = '/tmp/clickhouse-test'
A
Alexander Kuzmenkov 已提交
906 907 908 909 910 911 912

        args.queries = '/usr/local/share/clickhouse-test/queries'

    if not os.path.isdir(args.queries):
        args.queries = '/usr/share/clickhouse-test/queries'

    if not os.path.isdir(args.queries):
913
        print("Failed to detect path to the queries directory. Please specify it with '--queries' option.", file=sys.stderr)
A
akuzm 已提交
914
        exit(1)
A
alesapin 已提交
915

A
Alexander Kuzmenkov 已提交
916 917
    print("Using queries from '" + args.queries + "' directory")

A
alesapin 已提交
918 919
    if args.skip_list_path is None:
        args.skip_list_path = os.path.join(args.queries, 'skip_list.json')
A
alesapin 已提交
920

921 922 923
    if args.sequential is None:
        args.sequential = set([])

P
proller 已提交
924 925
    if args.tmp is None:
        args.tmp = args.queries
926
    if args.client is None:
A
alesapin 已提交
927
        if find_binary(args.binary + '-client'):
P
proller 已提交
928
            args.client = args.binary + '-client'
M
typo  
myrrc 已提交
929 930

            print("Using " + args.client + " as client program (expecting unbundled mode)")
A
alesapin 已提交
931
        elif find_binary(args.binary):
P
proller 已提交
932
            args.client = args.binary + ' client'
M
typo  
myrrc 已提交
933 934

            print("Using " + args.client + " as client program (expecting bundled mode)")
935
        else:
M
typo  
myrrc 已提交
936
            print("No 'clickhouse' or 'clickhouse-client' client binary found", file=sys.stderr)
A
alesapin 已提交
937 938 939
            parser.print_help()
            exit(1)

P
proller 已提交
940
        if args.configclient:
941 942 943 944 945
            args.client += ' --config-file=' + args.configclient
        if os.getenv("CLICKHOUSE_HOST"):
            args.client += ' --host=' + os.getenv("CLICKHOUSE_HOST")
        if os.getenv("CLICKHOUSE_PORT_TCP"):
            args.client += ' --port=' + os.getenv("CLICKHOUSE_PORT_TCP")
946 947 948
        if os.getenv("CLICKHOUSE_DATABASE"):
            args.client += ' --database=' + os.getenv("CLICKHOUSE_DATABASE")

949
    if args.client_option:
950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965
        # Set options for client
        if 'CLICKHOUSE_CLIENT_OPT' in os.environ:
           os.environ['CLICKHOUSE_CLIENT_OPT'] += ' '
        else:
           os.environ['CLICKHOUSE_CLIENT_OPT'] = ''

        os.environ['CLICKHOUSE_CLIENT_OPT'] += get_additional_client_options(args)

        # Set options for curl
        if 'CLICKHOUSE_URL_PARAMS' in os.environ:
           os.environ['CLICKHOUSE_URL_PARAMS'] += '&'
        else:
           os.environ['CLICKHOUSE_URL_PARAMS'] = ''

        os.environ['CLICKHOUSE_URL_PARAMS'] += get_additional_client_options_url(args)

P
proller 已提交
966 967 968 969 970 971
    if args.extract_from_config is None:
        if os.access(args.binary + '-extract-from-config', os.X_OK):
            args.extract_from_config = args.binary + '-extract-from-config'
        else:
            args.extract_from_config = args.binary + ' extract-from-config'

P
proller 已提交
972
    if args.jobs is None:
973
        args.jobs = multiprocessing.cpu_count()
P
proller 已提交
974

975
    main(args)