conftest.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. import datetime
  4. import logging
  5. import os
  6. import shutil
  7. import subprocess
  8. import typing
  9. from pathlib import Path
  10. from tempfile import mkdtemp
  11. import pytest
  12. from _pytest.fixtures import FixtureRequest
  13. from test_build_system_helpers import EXT_IDF_PATH, EnvDict, IdfPyFunc, get_idf_build_env, run_idf_py
  14. # Pytest hook used to check if the test has passed or failed, from a fixture.
  15. # Based on https://docs.pytest.org/en/latest/example/simple.html#making-test-result-information-available-in-fixtures
  16. @pytest.hookimpl(tryfirst=True, hookwrapper=True)
  17. def pytest_runtest_makereport(item: typing.Any, call: typing.Any) -> typing.Generator[None, pytest.TestReport, None]: # pylint: disable=unused-argument
  18. outcome = yield # Execute all other hooks to obtain the report object
  19. report = outcome.get_result()
  20. if report.when == 'call' and report.passed:
  21. # set an attribute which can be checked using 'should_clean_test_dir' function below
  22. setattr(item, 'passed', True)
  23. def should_clean_test_dir(request: FixtureRequest) -> bool:
  24. # Only remove the test directory if the test has passed
  25. return getattr(request.node, 'passed', False)
  26. def pytest_addoption(parser: pytest.Parser) -> None:
  27. parser.addoption(
  28. '--work-dir', action='store', default=None,
  29. help='Directory for temporary files. If not specified, an OS-specific '
  30. 'temporary directory will be used.'
  31. )
  32. @pytest.fixture(name='session_work_dir', scope='session', autouse=True)
  33. def fixture_session_work_dir(request: FixtureRequest) -> typing.Generator[Path, None, None]:
  34. work_dir = request.config.getoption('--work-dir')
  35. if work_dir:
  36. work_dir = os.path.join(work_dir, datetime.datetime.utcnow().strftime('%Y-%m-%d_%H-%M-%S'))
  37. logging.debug(f'using work directory: {work_dir}')
  38. os.makedirs(work_dir, exist_ok=True)
  39. clean_dir = None
  40. else:
  41. work_dir = mkdtemp()
  42. logging.debug(f'created temporary work directory: {work_dir}')
  43. clean_dir = work_dir
  44. # resolve allows to use relative paths with --work-dir option
  45. yield Path(work_dir).resolve()
  46. if clean_dir:
  47. logging.debug(f'cleaning up {clean_dir}')
  48. shutil.rmtree(clean_dir, ignore_errors=True)
  49. @pytest.fixture
  50. def test_app_copy(session_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]:
  51. # by default, use hello_world app and copy it to a temporary directory with
  52. # the name resembling that of the test
  53. copy_from = 'tools/test_build_system/build_test_app'
  54. copy_to = request.node.name + '_app'
  55. # allow overriding source and destination via pytest.mark.test_app_copy()
  56. mark = request.node.get_closest_marker('test_app_copy')
  57. if mark:
  58. copy_from = mark.args[0]
  59. if len(mark.args) > 1:
  60. copy_to = mark.args[1]
  61. path_from = Path(os.environ['IDF_PATH']) / copy_from
  62. path_to = session_work_dir / copy_to
  63. # if the new directory inside the original directory,
  64. # make sure not to go into recursion.
  65. ignore = shutil.ignore_patterns(
  66. path_to.name,
  67. # also ignore files which may be present in the work directory
  68. 'build', 'sdkconfig')
  69. logging.debug(f'copying {path_from} to {path_to}')
  70. shutil.copytree(path_from, path_to, ignore=ignore, symlinks=True)
  71. old_cwd = Path.cwd()
  72. os.chdir(path_to)
  73. yield Path(path_to)
  74. os.chdir(old_cwd)
  75. if should_clean_test_dir(request):
  76. logging.debug('cleaning up work directory after a successful test: {}'.format(path_to))
  77. shutil.rmtree(path_to, ignore_errors=True)
  78. @pytest.fixture
  79. def test_git_template_app(session_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]:
  80. copy_to = request.node.name + '_app'
  81. path_to = session_work_dir / copy_to
  82. logging.debug(f'clonning git-teplate app to {path_to}')
  83. path_to.mkdir()
  84. # No need to clone full repository, just single master branch
  85. subprocess.run(['git', 'clone', '--single-branch', '-b', 'master', '--depth', '1', 'https://github.com/espressif/esp-idf-template.git', '.'],
  86. cwd=path_to, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  87. old_cwd = Path.cwd()
  88. os.chdir(path_to)
  89. yield Path(path_to)
  90. os.chdir(old_cwd)
  91. if should_clean_test_dir(request):
  92. logging.debug('cleaning up work directory after a successful test: {}'.format(path_to))
  93. shutil.rmtree(path_to, ignore_errors=True)
  94. @pytest.fixture
  95. def idf_copy(session_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]:
  96. copy_to = request.node.name + '_idf'
  97. # allow overriding the destination via pytest.mark.idf_copy()
  98. mark = request.node.get_closest_marker('idf_copy')
  99. if mark:
  100. copy_to = mark.args[0]
  101. path_from = EXT_IDF_PATH
  102. path_to = session_work_dir / copy_to
  103. # if the new directory inside the original directory,
  104. # make sure not to go into recursion.
  105. ignore = shutil.ignore_patterns(
  106. path_to.name,
  107. # also ignore the build directories which may be quite large
  108. '**/build')
  109. logging.debug(f'copying {path_from} to {path_to}')
  110. shutil.copytree(path_from, path_to, ignore=ignore, symlinks=True)
  111. orig_idf_path = os.environ['IDF_PATH']
  112. os.environ['IDF_PATH'] = str(path_to)
  113. yield Path(path_to)
  114. os.environ['IDF_PATH'] = orig_idf_path
  115. if should_clean_test_dir(request):
  116. logging.debug('cleaning up work directory after a successful test: {}'.format(path_to))
  117. shutil.rmtree(path_to, ignore_errors=True)
  118. @pytest.fixture(name='default_idf_env')
  119. def fixture_default_idf_env() -> EnvDict:
  120. return get_idf_build_env(os.environ['IDF_PATH']) # type: ignore
  121. @pytest.fixture
  122. def idf_py(default_idf_env: EnvDict) -> IdfPyFunc:
  123. def result(*args: str, check: bool = True, input_str: typing.Optional[str] = None) -> subprocess.CompletedProcess:
  124. return run_idf_py(*args, env=default_idf_env, workdir=os.getcwd(), check=check, input_str=input_str) # type: ignore
  125. return result