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

191 行
5.9KB

  1. """
  2. Test fixtures used by DDS in pytest
  3. """
  4. from pathlib import Path
  5. import pytest
  6. import json
  7. import shutil
  8. from typing import Sequence, cast, Optional
  9. from typing_extensions import TypedDict
  10. from _pytest.config import Config as PyTestConfig
  11. from _pytest.tmpdir import TempPathFactory
  12. from _pytest.fixtures import FixtureRequest
  13. from dds_ci import toolchain, paths
  14. from ..dds import DDSWrapper, NewDDSWrapper
  15. from ..util import Pathish
  16. tc_mod = toolchain
  17. def ensure_absent(path: Pathish) -> None:
  18. path = Path(path)
  19. if path.is_dir():
  20. shutil.rmtree(path)
  21. elif path.exists():
  22. path.unlink()
  23. else:
  24. # File does not exist, wo we are safe to ignore it
  25. pass
  26. class _PackageJSONRequired(TypedDict):
  27. name: str
  28. namespace: str
  29. version: str
  30. class PackageJSON(_PackageJSONRequired, total=False):
  31. depends: Sequence[str]
  32. class _LibraryJSONRequired(TypedDict):
  33. name: str
  34. class LibraryJSON(_LibraryJSONRequired, total=False):
  35. uses: Sequence[str]
  36. class Project:
  37. def __init__(self, dirpath: Path, dds: DDSWrapper) -> None:
  38. self.dds = dds
  39. self.root = dirpath
  40. self.build_root = dirpath / '_build'
  41. @property
  42. def package_json(self) -> PackageJSON:
  43. return cast(PackageJSON, json.loads(self.root.joinpath('package.jsonc').read_text()))
  44. @package_json.setter
  45. def package_json(self, data: PackageJSON) -> None:
  46. self.root.joinpath('package.jsonc').write_text(json.dumps(data, indent=2))
  47. @property
  48. def library_json(self) -> LibraryJSON:
  49. return cast(LibraryJSON, json.loads(self.root.joinpath('library.jsonc').read_text()))
  50. @library_json.setter
  51. def library_json(self, data: LibraryJSON) -> None:
  52. self.root.joinpath('library.jsonc').write_text(json.dumps(data, indent=2))
  53. @property
  54. def project_dir_arg(self) -> str:
  55. """Argument for --project"""
  56. return f'--project={self.root}'
  57. def build(self, *, toolchain: Optional[Pathish] = None) -> None:
  58. """
  59. Execute 'dds build' on the project
  60. """
  61. with tc_mod.fixup_toolchain(toolchain or tc_mod.get_default_test_toolchain()) as tc:
  62. self.dds.build(root=self.root, build_root=self.build_root, toolchain=tc, more_args=['-ldebug'])
  63. def compile_file(self, *paths: Pathish, toolchain: Optional[Pathish] = None) -> None:
  64. with tc_mod.fixup_toolchain(toolchain or tc_mod.get_default_test_toolchain()) as tc:
  65. self.dds.compile_file(paths, toolchain=tc, out=self.build_root, project_dir=self.root)
  66. def sdist_create(self, *, dest: Optional[Pathish] = None) -> None:
  67. self.build_root.mkdir(exist_ok=True, parents=True)
  68. self.dds.run([
  69. 'sdist',
  70. 'create',
  71. self.project_dir_arg,
  72. f'--out={dest}' if dest else (),
  73. ], cwd=self.build_root)
  74. def sdist_export(self) -> None:
  75. self.dds.run(['sdist', 'export', self.dds.repo_dir_arg, self.project_dir_arg])
  76. def write(self, path: Pathish, content: str) -> Path:
  77. path = Path(path)
  78. if not path.is_absolute():
  79. path = self.root / path
  80. path.parent.mkdir(exist_ok=True, parents=True)
  81. path.write_text(content)
  82. return path
  83. @pytest.fixture()
  84. def test_parent_dir(request: FixtureRequest) -> Path:
  85. return Path(request.fspath).parent
  86. class ProjectOpener():
  87. def __init__(self, dds: DDSWrapper, request: FixtureRequest, worker: str,
  88. tmp_path_factory: TempPathFactory) -> None:
  89. self.dds = dds
  90. self._request = request
  91. self._worker_id = worker
  92. self._tmppath_fac = tmp_path_factory
  93. @property
  94. def test_name(self) -> str:
  95. """The name of the test that requested this opener"""
  96. return str(self._request.function.__name__)
  97. @property
  98. def test_dir(self) -> Path:
  99. """The directory that contains the test that requested this opener"""
  100. return Path(self._request.fspath).parent
  101. def open(self, dirpath: Pathish) -> Project:
  102. dirpath = Path(dirpath)
  103. if not dirpath.is_absolute():
  104. dirpath = self.test_dir / dirpath
  105. proj_copy = self.test_dir / '__test_project'
  106. if self._worker_id != 'master':
  107. proj_copy = self._tmppath_fac.mktemp('test-project-') / self.test_name
  108. else:
  109. self._request.addfinalizer(lambda: ensure_absent(proj_copy))
  110. shutil.copytree(dirpath, proj_copy)
  111. new_dds = self.dds.clone()
  112. if self._worker_id == 'master':
  113. repo_dir = self.test_dir / '__test_repo'
  114. else:
  115. repo_dir = self._tmppath_fac.mktemp('test-repo-') / self.test_name
  116. new_dds.set_repo_scratch(repo_dir)
  117. new_dds.default_cwd = proj_copy
  118. self._request.addfinalizer(lambda: ensure_absent(repo_dir))
  119. return Project(proj_copy, new_dds)
  120. @pytest.fixture()
  121. def project_opener(request: FixtureRequest, worker_id: str, dds: DDSWrapper,
  122. tmp_path_factory: TempPathFactory) -> ProjectOpener:
  123. opener = ProjectOpener(dds, request, worker_id, tmp_path_factory)
  124. return opener
  125. @pytest.fixture()
  126. def tmp_project(request: FixtureRequest, worker_id: str, project_opener: ProjectOpener,
  127. tmp_path_factory: TempPathFactory) -> Project:
  128. if worker_id != 'master':
  129. proj_dir = tmp_path_factory.mktemp('temp-project')
  130. return project_opener.open(proj_dir)
  131. proj_dir = project_opener.test_dir / '__test_project_empty'
  132. ensure_absent(proj_dir)
  133. proj_dir.mkdir()
  134. proj = project_opener.open(proj_dir)
  135. request.addfinalizer(lambda: ensure_absent(proj_dir))
  136. return proj
  137. @pytest.fixture(scope='session')
  138. def dds(dds_exe: Path) -> NewDDSWrapper:
  139. wr = NewDDSWrapper(dds_exe)
  140. return wr
  141. @pytest.fixture(scope='session')
  142. def dds_exe(pytestconfig: PyTestConfig) -> Path:
  143. opt = pytestconfig.getoption('--dds-exe') or paths.BUILD_DIR / 'dds'
  144. return Path(opt)