您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

183 行
6.1KB

  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, Optional
  6. import subprocess
  7. import shutil
  8. import pytest
  9. from dds_ci import proc
  10. from . import fileutil
  11. class DDS:
  12. def __init__(self, dds_exe: Path, test_dir: Path, project_dir: Path, scope: ExitStack) -> None:
  13. self.dds_exe = dds_exe
  14. self.test_dir = test_dir
  15. self.source_root = project_dir
  16. self.scratch_dir = project_dir / '_test_scratch/Ю́рий Алексе́евич Гага́рин'
  17. self.scope = scope
  18. self.scope.callback(self.cleanup)
  19. @property
  20. def repo_dir(self) -> Path:
  21. return self.scratch_dir / 'repo'
  22. @property
  23. def catalog_path(self) -> Path:
  24. return self.scratch_dir / 'catalog.db'
  25. @property
  26. def deps_build_dir(self) -> Path:
  27. return self.scratch_dir / 'deps-build'
  28. @property
  29. def build_dir(self) -> Path:
  30. return self.scratch_dir / 'build'
  31. @property
  32. def lmi_path(self) -> Path:
  33. return self.scratch_dir / 'INDEX.lmi'
  34. def cleanup(self):
  35. if self.scratch_dir.exists():
  36. shutil.rmtree(self.scratch_dir)
  37. def run_unchecked(self, cmd: proc.CommandLine, *, cwd: Path = None) -> subprocess.CompletedProcess:
  38. full_cmd = itertools.chain([self.dds_exe, '-ltrace'], cmd)
  39. return proc.run(full_cmd, cwd=cwd or self.source_root)
  40. def run(self, cmd: proc.CommandLine, *, cwd: Path = None, check=True) -> subprocess.CompletedProcess:
  41. cmdline = list(proc.flatten_cmd(cmd))
  42. res = self.run_unchecked(cmd, cwd=cwd)
  43. if res.returncode != 0 and check:
  44. raise subprocess.CalledProcessError(res.returncode, [self.dds_exe] + cmdline, res.stdout)
  45. return res
  46. @property
  47. def repo_dir_arg(self) -> str:
  48. return f'--repo-dir={self.repo_dir}'
  49. @property
  50. def project_dir_arg(self) -> str:
  51. return f'--project-dir={self.source_root}'
  52. @property
  53. def catalog_path_arg(self) -> str:
  54. return f'--catalog={self.catalog_path}'
  55. def build_deps(self, args: proc.CommandLine, *, toolchain: str = None) -> subprocess.CompletedProcess:
  56. return self.run([
  57. 'build-deps',
  58. f'--toolchain={toolchain or self.default_builtin_toolchain}',
  59. self.catalog_path_arg,
  60. self.repo_dir_arg,
  61. f'--out={self.deps_build_dir}',
  62. f'--lmi-path={self.lmi_path}',
  63. args,
  64. ])
  65. def repo_add(self, url: str) -> None:
  66. self.run(['repo', 'add', url, '--update', self.catalog_path_arg])
  67. def build(self,
  68. *,
  69. toolchain: str = None,
  70. apps: bool = True,
  71. warnings: bool = True,
  72. catalog_path: Optional[Path] = None,
  73. tests: bool = True,
  74. more_args: proc.CommandLine = [],
  75. check: bool = True) -> subprocess.CompletedProcess:
  76. catalog_path = catalog_path or self.catalog_path.relative_to(self.source_root)
  77. return self.run(
  78. [
  79. 'build',
  80. f'--out={self.build_dir}',
  81. f'--toolchain={toolchain or self.default_builtin_toolchain}',
  82. f'--catalog={catalog_path}',
  83. f'--repo-dir={self.repo_dir.relative_to(self.source_root)}',
  84. ['--no-tests'] if not tests else [],
  85. ['--no-apps'] if not apps else [],
  86. ['--no-warnings'] if not warnings else [],
  87. self.project_dir_arg,
  88. more_args,
  89. ],
  90. check=check,
  91. )
  92. def sdist_create(self) -> subprocess.CompletedProcess:
  93. self.build_dir.mkdir(exist_ok=True, parents=True)
  94. return self.run(['sdist', 'create', self.project_dir_arg], cwd=self.build_dir)
  95. def sdist_export(self) -> subprocess.CompletedProcess:
  96. return self.run([
  97. 'sdist',
  98. 'export',
  99. self.project_dir_arg,
  100. self.repo_dir_arg,
  101. ])
  102. def repo_import(self, sdist: Path) -> subprocess.CompletedProcess:
  103. return self.run(['repo', self.repo_dir_arg, 'import', sdist])
  104. @property
  105. def default_builtin_toolchain(self) -> str:
  106. if os.name == 'posix':
  107. return str(Path(__file__).parent.joinpath('gcc-9.tc.jsonc'))
  108. elif os.name == 'nt':
  109. return str(Path(__file__).parent.joinpath('msvc.tc.jsonc'))
  110. else:
  111. raise RuntimeError(f'No default builtin toolchain defined for tests on platform "{os.name}"')
  112. @property
  113. def exe_suffix(self) -> str:
  114. if os.name == 'posix':
  115. return ''
  116. elif os.name == 'nt':
  117. return '.exe'
  118. else:
  119. raise RuntimeError(f'We don\'t know the executable suffix for the platform "{os.name}"')
  120. def catalog_create(self) -> subprocess.CompletedProcess:
  121. self.scratch_dir.mkdir(parents=True, exist_ok=True)
  122. return self.run(['catalog', 'create', f'--catalog={self.catalog_path}'], cwd=self.test_dir)
  123. def catalog_get(self, req: str) -> subprocess.CompletedProcess:
  124. return self.run([
  125. 'catalog',
  126. 'get',
  127. f'--catalog={self.catalog_path}',
  128. f'--out-dir={self.scratch_dir}',
  129. req,
  130. ])
  131. def set_contents(self, path: Union[str, Path], content: bytes) -> ContextManager[Path]:
  132. return fileutil.set_contents(self.source_root / path, content)
  133. @contextmanager
  134. def scoped_dds(dds_exe: Path, test_dir: Path, project_dir: Path, name: str):
  135. if os.name == 'nt':
  136. dds_exe = dds_exe.with_suffix('.exe')
  137. with ExitStack() as scope:
  138. yield DDS(dds_exe, test_dir, project_dir, scope)
  139. class DDSFixtureParams(NamedTuple):
  140. ident: str
  141. subdir: Union[Path, str]
  142. def dds_fixture_conf(*argsets: DDSFixtureParams):
  143. args = list(argsets)
  144. return pytest.mark.parametrize('dds', args, indirect=True, ids=[p.ident for p in args])
  145. def dds_fixture_conf_1(subdir: Union[Path, str]):
  146. params = DDSFixtureParams(ident='only', subdir=subdir)
  147. return pytest.mark.parametrize('dds', [params], indirect=True, ids=['.'])