clickhouse-test 33.0 KB
Newer Older
1
#!/usr/bin/env python2
2
from __future__ import print_function
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 19 20
from datetime import datetime
from time import sleep
from errno import ESRCH
21 22 23 24
try:
    import termcolor
except ImportError:
    termcolor = None
25
from random import random
P
proller 已提交
26
import commands
P
proller 已提交
27
import multiprocessing
P
proller 已提交
28
from contextlib import closing
29

30

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

38

39 40 41 42 43 44 45 46
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:
            return unichr(int(s, base))
        return default
47 48 49
    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)
50 51
    return s

52 53 54

def run_single_test(args, ext, server_logs_level, client_options, case_file, stdout_file, stderr_file):

55 56
    # print(client_options)

57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
    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())

        clickhouse_proc_create = Popen(shlex.split(args.client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
        clickhouse_proc_create.communicate("CREATE DATABASE " + database)

        os.environ["CLICKHOUSE_DATABASE"] = database

76
    params = {
77
        'client': args.client + ' --database=' + database,
78 79 80 81 82 83 84
        'logs_level': server_logs_level,
        'options': client_options,
        'test': case_file,
        'stdout': stdout_file,
        'stderr': stderr_file,
    }

85 86
    pattern = '{test} > {stdout} 2> {stderr}'

A
alesapin 已提交
87
    if ext == '.sql':
88 89 90 91
        pattern = "{client} --send_logs_level={logs_level} --testmode --multiquery {options} < " + pattern

    command = pattern.format(**params)
    #print(command)
A
alesapin 已提交
92

93
    proc = Popen(command, shell=True, env=os.environ)
A
alesapin 已提交
94 95 96 97
    start_time = datetime.now()
    while (datetime.now() - start_time).total_seconds() < args.timeout and proc.poll() is None:
        sleep(0.01)

98
    if not args.database:
99
        clickhouse_proc_create = Popen(shlex.split(args.client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
100
        clickhouse_proc_create.communicate("DROP DATABASE " + database)
101

102 103
    total_time = (datetime.now() - start_time).total_seconds()

A
alexey-milovidov 已提交
104
    # Normalize randomized database names in stdout, stderr files.
105 106
    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))
107

A
alesapin 已提交
108 109 110 111 112
    stdout = open(stdout_file, 'r').read() if os.path.exists(stdout_file) else ''
    stdout = unicode(stdout, errors='replace', encoding='utf-8')
    stderr = open(stderr_file, 'r').read() if os.path.exists(stderr_file) else ''
    stderr = unicode(stderr, errors='replace', encoding='utf-8')

113
    return proc, stdout, stderr, total_time
A
alesapin 已提交
114

115

A
alesapin 已提交
116 117 118
def need_retry(stderr):
    return any(msg in stderr for msg in MESSAGES_TO_RETRY)

119

120 121 122 123 124 125
def get_processlist(client_cmd):
    try:
        return subprocess.check_output("{} --query 'SHOW PROCESSLIST FORMAT Vertical'".format(client_cmd), shell=True)
    except:
        return "" #  server seems dead

126

A
alesapin 已提交
127
# collect server stacktraces using gdb
128
def get_stacktraces_from_gdb(server_pid):
129
    cmd = "gdb -batch -ex 'thread apply all backtrace' -p {}".format(server_pid)
130 131 132
    try:
        return subprocess.check_output(cmd, shell=True)
    except Exception as ex:
133 134 135
        return "Error occured while receiving stack traces from gdb: {}".format(str(ex))


A
alesapin 已提交
136
# collect server stacktraces from system.stack_trace table
137
# it does not work in Sandbox
138 139
def get_stacktraces_from_clickhouse(client):
    try:
140
        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)
141 142
    except Exception as ex:
        return "Error occured while receiving stack traces from client: {}".format(str(ex))
143

144

145
def get_server_pid(server_tcp_port):
146
    cmd = "lsof -i tcp:{port} -s tcp:LISTEN -Fp | awk '/^p[0-9]+$/{{print substr($0, 2)}}'".format(port=server_tcp_port)
147 148 149
    try:
        output = subprocess.check_output(cmd, shell=True)
        if output:
150
            return int(output)
151 152 153 154 155
        else:
            return None # server dead
    except Exception as ex:
        return None

156

P
proller 已提交
157
def colored(text, args, color=None, on_color=None, attrs=None):
158
       if termcolor and (sys.stdout.isatty() or args.force_color):
P
proller 已提交
159 160 161 162
           return termcolor.colored(text, color, on_color, attrs)
       else:
           return text

163

P
proller 已提交
164 165 166
SERVER_DIED = False
exit_code = 0

167

168
# def run_tests_array(all_tests, suite, suite_dir, suite_tmp_dir, run_total):
169 170
def run_tests_array(all_tests_with_params):
    all_tests, suite, suite_dir, suite_tmp_dir, run_total = all_tests_with_params
P
proller 已提交
171
    global exit_code
P
proller 已提交
172
    global SERVER_DIED
173

P
proller 已提交
174 175 176 177 178 179 180 181 182 183 184 185 186
    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
187

188 189
    client_options = get_additional_client_options(args)

190 191 192 193
    def print_test_time(test_time):
        if args.print_time:
            print(" {0:.2f} sec.".format(test_time), end='')

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

197
    for case in all_tests:
P
proller 已提交
198 199
        if SERVER_DIED:
            break
A
alesapin 已提交
200

P
proller 已提交
201 202 203 204
        case_file = os.path.join(suite_dir, case)
        (name, ext) = os.path.splitext(case)

        try:
A
alesapin 已提交
205
            sys.stdout.flush()
P
proller 已提交
206 207 208 209 210
            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
211 212
            elif not args.zookeeper and ('zookeeper' in name
                    or 'replica' in name):
P
proller 已提交
213 214
                print(MSG_SKIPPED + " - no zookeeper")
                skipped_total += 1
A
akuzm 已提交
215 216 217
            elif not args.shard and ('shard' in name
                    or 'distributed' in name
                    or 'global' in name):
P
proller 已提交
218 219
                print(MSG_SKIPPED + " - no shard")
                skipped_total += 1
C
CurtizJ 已提交
220 221 222 223 224
            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 已提交
225 226 227 228 229 230 231 232 233 234 235
                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:
236
                        clickhouse_proc = Popen(shlex.split(args.client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
P
proller 已提交
237 238 239 240 241 242
                        clickhouse_proc.communicate("SELECT 'Running test {suite}/{case} from pid={pid}';".format(pid = os.getpid(), case = case, suite = suite))

                    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'

243
                    proc, stdout, stderr, total_time = run_single_test(args, ext, server_logs_level, client_options, case_file, stdout_file, stderr_file)
P
proller 已提交
244 245 246 247 248 249 250 251
                    if proc.returncode is None:
                        try:
                            proc.kill()
                        except OSError as e:
                            if e.errno != ESRCH:
                                raise

                        failures += 1
252 253 254
                        print(MSG_FAIL, end='')
                        print_test_time(total_time)
                        print(" - Timeout!")
P
proller 已提交
255 256 257
                    else:
                        counter = 1
                        while proc.returncode != 0 and need_retry(stderr):
258
                            proc, stdout, stderr, total_time = run_single_test(args, ext, server_logs_level, client_options, case_file, stdout_file, stderr_file)
P
proller 已提交
259 260 261 262 263 264 265 266
                            sleep(2**counter)
                            counter += 1
                            if counter > 6:
                                break

                        if proc.returncode != 0:
                            failures += 1
                            failures_chain += 1
267 268 269
                            print(MSG_FAIL, end='')
                            print_test_time(total_time)
                            print(" - return code {}".format(proc.returncode))
P
proller 已提交
270 271

                            if stderr:
272
                                print(stderr.encode('utf-8'))
P
proller 已提交
273

A
Alexey Milovidov 已提交
274 275 276 277
                            # Stop on fatal errors like segmentation fault. They are send to client via logs.
                            if ' <Fatal> ' in stderr:
                                SERVER_DIED = True

P
proller 已提交
278 279 280 281 282 283
                            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

                        elif stderr:
                            failures += 1
                            failures_chain += 1
284 285 286
                            print(MSG_FAIL, end='')
                            print_test_time(total_time)
                            print(" - having stderror:\n{}".format(stderr.encode('utf-8')))
P
proller 已提交
287 288 289
                        elif 'Exception' in stdout:
                            failures += 1
                            failures_chain += 1
290 291 292
                            print(MSG_FAIL, end='')
                            print_test_time(total_time)
                            print(" - having exception:\n{}".format(stdout.encode('utf-8')))
P
proller 已提交
293
                        elif not os.path.isfile(reference_file):
294 295 296
                            print(MSG_UNKNOWN, end='')
                            print_test_time(total_time)
                            print(" - no reference file")
P
proller 已提交
297 298 299 300
                        else:
                            result_is_different = subprocess.call(['diff', '-q', reference_file, stdout_file], stdout = PIPE)

                            if result_is_different:
301
                                diff = Popen(['diff', '-U', str(args.unified), reference_file, stdout_file], stdout = PIPE).communicate()[0]
P
proller 已提交
302
                                failures += 1
303 304 305
                                print(MSG_FAIL, end='')
                                print_test_time(total_time)
                                print(" - result differs with reference:\n{}".format(diff))
P
proller 已提交
306 307 308
                            else:
                                passed_total += 1
                                failures_chain = 0
309 310 311
                                print(MSG_OK, end='')
                                print_test_time(total_time)
                                print()
P
proller 已提交
312 313 314 315
                                if os.path.exists(stdout_file):
                                    os.remove(stdout_file)
                                if os.path.exists(stderr_file):
                                    os.remove(stderr_file)
316
        except KeyboardInterrupt as e:
P
proller 已提交
317
            print(colored("Break tests execution", args, "red"))
318
            raise e
P
proller 已提交
319 320 321 322
        except:
            import traceback
            exc_type, exc_value, tb = sys.exc_info()
            failures += 1
323
            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 已提交
324 325 326 327 328 329 330

        if failures_chain >= 20:
            break

    failures_total = failures_total + failures

    if failures_total > 0:
331
        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 已提交
332 333 334
        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 已提交
335

336

P
proller 已提交
337 338
server_logs_level = "warning"

339

340
def check_server_started(client, retry_count):
341 342
    print("Connecting to ClickHouse server...", end='')
    sys.stdout.flush()
343 344 345
    while retry_count > 0:
        clickhouse_proc = Popen(shlex.split(client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
        (stdout, stderr) = clickhouse_proc.communicate("SELECT 1")
346 347 348 349 350 351 352 353 354 355

        if clickhouse_proc.returncode == 0 and stdout.startswith("1"):
            print(" OK")
            sys.stdout.flush()
            return True

        if clickhouse_proc.returncode == 210:
            # Connection refused, retry
            print('.', end = '')
            sys.stdout.flush()
356 357
            retry_count -= 1
            sleep(0.5)
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
            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()
376 377 378 379

    return False


A
alesapin 已提交
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
class BuildFlags(object):
    THREAD = 'thread-sanitizer'
    ADDRESS = 'address-sanitizer'
    UNDEFINED = 'ub-sanitizer'
    MEMORY = 'memory-sanitizer'
    DEBUG = 'debug-build'
    UNBUNDLED = 'unbundled-build'
    RELEASE = 'release-build'
    DATABASE_ATOMIC = 'database-atomic'
    POLYMORPHIC_PARTS = 'polymorphic-parts'


def collect_build_flags(client):
    clickhouse_proc = Popen(shlex.split(client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
    (stdout, stderr) = clickhouse_proc.communicate("SELECT value FROM system.build_options WHERE name = 'CXX_FLAGS'")
    result = []

    if clickhouse_proc.returncode == 0:
        if '-fsanitize=thread' in stdout:
            result.append(BuildFlags.THREAD)
        elif '-fsanitize=address' in stdout:
            result.append(BuildFlags.ADDRESS)
        elif '-fsanitize=undefined' in stdout:
            result.append(BuildFlags.UNDEFINED)
        elif '-fsanitize=memory' in stdout:
            result.append(BuildFlags.MEMORY)
    else:
        raise Exception("Cannot get inforamtion about build from server errorcode {}, stderr {}".format(clickhouse_proc.returncode, stderr))

    clickhouse_proc = Popen(shlex.split(client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
    (stdout, stderr) = clickhouse_proc.communicate("SELECT value FROM system.build_options WHERE name = 'BUILD_TYPE'")

    if clickhouse_proc.returncode == 0:
        if 'Debug' in stdout:
            result.append(BuildFlags.DEBUG)
        elif 'RelWithDebInfo' in stdout or 'Release' in stdout:
            result.append(BuildFlags.RELEASE)
    else:
        raise Exception("Cannot get inforamtion about build from server errorcode {}, stderr {}".format(clickhouse_proc.returncode, stderr))

    clickhouse_proc = Popen(shlex.split(client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
    (stdout, stderr) = clickhouse_proc.communicate("SELECT value FROM system.build_options WHERE name = 'UNBUNDLED'")

    if clickhouse_proc.returncode == 0:
A
alesapin 已提交
424
        if 'ON' in stdout or '1' in stdout:
A
alesapin 已提交
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
            result.append(BuildFlags.UNBUNDLED)
    else:
        raise Exception("Cannot get inforamtion about build from server errorcode {}, stderr {}".format(clickhouse_proc.returncode, stderr))

    clickhouse_proc = Popen(shlex.split(client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
    (stdout, stderr) = clickhouse_proc.communicate("SELECT value FROM system.settings WHERE name = 'default_database_engine'")

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

    clickhouse_proc = Popen(shlex.split(client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
    (stdout, stderr) = clickhouse_proc.communicate("SELECT value FROM system.merge_tree_settings WHERE name = 'min_bytes_for_wide_part'")

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

    return result


P
proller 已提交
450 451 452 453
def main(args):
    global SERVER_DIED
    global exit_code
    global server_logs_level
A
alesapin 已提交
454

455
    def is_data_present():
P
proller 已提交
456 457
        clickhouse_proc = Popen(shlex.split(args.client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
        (stdout, stderr) = clickhouse_proc.communicate("EXISTS TABLE test.hits")
P
proller 已提交
458
        if clickhouse_proc.returncode != 0:
P
proller 已提交
459
            raise CalledProcessError(clickhouse_proc.returncode, args.client, stderr)
460 461 462

        return stdout.startswith('1')

463 464
    if not check_server_started(args.client, args.server_check_retries):
        raise Exception("clickhouse-server is not responding. Cannot execute 'SELECT 1' query.")
A
alesapin 已提交
465
    build_flags = collect_build_flags(args.client)
A
alesapin 已提交
466 467 468
    if args.use_skip_list:
        tests_to_skip_from_list = collect_tests_to_skip(args.skip_list_path, build_flags)
    else:
A
Fixes  
alesapin 已提交
469
        tests_to_skip_from_list = set([])
A
alesapin 已提交
470

A
alesapin 已提交
471 472 473 474
    if args.skip:
        args.skip = set(args.skip) | tests_to_skip_from_list
    else:
        args.skip = tests_to_skip_from_list
475

P
proller 已提交
476 477 478
    base_dir = os.path.abspath(args.queries)
    tmp_dir = os.path.abspath(args.tmp)

479
    # Keep same default values as in queries/shell_config.sh
P
proller 已提交
480
    os.environ.setdefault("CLICKHOUSE_BINARY", args.binary)
481
    #os.environ.setdefault("CLICKHOUSE_CLIENT", args.client)
482
    os.environ.setdefault("CLICKHOUSE_CONFIG", args.configserver)
P
proller 已提交
483 484
    if args.configclient:
        os.environ.setdefault("CLICKHOUSE_CONFIG_CLIENT", args.configclient)
P
proller 已提交
485
    os.environ.setdefault("CLICKHOUSE_TMP", tmp_dir)
486

487
    # Force to print server warnings in stderr
488
    # Shell scripts could change logging level
489 490
    os.environ.setdefault("CLICKHOUSE_CLIENT_SERVER_LOGS_LEVEL", server_logs_level)

491
    if args.zookeeper is None:
492
        code, out = commands.getstatusoutput(args.extract_from_config + " --try --config " + args.configserver + ' --key zookeeper | grep . | wc -l')
493 494 495 496 497 498
        try:
            if int(out) > 0:
                args.zookeeper = True
            else:
                args.zookeeper = False
        except ValueError:
499 500 501
            args.zookeeper = False

    if args.shard is None:
P
proller 已提交
502
        code, out = commands.getstatusoutput(args.extract_from_config + " --try --config " + args.configserver + ' --key listen_host | grep -E "127.0.0.2|::"')
P
proller 已提交
503
        if out:
P
proller 已提交
504
            args.shard = True
P
proller 已提交
505 506
        else:
            args.shard = False
507

508
    if args.database and args.database != "test":
509
        clickhouse_proc_create = Popen(shlex.split(args.client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
510 511 512 513
        clickhouse_proc_create.communicate("CREATE DATABASE IF NOT EXISTS " + args.database)

    clickhouse_proc_create = Popen(shlex.split(args.client), stdin=PIPE, stdout=PIPE, stderr=PIPE)
    clickhouse_proc_create.communicate("CREATE DATABASE IF NOT EXISTS test")
514 515 516 517 518

    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 已提交
519

520 521 522 523 524 525 526 527 528 529 530 531 532 533
    def sute_key_func(item):
       if args.order == 'random':
             return random()

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

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

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

534
    total_tests_run = 0
535
    for suite in sorted(os.listdir(base_dir), key=sute_key_func):
536 537 538 539 540 541 542
        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
543 544 545 546 547

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

548 549 550
        suite = suite_re_obj.group(1)
        if os.path.isdir(suite_dir):

P
proller 已提交
551
            if 'stateful' in suite and not args.no_stateful and not is_data_present():
552
                print("Won't run stateful tests because test data wasn't loaded.")
553
                continue
554 555 556
            if 'stateless' in suite and args.no_stateless:
                print("Won't run stateless tests because they were manually disabled.")
                continue
557 558 559
            if 'stateful' in suite and args.no_stateful:
                print("Won't run stateful tests because they were manually disabled.")
                continue
560

P
proller 已提交
561 562 563
            # Reverse sort order: we want run newest test first.
            # And not reverse subtests
            def key_func(item):
564
                if args.order == 'random':
565
                    return random()
566 567 568 569 570 571

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

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

P
proller 已提交
572
                prefix, suffix = item.split('_', 1)
573 574 575 576 577 578

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

579 580
            all_tests = os.listdir(suite_dir)
            all_tests = filter(lambda case: is_test_from_dir(suite_dir, case), all_tests)
581 582
            if args.test:
                all_tests = [t for t in all_tests if any([re.search(r, t) for r in args.test])]
583
            all_tests.sort(key=key_func)
584

585 586 587
            run_n, run_total = args.parallel.split('/')
            run_n = float(run_n)
            run_total = float(run_total)
P
proller 已提交
588 589 590 591 592
            tests_n = len(all_tests)
            if run_total > tests_n:
                run_total = tests_n
            if run_n > run_total:
                continue
593

P
proller 已提交
594
            jobs = args.jobs
P
proller 已提交
595 596
            if jobs > tests_n:
                jobs = tests_n
P
proller 已提交
597 598
            if jobs > run_total:
                run_total = jobs
P
proller 已提交
599

A
alesapin 已提交
600
            batch_size = len(all_tests) / jobs
P
proller 已提交
601
            all_tests_array = []
A
alesapin 已提交
602 603
            for i in range(0, len(all_tests), batch_size):
                all_tests_array.append((all_tests[i:i+batch_size], suite, suite_dir, suite_tmp_dir, run_total))
P
proller 已提交
604

P
proller 已提交
605
            if jobs > 1:
606 607
                with closing(multiprocessing.Pool(processes=jobs)) as pool:
                    pool.map(run_tests_array, all_tests_array)
P
proller 已提交
608
            else:
609
                run_tests_array(all_tests_array[int(run_n)-1])
610

611 612
            total_tests_run += tests_n

613
    if args.hung_check:
614 615 616 617 618 619 620 621

        # 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)

622 623 624 625 626 627 628 629
        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))
630 631 632 633 634

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

635 636
                print("\nCollecting stacktraces from all running threads with gdb:")
                print(get_stacktraces_from_gdb(server_pid))
637
            else:
638 639 640 641 642 643 644
                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
645
        else:
646
            print(colored("\nNo queries hung.", args, "green", attrs=["bold"]))
647

648 649 650 651
    if total_tests_run == 0:
        print("No tests were run.")
        sys.exit(1)

652 653
    sys.exit(exit_code)

654

A
alesapin 已提交
655
def find_binary(name):
656 657
    if os.path.exists(name) and os.access(name, os.X_OK):
        return True
A
alesapin 已提交
658 659 660 661 662 663
    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 已提交
664 665 666 667 668 669
    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

670

671
def get_additional_client_options(args):
672 673 674 675 676 677 678 679 680 681 682
    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 ''
683 684


A
alesapin 已提交
685 686 687 688 689 690 691 692 693
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:
        skip_dict = json.load(skip_list_file)
        for build_flag in build_flags:
            result |= set(skip_dict[build_flag])
A
alesapin 已提交
694 695 696 697

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

A
alesapin 已提交
698 699
    return result

700
if __name__ == '__main__':
701 702 703
    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')
704
    parser.add_argument('-b', '--binary', default='clickhouse', help='Path to clickhouse binary or name of binary in PATH')
705 706 707 708 709 710
    parser.add_argument('-c', '--client', help='Client program')
    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')
711
    parser.add_argument('test', nargs='*', help='Optional test case name regex')
712 713
    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')
714
    parser.add_argument('--order', default='desc', choices=['asc', 'desc', 'random'], help='Run order')
715 716
    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 已提交
717
    parser.add_argument('--force-color', action='store_true', default=False)
718
    parser.add_argument('--database', help='Database for tests (random name test_XXXXXX by default)')
P
proller 已提交
719
    parser.add_argument('--parallel', default='1/1', help='One parallel test run number/total')
P
proller 已提交
720
    parser.add_argument('-j', '--jobs', default=1, nargs='?', type=int, help='Run all tests in parallel')
721
    parser.add_argument('-U', '--unified', default=3, type=int, help='output NUM lines of unified context')
722
    parser.add_argument('-r', '--server-check-retries', default=30, type=int, help='Num of tries to execute SELECT 1 before tests started')
A
alesapin 已提交
723 724
    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")
725 726

    parser.add_argument('--no-stateless', action='store_true', help='Disable all stateless tests')
727
    parser.add_argument('--no-stateful', action='store_true', help='Disable all stateful tests')
728 729
    parser.add_argument('--skip', nargs='+', help="Skip these tests")
    parser.add_argument('--no-long', action='store_false', dest='no_long', help='Do not run long tests')
730
    parser.add_argument('--client-option', nargs='+', help='Specify additional client argument')
731
    parser.add_argument('--print-time', action='store_true', dest='print_time', help='Print test time')
732 733 734 735 736 737
    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')
738 739

    args = parser.parse_args()
740 741 742

    if args.queries is None and os.path.isdir('queries'):
        args.queries = 'queries'
P
proller 已提交
743
    elif args.queries is None:
P
proller 已提交
744 745 746 747
        if (os.path.isdir('/usr/local/share/clickhouse-test/queries')):
            args.queries = '/usr/local/share/clickhouse-test/queries'
        if (args.queries is None and os.path.isdir('/usr/share/clickhouse-test/queries')):
            args.queries = '/usr/share/clickhouse-test/queries'
748 749
        if args.tmp is None:
            args.tmp = '/tmp/clickhouse-test'
A
akuzm 已提交
750
    if args.queries is None:
751
        print("Failed to detect path to the queries directory. Please specify it with '--queries' option.", file=sys.stderr)
A
akuzm 已提交
752
        exit(1)
A
alesapin 已提交
753

A
alesapin 已提交
754 755
    if args.skip_list_path is None:
        args.skip_list_path = os.path.join(args.queries, 'skip_list.json')
A
alesapin 已提交
756

P
proller 已提交
757 758
    if args.tmp is None:
        args.tmp = args.queries
759
    if args.client is None:
A
alesapin 已提交
760
        if find_binary(args.binary + '-client'):
P
proller 已提交
761
            args.client = args.binary + '-client'
A
alesapin 已提交
762
        elif find_binary(args.binary):
P
proller 已提交
763
            args.client = args.binary + ' client'
764
        else:
765
            print("No 'clickhouse' binary found in PATH", file=sys.stderr)
A
alesapin 已提交
766 767 768
            parser.print_help()
            exit(1)

P
proller 已提交
769
        if args.configclient:
770 771 772 773 774
            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")
775 776 777
        if os.getenv("CLICKHOUSE_DATABASE"):
            args.client += ' --database=' + os.getenv("CLICKHOUSE_DATABASE")

778
    if args.client_option:
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
        # 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 已提交
795 796 797 798 799 800
    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 已提交
801
    if args.jobs is None:
802
        args.jobs = multiprocessing.cpu_count()
P
proller 已提交
803

804
    main(args)