run_tests.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. #!/usr/bin/env python3
  2. # Copyright 2012-2017 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. import os
  13. import sys
  14. import time
  15. import shutil
  16. import subprocess
  17. import tempfile
  18. import platform
  19. from mesonbuild import mesonlib
  20. from mesonbuild import mesonmain
  21. from mesonbuild import mlog
  22. from mesonbuild.environment import detect_ninja
  23. from io import StringIO
  24. from enum import Enum
  25. from glob import glob
  26. Backend = Enum('Backend', 'ninja vs xcode')
  27. if 'MESON_EXE' in os.environ:
  28. import shlex
  29. meson_exe = shlex.split(os.environ['MESON_EXE'])
  30. else:
  31. meson_exe = None
  32. if mesonlib.is_windows() or mesonlib.is_cygwin():
  33. exe_suffix = '.exe'
  34. else:
  35. exe_suffix = ''
  36. def get_backend_args_for_dir(backend, builddir):
  37. '''
  38. Visual Studio backend needs to be given the solution to build
  39. '''
  40. if backend is Backend.vs:
  41. sln_name = glob(os.path.join(builddir, '*.sln'))[0]
  42. return [os.path.split(sln_name)[-1]]
  43. return []
  44. def find_vcxproj_with_target(builddir, target):
  45. import re, fnmatch
  46. t, ext = os.path.splitext(target)
  47. if ext:
  48. p = '<TargetName>{}</TargetName>\s*<TargetExt>\{}</TargetExt>'.format(t, ext)
  49. else:
  50. p = '<TargetName>{}</TargetName>'.format(t)
  51. for root, dirs, files in os.walk(builddir):
  52. for f in fnmatch.filter(files, '*.vcxproj'):
  53. f = os.path.join(builddir, f)
  54. with open(f, 'r', encoding='utf-8') as o:
  55. if re.search(p, o.read(), flags=re.MULTILINE):
  56. return f
  57. raise RuntimeError('No vcxproj matching {!r} in {!r}'.format(p, builddir))
  58. def get_builddir_target_args(backend, builddir, target):
  59. dir_args = []
  60. if not target:
  61. dir_args = get_backend_args_for_dir(backend, builddir)
  62. if target is None:
  63. return dir_args
  64. if backend is Backend.vs:
  65. vcxproj = find_vcxproj_with_target(builddir, target)
  66. target_args = [vcxproj]
  67. elif backend is Backend.xcode:
  68. target_args = ['-target', target]
  69. elif backend is Backend.ninja:
  70. target_args = [target]
  71. else:
  72. raise AssertionError('Unknown backend: {!r}'.format(backend))
  73. return target_args + dir_args
  74. def get_backend_commands(backend, debug=False):
  75. install_cmd = []
  76. uninstall_cmd = []
  77. if backend is Backend.vs:
  78. cmd = ['msbuild']
  79. clean_cmd = cmd + ['/target:Clean']
  80. test_cmd = cmd + ['RUN_TESTS.vcxproj']
  81. elif backend is Backend.xcode:
  82. cmd = ['xcodebuild']
  83. clean_cmd = cmd + ['-alltargets', 'clean']
  84. test_cmd = cmd + ['-target', 'RUN_TESTS']
  85. elif backend is Backend.ninja:
  86. # We need at least 1.6 because of -w dupbuild=err
  87. cmd = [detect_ninja('1.6'), '-w', 'dupbuild=err', '-d', 'explain']
  88. if cmd[0] is None:
  89. raise RuntimeError('Could not find Ninja v1.6 or newer')
  90. if debug:
  91. cmd += ['-v']
  92. clean_cmd = cmd + ['clean']
  93. test_cmd = cmd + ['test', 'benchmark']
  94. install_cmd = cmd + ['install']
  95. uninstall_cmd = cmd + ['uninstall']
  96. else:
  97. raise AssertionError('Unknown backend: {!r}'.format(backend))
  98. return cmd, clean_cmd, test_cmd, install_cmd, uninstall_cmd
  99. def ensure_backend_detects_changes(backend):
  100. # We're using a ninja with QuLogic's patch for sub-1s resolution timestamps
  101. # and not running on HFS+ which only stores dates in seconds:
  102. # https://developer.apple.com/legacy/library/technotes/tn/tn1150.html#HFSPlusDates
  103. # FIXME: Upgrade Travis image to Apple FS when that becomes available
  104. if 'MESON_FIXED_NINJA' in os.environ and not mesonlib.is_osx():
  105. return
  106. # This is needed to increase the difference between build.ninja's
  107. # timestamp and the timestamp of whatever you changed due to a Ninja
  108. # bug: https://github.com/ninja-build/ninja/issues/371
  109. if backend is Backend.ninja:
  110. time.sleep(1)
  111. def get_fake_options(prefix):
  112. import argparse
  113. opts = argparse.Namespace()
  114. opts.cross_file = None
  115. opts.wrap_mode = None
  116. opts.prefix = prefix
  117. return opts
  118. def should_run_linux_cross_tests():
  119. return shutil.which('arm-linux-gnueabihf-gcc-7') and not platform.machine().lower().startswith('arm')
  120. def run_configure_inprocess(meson_command, commandlist):
  121. old_stdout = sys.stdout
  122. sys.stdout = mystdout = StringIO()
  123. old_stderr = sys.stderr
  124. sys.stderr = mystderr = StringIO()
  125. try:
  126. returncode = mesonmain.run(commandlist, meson_command)
  127. finally:
  128. sys.stdout = old_stdout
  129. sys.stderr = old_stderr
  130. return returncode, mystdout.getvalue(), mystderr.getvalue()
  131. def run_configure_external(full_command):
  132. pc, o, e = mesonlib.Popen_safe(full_command)
  133. return pc.returncode, o, e
  134. def run_configure(meson_command, commandlist):
  135. global meson_exe
  136. if meson_exe:
  137. return run_configure_external(meson_exe + commandlist)
  138. return run_configure_inprocess(meson_command, commandlist)
  139. def print_system_info():
  140. print(mlog.bold('System information.').get_text(mlog.colorize_console))
  141. print('Architecture:', platform.architecture())
  142. print('Machine:', platform.machine())
  143. print('Platform:', platform.system())
  144. print('Processor:', platform.processor())
  145. print('System:', platform.system())
  146. print('')
  147. if __name__ == '__main__':
  148. print_system_info()
  149. # Enable coverage early...
  150. enable_coverage = '--cov' in sys.argv
  151. if enable_coverage:
  152. os.makedirs('.coverage', exist_ok=True)
  153. sys.argv.remove('--cov')
  154. import coverage
  155. coverage.process_startup()
  156. returncode = 0
  157. # Iterate over list in reverse order to find the last --backend arg
  158. backend = Backend.ninja
  159. for arg in reversed(sys.argv[1:]):
  160. if arg.startswith('--backend'):
  161. if arg.startswith('--backend=vs'):
  162. backend = Backend.vs
  163. elif arg == '--backend=xcode':
  164. backend = Backend.xcode
  165. break
  166. # Running on a developer machine? Be nice!
  167. if not mesonlib.is_windows() and not mesonlib.is_haiku() and 'TRAVIS' not in os.environ:
  168. os.nice(20)
  169. # Appveyor sets the `platform` environment variable which completely messes
  170. # up building with the vs2010 and vs2015 backends.
  171. #
  172. # Specifically, MSBuild reads the `platform` environment variable to set
  173. # the configured value for the platform (Win32/x64/arm), which breaks x86
  174. # builds.
  175. #
  176. # Appveyor setting this also breaks our 'native build arch' detection for
  177. # Windows in environment.py:detect_windows_arch() by overwriting the value
  178. # of `platform` set by vcvarsall.bat.
  179. #
  180. # While building for x86, `platform` should be unset.
  181. if 'APPVEYOR' in os.environ and os.environ['arch'] == 'x86':
  182. os.environ.pop('platform')
  183. # Run tests
  184. print(mlog.bold('Running unittests.').get_text(mlog.colorize_console))
  185. print()
  186. # Can't pass arguments to unit tests, so set the backend to use in the environment
  187. env = os.environ.copy()
  188. env['MESON_UNIT_TEST_BACKEND'] = backend.name
  189. with tempfile.TemporaryDirectory() as td:
  190. # Enable coverage on all subsequent processes.
  191. if enable_coverage:
  192. with open(os.path.join(td, 'usercustomize.py'), 'w') as f:
  193. f.write('import coverage\n'
  194. 'coverage.process_startup()\n')
  195. env['COVERAGE_PROCESS_START'] = '.coveragerc'
  196. env['PYTHONPATH'] = os.pathsep.join([td] + env.get('PYTHONPATH', []))
  197. returncode += subprocess.call(mesonlib.python_command + ['run_unittests.py', '-v'], env=env)
  198. # Ubuntu packages do not have a binary without -6 suffix.
  199. if should_run_linux_cross_tests():
  200. print(mlog.bold('Running cross compilation tests.').get_text(mlog.colorize_console))
  201. print()
  202. returncode += subprocess.call(mesonlib.python_command + ['run_cross_test.py', 'cross/ubuntu-armhf.txt'],
  203. env=env)
  204. returncode += subprocess.call(mesonlib.python_command + ['run_project_tests.py'] + sys.argv[1:], env=env)
  205. sys.exit(returncode)