clickhouse-test 9.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#!/usr/bin/env python
import sys
import os
import os.path
import re
import lxml.etree as et

from argparse import ArgumentParser
from argparse import FileType
from pprint import pprint
from subprocess import check_call
from subprocess import Popen
from subprocess import PIPE
from subprocess import CalledProcessError
15 16 17
from datetime import datetime
from time import sleep
from errno import ESRCH
18
from termcolor import colored
19 20


21 22 23 24 25 26 27
OP_SQUARE_BRACKET = colored("[", attrs=['bold'])
CL_SQUARE_BRACKET = colored("]", attrs=['bold'])

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

29 30 31 32

def main(args):

	SERVER_DIED = False
33 34


35 36 37 38 39
	def is_data_present():
		proc = Popen(args.client, stdin=PIPE, stdout=PIPE, stderr=PIPE)
		(stdout, stderr) = proc.communicate("EXISTS TABLE test.hits")
		if proc.returncode != 0:
			raise CalledProcessError(proc.returncode, args.client, stderr)
40

41
		return stdout.startswith('1')
42 43


44 45 46 47 48 49 50 51 52 53 54 55
	def dump_report(destination, suite, test_case, report):
		if destination is not None:
			destination_file = os.path.join(destination, suite, test_case + ".xml")
			destination_dir = os.path.dirname(destination_file)
			if not os.path.exists(destination_dir):
				os.makedirs(destination_dir)
			with open(destination_file, 'w') as report_file:
				report_root = et.Element("testsuites", attrib = {'name': 'ClickHouse Tests'})
				report_suite = et.Element("testsuite", attrib = {"name": suite})
				report_suite.append(report)
				report_root.append(report_suite)
				report_file.write(et.tostring(report_root, encoding = "UTF-8", xml_declaration=True, pretty_print=True))
56 57


58 59 60 61 62 63
	if args.zookeeper is None:
		try:
			check_call(['grep', '-q', '<zookeeper', '/etc/clickhouse-server/config-preprocessed.xml'], )
			args.zookeeper = True
		except CalledProcessError:
			args.zookeeper = False
64

P
wip  
proller 已提交
65 66
	if args.shard is None:
		try:
P
wip  
proller 已提交
67
			check_call(['grep', '-qE', '"127.0.0.2|<listen_host>::</listen_host>"', '/etc/clickhouse-server/config-preprocessed.xml'], )
P
wip  
proller 已提交
68 69
			args.shard = True
		except CalledProcessError:
P
wip  
proller 已提交
70 71
			# TODO: false here after setting <listen_host>::1</listen_host>
			args.shard = True
P
wip  
proller 已提交
72

73
	base_dir = os.path.abspath(args.queries)
74

75
	failures_total = 0
76

77 78 79
	for suite in sorted(os.listdir(base_dir)):
		if SERVER_DIED:
			break
80

81
		suite_dir = os.path.join(base_dir, suite)
82 83 84 85
		suite_re_obj = re.search('^[0-9]+_(.*)$', suite)
		if not suite_re_obj: #skip .gitignore and so on
			continue
		suite = suite_re_obj.group(1)
86 87
		if os.path.isdir(suite_dir):
			print("\nRunning {} tests.\n".format(suite))
88

89 90 91 92 93 94 95 96
			failures = 0
			if 'stateful' in suite and not is_data_present():
				print("Won't run stateful tests because test data wasn't loaded. See README.txt.")
				continue

			for case in sorted(filter(lambda case: re.search(args.test, case) if args.test else True, os.listdir(suite_dir))):
				if SERVER_DIED:
					break
97

98
				case_file = os.path.join(suite_dir, case)
99
				if os.path.isfile(case_file) and (case.endswith('.sh') or case.endswith('.py') or case.endswith('.sql')):
100 101
					(name, ext) = os.path.splitext(case)
					report_testcase = et.Element("testcase", attrib = {"name": name})
102

E
Eugene Konkov 已提交
103
					print "{0:70}".format(name + ": "),
E
Eugene Konkov 已提交
104
					sys.stdout.flush()
105

106 107
					if not args.zookeeper and 'zookeeper' in name:
						report_testcase.append(et.Element("skipped", attrib = {"message": "no zookeeper"}))
108
						print(MSG_SKIPPED + " - no zookeeper")
P
wip  
proller 已提交
109 110 111
					elif not args.shard and 'shard' in name:
						report_testcase.append(et.Element("skipped", attrib = {"message": "no shard"}))
						print(MSG_SKIPPED + " - no shard")
112
					else:
113 114 115
						reference_file = os.path.join(suite_dir, name) + '.reference'
						stdout_file = os.path.join(suite_dir, name) + '.stdout'
						stderr_file = os.path.join(suite_dir, name) + '.stderr'
116

117
						if ext == '.sql':
118
							command = "{0} --multiquery < {1} > {2} 2> {3}".format(args.client, case_file, stdout_file, stderr_file)
119
						else:
120
							command = "{0} > {1} 2> {2}".format(case_file, stdout_file, stderr_file)
121

122 123 124 125
						proc = Popen(command, shell = True)
						start_time = datetime.now()
						while (datetime.now() - start_time).total_seconds() < args.timeout and proc.poll() is None:
							sleep(0)
126

127 128 129 130 131 132
						if proc.returncode is None:
							try:
								proc.kill()
							except OSError as e:
								if e.errno != ESRCH:
									raise
133

134 135
							failure = et.Element("failure", attrib = {"message": "Timeout"})
							report_testcase.append(failure)
136

137
							failures = failures + 1
138 139 140
							print("{0} - Timeout!".format(MSG_FAIL))
						else:
							stdout = open(stdout_file, 'r').read() if os.path.exists(stdout_file) else ''
141
							stdout = unicode(stdout, errors='replace', encoding='utf-8')
142
							stderr = open(stderr_file, 'r').read() if os.path.exists(stderr_file) else ''
143
							stderr = unicode(stderr, errors='replace', encoding='utf-8')
144

145 146 147
							if proc.returncode != 0:
								failure = et.Element("failure", attrib = {"message": "return code {}".format(proc.returncode)})
								report_testcase.append(failure)
148

149 150 151
								stdout_element = et.Element("system-out")
								stdout_element.text = et.CDATA(stdout)
								report_testcase.append(stdout_element)
152

153 154
								failures = failures + 1
								print("{0} - return code {1}".format(MSG_FAIL, proc.returncode))
155

156 157 158 159 160
								if stderr:
									stderr_element = et.Element("system-err")
									stderr_element.text = et.CDATA(stderr)
									report_testcase.append(stderr_element)
									print(stderr)
161

162 163
								if 'Connection refused' in stderr or 'Attempt to read after eof' in stderr:
									SERVER_DIED = True
164

165 166 167
							elif stderr:
								failure = et.Element("failure", attrib = {"message": "having stderror"})
								report_testcase.append(failure)
168

169 170 171
								stderr_element = et.Element("system-err")
								stderr_element.text = et.CDATA(stderr)
								report_testcase.append(stderr_element)
172

173
								failures = failures + 1
174
								print("{0} - having stderror:\n{1}".format(MSG_FAIL, stderr.encode('utf-8')))
175 176
							elif 'Exception' in stdout:
								failure = et.Element("error", attrib = {"message": "having exception"})
177
								report_testcase.append(failure)
178

179
								stdout_element = et.Element("system-out")
180
								stdout_element.text = et.CDATA(stdout)
181
								report_testcase.append(stdout_element)
182

183
								failures = failures + 1
184
								print("{0} - having exception:\n{1}".format(MSG_FAIL, stdout.encode('utf-8')))
185 186 187 188
							elif not os.path.isfile(reference_file):
								skipped = et.Element("skipped", attrib = {"message": "no reference file"})
								report_testcase.append(skipped)
								print("{0} - no reference file".format(MSG_UNKNOWN))
189
							else:
190
								(diff, _) = Popen(['diff', reference_file, stdout_file], stdout = PIPE).communicate()
191

192 193 194
								if diff:
									failure = et.Element("failure", attrib = {"message": "result differs with reference"})
									report_testcase.append(failure)
195

196 197 198
									stdout_element = et.Element("system-out")
									stdout_element.text = et.CDATA(diff)
									report_testcase.append(stdout_element)
199

200 201 202 203 204 205 206 207
									failures = failures + 1
									print("{0} - result differs with reference:\n{1}".format(MSG_FAIL, diff))
								else:
									print(MSG_OK)
									if os.path.exists(stdout_file):
										os.remove(stdout_file)
									if os.path.exists(stderr_file):
										os.remove(stderr_file)
208

209
					dump_report(args.output, suite, name, report_testcase)
210

211
			failures_total = failures_total + failures
212

213
	if failures_total > 0:
214
		print(colored("\nHaving {0} errors!".format(failures_total), "red", attrs=["bold"]))
215 216
		sys.exit(1)
	else:
217
		print(colored("\nAll tests passed.", "green", attrs=["bold"]))
218
		sys.exit(0)
219

220 221 222 223 224

if __name__ == '__main__':
	parser = ArgumentParser(description = 'ClickHouse functional tests')
	parser.add_argument('-q', '--queries', default = 'queries', help = 'Path to queries dir')
	parser.add_argument('-c', '--client', default = 'clickhouse-client', help = 'Client program')
225
	parser.add_argument('-o', '--output', help = 'Output xUnit compliant test report directory')
226
	parser.add_argument('-t', '--timeout', type = int, default = 600, help = 'Timeout for each test case in seconds')
227
	parser.add_argument('test', nargs = '?', help = 'Optional test case name regex')
228

229 230 231
	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')
P
wip  
proller 已提交
232
	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)')
233
	group.add_argument('--no-shard', action = 'store_false', default = None, dest = 'shard', help = 'Do not run shard related tests')
234

235
	args = parser.parse_args()
236

237
	main(args)