No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import os
  2. import itertools
  3. from contextlib import contextmanager, ExitStack
  4. from pathlib import Path
  5. from typing import Iterable, Union, Any, Dict, NamedTuple, ContextManager
  6. import subprocess
  7. import shutil
  8. import pytest
  9. from . import fileutil
  10. CommandLineArg = Union[str, Path, int, float]
  11. CommandLineArg1 = Union[CommandLineArg, Iterable[CommandLineArg]]
  12. CommandLineArg2 = Union[CommandLineArg1, Iterable[CommandLineArg1]]
  13. CommandLineArg3 = Union[CommandLineArg2, Iterable[CommandLineArg2]]
  14. CommandLineArg4 = Union[CommandLineArg3, Iterable[CommandLineArg3]]
  15. CommandLine = Iterable[CommandLineArg4]
  16. def _flatten_cmd(cmd: CommandLine) -> Iterable[str]:
  17. if isinstance(cmd, (str, Path)):
  18. yield str(cmd)
  19. elif isinstance(cmd, (int, float)):
  20. yield str(cmd)
  21. elif hasattr(cmd, '__iter__'):
  22. each = (_flatten_cmd(arg) for arg in cmd) # type: ignore
  23. for item in each:
  24. yield from item
  25. else:
  26. assert False, f'Invalid command line element: {repr(cmd)}'
  27. class DDS:
  28. def __init__(self, dds_exe: Path, test_dir: Path, project_dir: Path,
  29. scope: ExitStack) -> None:
  30. self.dds_exe = dds_exe
  31. self.test_dir = test_dir
  32. self.source_root = project_dir
  33. self.scratch_dir = project_dir / '_test_scratch'
  34. self.scope = scope
  35. self.scope.callback(self.cleanup)
  36. @property
  37. def repo_dir(self) -> Path:
  38. return self.scratch_dir / 'repo'
  39. @property
  40. def deps_build_dir(self) -> Path:
  41. return self.scratch_dir / 'deps-build'
  42. @property
  43. def build_dir(self) -> Path:
  44. return self.scratch_dir / 'build'
  45. @property
  46. def lmi_path(self) -> Path:
  47. return self.scratch_dir / 'INDEX.lmi'
  48. def cleanup(self):
  49. if self.scratch_dir.exists():
  50. shutil.rmtree(self.scratch_dir)
  51. def run_unchecked(self, cmd: CommandLine, *,
  52. cwd: Path = None) -> subprocess.CompletedProcess:
  53. full_cmd = list(_flatten_cmd(itertools.chain([self.dds_exe], cmd)))
  54. return subprocess.run(
  55. full_cmd,
  56. cwd=cwd or self.source_root,
  57. # stdout=subprocess.PIPE,
  58. # stderr=subprocess.STDOUT,
  59. )
  60. def run(self, cmd: CommandLine, *,
  61. cwd: Path = None) -> subprocess.CompletedProcess:
  62. cmdline = list(_flatten_cmd(cmd))
  63. res = self.run_unchecked(cmd)
  64. if res.returncode != 0:
  65. raise subprocess.CalledProcessError(
  66. res.returncode, [self.dds_exe] + cmdline, res.stdout)
  67. return res
  68. @property
  69. def repo_dir_arg(self) -> str:
  70. return f'--repo-dir={self.repo_dir}'
  71. @property
  72. def project_dir_arg(self) -> str:
  73. return f'--project-dir={self.source_root}'
  74. def deps_ls(self) -> subprocess.CompletedProcess:
  75. return self.run(['deps', 'ls'])
  76. def deps_get(self) -> subprocess.CompletedProcess:
  77. return self.run([
  78. 'deps',
  79. 'get',
  80. self.repo_dir_arg,
  81. ])
  82. def deps_build(self, *,
  83. toolchain: str = None) -> subprocess.CompletedProcess:
  84. return self.run([
  85. 'deps',
  86. 'build',
  87. f'--toolchain={toolchain or self.default_builtin_toolchain}',
  88. self.repo_dir_arg,
  89. f'--deps-build-dir={self.deps_build_dir}',
  90. f'--lmi-path={self.lmi_path}',
  91. ])
  92. def build(self,
  93. *,
  94. toolchain: str = None,
  95. apps: bool = True,
  96. warnings: bool = True,
  97. tests: bool = True,
  98. export: bool = False) -> subprocess.CompletedProcess:
  99. return self.run([
  100. 'build',
  101. f'--out={self.build_dir}',
  102. ['--tests'] if tests else [],
  103. ['--apps'] if apps else [],
  104. ['--warnings'] if warnings else [],
  105. ['--export'] if export else [],
  106. f'--toolchain={toolchain or self.default_builtin_toolchain}',
  107. self.project_dir_arg,
  108. ])
  109. def sdist_create(self) -> subprocess.CompletedProcess:
  110. return self.run([
  111. 'sdist',
  112. 'create',
  113. self.project_dir_arg,
  114. f'--out={self.build_dir / "stuff.sds"}',
  115. ])
  116. def sdist_export(self) -> subprocess.CompletedProcess:
  117. return self.run([
  118. 'sdist',
  119. 'export',
  120. self.project_dir_arg,
  121. self.repo_dir_arg,
  122. ])
  123. @property
  124. def default_builtin_toolchain(self) -> str:
  125. if os.name == 'posix':
  126. return ':gcc-8'
  127. elif os.name == 'nt':
  128. return ':msvc'
  129. else:
  130. raise RuntimeError(
  131. f'No default builtin toolchain defined for tests on platform "{os.name}"'
  132. )
  133. @property
  134. def exe_suffix(self) -> str:
  135. if os.name == 'posix':
  136. return ''
  137. elif os.name == 'nt':
  138. return '.exe'
  139. else:
  140. raise RuntimeError(
  141. f'We don\'t know the executable suffix for the platform "{os.name}"'
  142. )
  143. def set_contents(self, path: Union[str, Path],
  144. content: bytes) -> ContextManager[Path]:
  145. return fileutil.set_contents(self.source_root / path, content)
  146. @contextmanager
  147. def scoped_dds(test_dir: Path, project_dir: Path, name: str):
  148. dds_exe = Path(__file__).absolute().parent.parent / '_build/dds'
  149. if os.name == 'nt':
  150. dds_exe = dds_exe.with_suffix('.exe')
  151. with ExitStack() as scope:
  152. yield DDS(dds_exe, test_dir, project_dir, scope)
  153. class DDSFixtureParams(NamedTuple):
  154. ident: str
  155. subdir: Union[Path, str]
  156. def dds_fixture_conf(*argsets: DDSFixtureParams):
  157. args = list(argsets)
  158. return pytest.mark.parametrize(
  159. 'dds', args, indirect=True, ids=[p.ident for p in args])
  160. def dds_fixture_conf_1(subdir: Union[Path, str]):
  161. params = DDSFixtureParams(ident='only', subdir=subdir)
  162. return pytest.mark.parametrize('dds', [params], indirect=True, ids=['.'])