Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 

204 Zeilen
7.2 KiB

  1. import functools
  2. import os
  3. import subprocess
  4. import unittest
  5. import shutil
  6. import sys
  7. import pqclean
  8. import logging
  9. def run_subprocess(command, working_dir='.', env=None, expected_returncode=0):
  10. """
  11. Helper function to run a shell command and report success/failure
  12. depending on the exit status of the shell command.
  13. """
  14. if env is not None:
  15. env_ = os.environ.copy()
  16. env_.update(env)
  17. env = env_
  18. # Note we need to capture stdout/stderr from the subprocess,
  19. # then print it, which nose/unittest will then capture and
  20. # buffer appropriately
  21. print(working_dir + " > " + " ".join(command))
  22. result = subprocess.run(
  23. command,
  24. stdout=subprocess.PIPE,
  25. stderr=subprocess.STDOUT,
  26. cwd=working_dir,
  27. env=env,
  28. )
  29. print(result.stdout.decode('utf-8'))
  30. if expected_returncode is not None:
  31. assert result.returncode == expected_returncode, \
  32. "Got unexpected return code {}".format(result.returncode)
  33. else:
  34. return (result.returncode, result.stdout.decode('utf-8'))
  35. return result.stdout.decode('utf-8')
  36. def make(*args, working_dir='.', env=None, expected_returncode=0, **kwargs):
  37. """
  38. Runs a make target in the specified working directory
  39. Usage:
  40. make('clean', 'targetb', SCHEME='bla')
  41. """
  42. if os.name == 'nt':
  43. make_command = ['nmake', '/f', 'Makefile.Microsoft_nmake',
  44. '/NOLOGO', '/E']
  45. # we need SCHEME_UPPERCASE and IMPLEMENTATION_UPPERCASE with nmake
  46. for envvar in ['IMPLEMENTATION', 'SCHEME']:
  47. if envvar in kwargs:
  48. kwargs['{}_UPPERCASE'.format(envvar)] = (
  49. kwargs[envvar].upper().replace('-', ''))
  50. else:
  51. make_command = ['make']
  52. return run_subprocess(
  53. [
  54. *make_command,
  55. *['{}={}'.format(k, v) for k, v in kwargs.items()],
  56. *args,
  57. ],
  58. working_dir=working_dir,
  59. env=env,
  60. expected_returncode=expected_returncode,
  61. )
  62. def skip_windows(message="This test is not supported on Windows"):
  63. def wrapper(f):
  64. @functools.wraps(f)
  65. def skip_windows(*args, **kwargs):
  66. raise unittest.SkipTest(message)
  67. if os.name == 'nt':
  68. return skip_windows
  69. else:
  70. return f
  71. return wrapper
  72. def slow_test(f):
  73. @functools.wraps(f)
  74. def wrapper(*args, **kwargs):
  75. if ('CI' in os.environ and 'RUN_SLOW' not in os.environ and
  76. os.environ.get('TRAVIS_EVENT_TYPE') != 'cron'):
  77. raise unittest.SkipTest("Slow test skipped on CI run")
  78. return f(*args, **kwargs)
  79. return wrapper
  80. def ensure_available(executable):
  81. """
  82. Checks if a command is available.
  83. If a command MUST be available, because we are in a CI environment,
  84. raises an AssertionError.
  85. In the docker containers, on Travis and on Windows, CI=true is set.
  86. """
  87. path = shutil.which(executable)
  88. if path:
  89. return path
  90. # Installing clang-tidy on LLVM will be too much of a mess.
  91. if ((executable == 'clang-tidy' and sys.platform == 'darwin')
  92. or 'CI' not in os.environ):
  93. raise unittest.SkipTest(
  94. "{} is not available on PATH. Install it to run this test.{}"
  95. .format(executable, "" if not os.name == 'nt'
  96. else "On Windows, make sure to add it to PATH")
  97. )
  98. raise AssertionError("{} not available on CI".format(executable))
  99. def permit_test(testname, thing, *args, **kwargs):
  100. if 'PQCLEAN_ONLY_TESTS' in os.environ:
  101. if not(testname.lower() in os.environ['PQCLEAN_ONLY_TESTS'].lower().split(',')):
  102. return False
  103. if 'PQCLEAN_SKIP_TESTS' in os.environ:
  104. if testname.lower() in os.environ['PQCLEAN_SKIP_TESTS'].lower().split(','):
  105. return False
  106. if isinstance(thing, pqclean.Implementation):
  107. scheme = thing.scheme
  108. elif isinstance(thing, pqclean.Scheme):
  109. scheme = thing
  110. else:
  111. return True
  112. if 'PQCLEAN_ONLY_TYPES' in os.environ:
  113. if not(scheme.type.lower() in os.environ['PQCLEAN_ONLY_TYPES'].lower().split(',')):
  114. return False
  115. if 'PQCLEAN_SKIP_TYPES' in os.environ:
  116. if scheme.type.lower() in os.environ['PQCLEAN_SKIP_TYPES'].lower().split(','):
  117. return False
  118. if 'PQCLEAN_ONLY_SCHEMES' in os.environ:
  119. if not(scheme.name.lower() in os.environ['PQCLEAN_ONLY_SCHEMES'].lower().split(',')):
  120. return False
  121. if 'PQCLEAN_SKIP_SCHEMES' in os.environ:
  122. if scheme.name.lower() in os.environ['PQCLEAN_SKIP_SCHEMES'].lower().split(','):
  123. return False
  124. if 'PQCLEAN_ONLY_DIFF' in os.environ:
  125. if shutil.which('git') is not None:
  126. # if we're on a non-master branch, and the only changes are in schemes,
  127. # only run tests on those schemes
  128. branch_result = subprocess.run(
  129. ['git', 'status', '--porcelain=2', '--branch'],
  130. stdout=subprocess.PIPE,
  131. stderr=subprocess.STDOUT,
  132. cwd="..",
  133. )
  134. # ensure we're in a working directory
  135. if branch_result.returncode != 0:
  136. return True
  137. # ensure we're not on master branch
  138. for branch_line in branch_result.stdout.decode('utf-8').splitlines():
  139. tokens = branch_line.split(' ')
  140. if tokens[0] == '#' and tokens[1] == 'branch.head':
  141. if tokens[2] == 'master':
  142. return True
  143. # where are there changes?
  144. diff_result = subprocess.run(
  145. ['git', 'diff', '--name-only', 'origin/master'],
  146. stdout=subprocess.PIPE,
  147. stderr=subprocess.STDOUT
  148. )
  149. assert diff_result.returncode == 0, \
  150. "Got unexpected return code {}".format(diff_result.returncode)
  151. for diff_line in diff_result.stdout.decode('utf-8').splitlines():
  152. # don't skip test if there are any changes outside schemes
  153. if (not diff_line.startswith('crypto_kem') and
  154. not diff_line.startswith('crypto_sign') and
  155. not diff_line.startswith(os.path.join('test', 'duplicate_consistency'))):
  156. logging.info("Running all tests as there are changes "
  157. "outside of schemes")
  158. return True
  159. # do test if the scheme in question has been changed
  160. if diff_line.startswith(thing.path(base='')):
  161. return True
  162. # do test if the scheme's duplicate_consistency files have been changed
  163. if diff_line.startswith(os.path.join('test', 'duplicate_consistency', scheme.name.lower())):
  164. return True
  165. # there were no changes outside schemes, and the scheme in question had no diffs
  166. return False
  167. return True
  168. def filtered_test(func):
  169. funcname = func.__name__[len("check_"):]
  170. @functools.wraps(func)
  171. def wrapper(*args, **kwargs):
  172. if permit_test(funcname, *args, **kwargs):
  173. return func(*args, **kwargs)
  174. else:
  175. raise unittest.SkipTest("Test disabled by filter")
  176. return wrapper