Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

181 line
5.4KB

  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
  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-dir"""
  56. return f'--project-dir={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)
  63. def sdist_create(self) -> None:
  64. self.build_root.mkdir(exist_ok=True, parents=True)
  65. self.dds.run(['sdist', 'create', self.project_dir_arg], cwd=self.build_root)
  66. def sdist_export(self) -> None:
  67. self.dds.run(['sdist', 'export', self.dds.repo_dir_arg, self.project_dir_arg])
  68. def write(self, path: Pathish, content: str) -> Path:
  69. path = Path(path)
  70. if not path.is_absolute():
  71. path = self.root / path
  72. path.parent.mkdir(exist_ok=True, parents=True)
  73. path.write_text(content)
  74. return path
  75. @pytest.fixture()
  76. def test_parent_dir(request: FixtureRequest) -> Path:
  77. return Path(request.fspath).parent
  78. class ProjectOpener():
  79. def __init__(self, dds: DDSWrapper, request: FixtureRequest, worker: str,
  80. tmp_path_factory: TempPathFactory) -> None:
  81. self.dds = dds
  82. self._request = request
  83. self._worker_id = worker
  84. self._tmppath_fac = tmp_path_factory
  85. @property
  86. def test_name(self) -> str:
  87. """The name of the test that requested this opener"""
  88. return str(self._request.function.__name__)
  89. @property
  90. def test_dir(self) -> Path:
  91. """The directory that contains the test that requested this opener"""
  92. return Path(self._request.fspath).parent
  93. def open(self, dirpath: Pathish) -> Project:
  94. dirpath = Path(dirpath)
  95. if not dirpath.is_absolute():
  96. dirpath = self.test_dir / dirpath
  97. proj_copy = self.test_dir / '__test_project'
  98. if self._worker_id != 'master':
  99. proj_copy = self._tmppath_fac.mktemp('test-project-') / self.test_name
  100. else:
  101. self._request.addfinalizer(lambda: ensure_absent(proj_copy))
  102. shutil.copytree(dirpath, proj_copy)
  103. new_dds = self.dds.clone()
  104. if self._worker_id == 'master':
  105. repo_dir = self.test_dir / '__test_repo'
  106. else:
  107. repo_dir = self._tmppath_fac.mktemp('test-repo-') / self.test_name
  108. new_dds.set_repo_scratch(repo_dir)
  109. new_dds.default_cwd = proj_copy
  110. self._request.addfinalizer(lambda: ensure_absent(repo_dir))
  111. return Project(proj_copy, new_dds)
  112. @pytest.fixture()
  113. def project_opener(request: FixtureRequest, worker_id: str, dds_2: DDSWrapper,
  114. tmp_path_factory: TempPathFactory) -> ProjectOpener:
  115. opener = ProjectOpener(dds_2, request, worker_id, tmp_path_factory)
  116. return opener
  117. @pytest.fixture()
  118. def tmp_project(request: FixtureRequest, worker_id: str, project_opener: ProjectOpener,
  119. tmp_path_factory: TempPathFactory) -> Project:
  120. if worker_id != 'master':
  121. proj_dir = tmp_path_factory.mktemp('temp-project')
  122. return project_opener.open(proj_dir)
  123. proj_dir = project_opener.test_dir / '__test_project_empty'
  124. ensure_absent(proj_dir)
  125. proj_dir.mkdir()
  126. proj = project_opener.open(proj_dir)
  127. request.addfinalizer(lambda: ensure_absent(proj_dir))
  128. return proj
  129. @pytest.fixture(scope='session')
  130. def dds_2(dds_exe: Path) -> DDSWrapper:
  131. return DDSWrapper(dds_exe)
  132. @pytest.fixture(scope='session')
  133. def dds_exe(pytestconfig: PyTestConfig) -> Path:
  134. opt = pytestconfig.getoption('--dds-exe') or paths.CUR_BUILT_DDS
  135. return Path(opt)