run_tests.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. #!/usr/bin/env python3
  2. # Copyright 2012-2015 The Meson development team
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS,
  9. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. # See the License for the specific language governing permissions and
  11. # limitations under the License.
  12. from glob import glob
  13. import os, subprocess, shutil, sys, platform, signal
  14. from io import StringIO
  15. import sys
  16. import environment
  17. import mesonlib
  18. import mlog
  19. import meson, meson_test
  20. import argparse
  21. import xml.etree.ElementTree as ET
  22. import time
  23. from meson import backendlist
  24. class TestResult:
  25. def __init__(self, msg, stdo, stde, conftime=0, buildtime=0, testtime=0):
  26. self.msg = msg
  27. self.stdo = stdo
  28. self.stde = stde
  29. self.conftime = conftime
  30. self.buildtime = buildtime
  31. self.testtime = testtime
  32. passing_tests = 0
  33. failing_tests = 0
  34. skipped_tests = 0
  35. print_debug = 'MESON_PRINT_TEST_OUTPUT' in os.environ
  36. test_build_dir = 'work area'
  37. install_dir = os.path.join(os.path.split(os.path.abspath(__file__))[0], 'install dir')
  38. meson_command = './meson.py'
  39. class StopException(Exception):
  40. def __init__(self):
  41. super().__init__('Stopped by user')
  42. stop = False
  43. def stop_handler(signal, frame):
  44. global stop
  45. stop = True
  46. signal.signal(signal.SIGINT, stop_handler)
  47. signal.signal(signal.SIGTERM, stop_handler)
  48. #unity_flags = ['--unity']
  49. unity_flags = []
  50. backend_flags = None
  51. compile_commands = None
  52. test_commands = None
  53. install_commands = None
  54. def setup_commands(backend):
  55. global backend_flags, compile_commands, test_commands, install_commands
  56. msbuild_exe = shutil.which('msbuild')
  57. if backend == 'vs2010' or (backend is None and msbuild_exe is not None):
  58. backend_flags = ['--backend=vs2010']
  59. compile_commands = ['msbuild']
  60. test_commands = ['msbuild', 'RUN_TESTS.vcxproj']
  61. install_commands = []
  62. elif backend == 'xcode' or (backend is None and mesonlib.is_osx()):
  63. backend_flags = ['--backend=xcode']
  64. compile_commands = ['xcodebuild']
  65. test_commands = ['xcodebuild', '-target', 'RUN_TESTS']
  66. install_commands = []
  67. else:
  68. backend_flags = []
  69. ninja_command = environment.detect_ninja()
  70. if ninja_command is None:
  71. raise RuntimeError('Could not find Ninja executable.')
  72. if print_debug:
  73. compile_commands = [ninja_command, '-v']
  74. else:
  75. compile_commands = [ninja_command]
  76. test_commands = [ninja_command, 'test']
  77. install_commands = [ninja_command, 'install']
  78. def platform_fix_filename(fname):
  79. if platform.system() == 'Darwin':
  80. if fname.endswith('.so'):
  81. return fname[:-2] + 'dylib'
  82. return fname.replace('.so.', '.dylib.')
  83. elif platform.system() == 'Windows':
  84. if fname.endswith('.so'):
  85. (p, f) = os.path.split(fname)
  86. f = f[3:-2] + 'dll'
  87. return os.path.join(p, f)
  88. if fname.endswith('.a'):
  89. return fname[:-1] + 'lib'
  90. return fname
  91. def validate_install(srcdir, installdir):
  92. if platform.system() == 'Windows':
  93. # Don't really know how Windows installs should work
  94. # so skip.
  95. return ''
  96. info_file = os.path.join(srcdir, 'installed_files.txt')
  97. expected = {}
  98. found = {}
  99. if os.path.exists(info_file):
  100. for line in open(info_file):
  101. expected[platform_fix_filename(line.strip())] = True
  102. for root, _, files in os.walk(installdir):
  103. for fname in files:
  104. found_name = os.path.join(root, fname)[len(installdir)+1:]
  105. found[found_name] = True
  106. expected = set(expected)
  107. found = set(found)
  108. missing = expected - found
  109. for fname in missing:
  110. return 'Expected file %s missing.' % fname
  111. extra = found - expected
  112. for fname in extra:
  113. return 'Found extra file %s.' % fname
  114. return ''
  115. def log_text_file(logfile, testdir, msg, stdo, stde):
  116. global passing_tests, failing_tests, stop
  117. if msg != '':
  118. print('Fail:', msg)
  119. failing_tests += 1
  120. else:
  121. print('Success')
  122. passing_tests += 1
  123. logfile.write('%s\nstdout\n\n---\n' % testdir)
  124. logfile.write(stdo)
  125. logfile.write('\n\n---\n\nstderr\n\n---\n')
  126. logfile.write(stde)
  127. logfile.write('\n\n---\n\n')
  128. if print_debug:
  129. print(stdo)
  130. print(stde, file=sys.stderr)
  131. if stop:
  132. raise StopException()
  133. def run_configure_inprocess(commandlist):
  134. old_stdout = sys.stdout
  135. sys.stdout = mystdout = StringIO()
  136. old_stderr = sys.stderr
  137. sys.stderr = mystderr = StringIO()
  138. returncode = meson.run(commandlist)
  139. sys.stdout = old_stdout
  140. sys.stderr = old_stderr
  141. return (returncode, mystdout.getvalue(), mystderr.getvalue())
  142. def run_test_inprocess(testdir):
  143. old_stdout = sys.stdout
  144. sys.stdout = mystdout = StringIO()
  145. old_stderr = sys.stderr
  146. sys.stderr = mystderr = StringIO()
  147. old_cwd = os.getcwd()
  148. os.chdir(testdir)
  149. returncode = meson_test.run(['meson-private/meson_test_setup.dat'])
  150. sys.stdout = old_stdout
  151. sys.stderr = old_stderr
  152. os.chdir(old_cwd)
  153. return (returncode, mystdout.getvalue(), mystderr.getvalue())
  154. def run_test(testdir, should_succeed):
  155. global compile_commands
  156. mlog.shutdown() # Close the log file because otherwise Windows wets itself.
  157. shutil.rmtree(test_build_dir)
  158. shutil.rmtree(install_dir)
  159. os.mkdir(test_build_dir)
  160. os.mkdir(install_dir)
  161. print('Running test: ' + testdir)
  162. gen_start = time.time()
  163. gen_command = [meson_command, '--prefix', '/usr', '--libdir', 'lib', testdir, test_build_dir]\
  164. + unity_flags + backend_flags
  165. (returncode, stdo, stde) = run_configure_inprocess(gen_command)
  166. gen_time = time.time() - gen_start
  167. if not should_succeed:
  168. if returncode != 0:
  169. return TestResult('', stdo, stde, gen_time)
  170. return TestResult('Test that should have failed succeeded', stdo, stde, gen_time)
  171. if returncode != 0:
  172. return TestResult('Generating the build system failed.', stdo, stde, gen_time)
  173. if 'msbuild' in compile_commands[0]:
  174. sln_name = glob(os.path.join(test_build_dir, '*.sln'))[0]
  175. comp = compile_commands + [os.path.split(sln_name)[-1]]
  176. else:
  177. comp = compile_commands
  178. build_start = time.time()
  179. pc = subprocess.Popen(comp, cwd=test_build_dir,
  180. stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  181. (o, e) = pc.communicate()
  182. build_time = time.time() - build_start
  183. stdo += o.decode('utf-8')
  184. stde += e.decode('utf-8')
  185. if pc.returncode != 0:
  186. return TestResult('Compiling source code failed.', stdo, stde, gen_time, build_time)
  187. test_start = time.time()
  188. # Note that we don't test that running e.g. 'ninja test' actually
  189. # works. One hopes that this is a common enough happening that
  190. # it is picked up immediately on development.
  191. (returncode, tstdo, tstde) = run_test_inprocess(test_build_dir)
  192. test_time = time.time() - test_start
  193. stdo += tstdo
  194. stde += tstde
  195. if returncode != 0:
  196. return TestResult('Running unit tests failed.', stdo, stde, gen_time, build_time, test_time)
  197. if len(install_commands) == 0:
  198. print("Skipping install test")
  199. return TestResult('', '', '', gen_time, build_time, test_time)
  200. else:
  201. env = os.environ.copy()
  202. env['DESTDIR'] = install_dir
  203. pi = subprocess.Popen(install_commands, cwd=test_build_dir, env=env,
  204. stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  205. (o, e) = pi.communicate()
  206. stdo += o.decode('utf-8')
  207. stde += e.decode('utf-8')
  208. if pi.returncode != 0:
  209. return TestResult('Running install failed.', stdo, stde, gen_time, build_time, test_time)
  210. return TestResult(validate_install(testdir, install_dir), stdo, stde, gen_time, build_time, test_time)
  211. def gather_tests(testdir):
  212. tests = [t.replace('\\', '/').split('/', 2)[2] for t in glob(os.path.join(testdir, '*'))]
  213. testlist = [(int(t.split()[0]), t) for t in tests]
  214. testlist.sort()
  215. tests = [os.path.join(testdir, t[1]) for t in testlist]
  216. return tests
  217. def detect_tests_to_run():
  218. all_tests = []
  219. all_tests.append(('common', gather_tests('test cases/common'), False))
  220. all_tests.append(('failing', gather_tests('test cases/failing'), False))
  221. all_tests.append(('prebuilt object', gather_tests('test cases/prebuilt object'), False))
  222. all_tests.append(('platform-osx', gather_tests('test cases/osx'), False if mesonlib.is_osx() else True))
  223. all_tests.append(('platform-windows', gather_tests('test cases/windows'), False if mesonlib.is_windows() else True))
  224. all_tests.append(('platform-linux', gather_tests('test cases/linuxlike'), False if not (mesonlib.is_osx() or mesonlib.is_windows()) else True))
  225. all_tests.append(('framework', gather_tests('test cases/frameworks'), False if not mesonlib.is_osx() and not mesonlib.is_windows() else True))
  226. all_tests.append(('java', gather_tests('test cases/java'), False if not mesonlib.is_osx() and shutil.which('javac') else True))
  227. all_tests.append(('C#', gather_tests('test cases/csharp'), False if shutil.which('mcs') else True))
  228. all_tests.append(('vala', gather_tests('test cases/vala'), False if shutil.which('valac') else True))
  229. all_tests.append(('rust', gather_tests('test cases/rust'), False if shutil.which('rustc') else True))
  230. all_tests.append(('objective c', gather_tests('test cases/objc'), False if not mesonlib.is_windows() else True))
  231. all_tests.append(('fortran', gather_tests('test cases/fortran'), False if shutil.which('gfortran') else True))
  232. return all_tests
  233. def run_tests():
  234. all_tests = detect_tests_to_run()
  235. logfile = open('meson-test-run.txt', 'w', encoding="utf_8")
  236. junit_root = ET.Element('testsuites')
  237. conf_time = 0
  238. build_time = 0
  239. test_time = 0
  240. try:
  241. os.mkdir(test_build_dir)
  242. except OSError:
  243. pass
  244. try:
  245. os.mkdir(install_dir)
  246. except OSError:
  247. pass
  248. for name, test_cases, skipped in all_tests:
  249. current_suite = ET.SubElement(junit_root, 'testsuite', {'name' : name, 'tests' : str(len(test_cases))})
  250. if skipped:
  251. print('\nNot running %s tests.\n' % name)
  252. else:
  253. print('\nRunning %s tests.\n' % name)
  254. for t in test_cases:
  255. # Jenkins screws us over by automatically sorting test cases by name
  256. # and getting it wrong by not doing logical number sorting.
  257. (testnum, testbase) = os.path.split(t)[-1].split(' ', 1)
  258. testname = '%.3d %s' % (int(testnum), testbase)
  259. if skipped:
  260. current_test = ET.SubElement(current_suite, 'testcase', {'name' : testname,
  261. 'classname' : name})
  262. ET.SubElement(current_test, 'skipped', {})
  263. global skipped_tests
  264. skipped_tests += 1
  265. else:
  266. ts = time.time()
  267. result = run_test(t, name != 'failing')
  268. te = time.time()
  269. conf_time += result.conftime
  270. build_time += result.buildtime
  271. test_time += result.testtime
  272. log_text_file(logfile, t, result.msg, result.stdo, result.stde)
  273. current_test = ET.SubElement(current_suite, 'testcase', {'name' : testname,
  274. 'classname' : name,
  275. 'time' : '%.3f' % (te - ts)})
  276. if result.msg != '':
  277. ET.SubElement(current_test, 'failure', {'message' : result.msg})
  278. stdoel = ET.SubElement(current_test, 'system-out')
  279. stdoel.text = result.stdo
  280. stdeel = ET.SubElement(current_test, 'system-err')
  281. stdeel.text = result.stde
  282. print("\nTotal configuration time: %.2fs" % conf_time)
  283. print("Total build time: %.2fs" % build_time)
  284. print("Total test time: %.2fs" % test_time)
  285. ET.ElementTree(element=junit_root).write('meson-test-run.xml', xml_declaration=True, encoding='UTF-8')
  286. def check_file(fname):
  287. linenum = 1
  288. for line in open(fname, 'rb').readlines():
  289. if b'\t' in line:
  290. print("File %s contains a literal tab on line %d. Only spaces are permitted." % (fname, linenum))
  291. sys.exit(1)
  292. if b'\r' in line:
  293. print("File %s contains DOS line ending on line %d. Only unix-style line endings are permitted." % (fname, linenum))
  294. sys.exit(1)
  295. linenum += 1
  296. def check_format():
  297. for (root, _, files) in os.walk('.'):
  298. for file in files:
  299. if file.endswith('.py') or file.endswith('.build'):
  300. fullname = os.path.join(root, file)
  301. check_file(fullname)
  302. def generate_prebuilt_object():
  303. source = 'test cases/prebuilt object/1 basic/source.c'
  304. objectbase = 'test cases/prebuilt object/1 basic/prebuilt.'
  305. if shutil.which('cl'):
  306. objectfile = objectbase + 'obj'
  307. cmd = ['cl', '/nologo', '/Fo'+objectfile, '/c', source]
  308. else:
  309. if mesonlib.is_windows():
  310. objectfile = objectbase + 'obj'
  311. else:
  312. objectfile = objectbase + 'o'
  313. if shutil.which('cc'):
  314. cmd = 'cc'
  315. elif shutil.which('gcc'):
  316. cmd = 'gcc'
  317. else:
  318. raise RuntimeError("Could not find C compiler.")
  319. cmd = [cmd, '-c', source, '-o', objectfile]
  320. subprocess.check_call(cmd)
  321. return objectfile
  322. if __name__ == '__main__':
  323. parser = argparse.ArgumentParser(description="Run the test suite of Meson.")
  324. parser.add_argument('--backend', default=None, dest='backend',
  325. choices = backendlist)
  326. options = parser.parse_args()
  327. setup_commands(options.backend)
  328. script_dir = os.path.split(__file__)[0]
  329. if script_dir != '':
  330. os.chdir(script_dir)
  331. check_format()
  332. pbfile = generate_prebuilt_object()
  333. try:
  334. run_tests()
  335. except StopException:
  336. pass
  337. os.unlink(pbfile)
  338. print('\nTotal passed tests:', passing_tests)
  339. print('Total failed tests:', failing_tests)
  340. print('Total skipped tests:', skipped_tests)
  341. sys.exit(failing_tests)