conftest.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. # SPDX-FileCopyrightText: 2022 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. yield Path(work_dir)
  45. if clean_dir:
  46. logging.debug(f'cleaning up {clean_dir}')
  47. shutil.rmtree(clean_dir, ignore_errors=True)
  48. @pytest.fixture
  49. def test_app_copy(session_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]:
  50. # by default, use hello_world app and copy it to a temporary directory with
  51. # the name resembling that of the test
  52. copy_from = 'tools/test_build_system/build_test_app'
  53. copy_to = request.node.name + '_app'
  54. # allow overriding source and destination via pytest.mark.test_app_copy()
  55. mark = request.node.get_closest_marker('test_app_copy')
  56. if mark:
  57. copy_from = mark.args[0]
  58. if len(mark.args) > 1:
  59. copy_to = mark.args[1]
  60. path_from = Path(os.environ['IDF_PATH']) / copy_from
  61. path_to = session_work_dir / copy_to
  62. # if the new directory inside the original directory,
  63. # make sure not to go into recursion.
  64. ignore = shutil.ignore_patterns(
  65. path_to.name,
  66. # also ignore files which may be present in the work directory
  67. 'build', 'sdkconfig')
  68. logging.debug(f'copying {path_from} to {path_to}')
  69. shutil.copytree(path_from, path_to, ignore=ignore, symlinks=True)
  70. old_cwd = Path.cwd()
  71. os.chdir(path_to)
  72. yield Path(path_to)
  73. os.chdir(old_cwd)
  74. if should_clean_test_dir(request):
  75. logging.debug('cleaning up work directory after a successful test: {}'.format(path_to))
  76. shutil.rmtree(path_to, ignore_errors=True)
  77. @pytest.fixture
  78. def idf_copy(session_work_dir: Path, request: FixtureRequest) -> typing.Generator[Path, None, None]:
  79. copy_to = request.node.name + '_idf'
  80. # allow overriding the destination via pytest.mark.idf_copy()
  81. mark = request.node.get_closest_marker('idf_copy')
  82. if mark:
  83. copy_to = mark.args[0]
  84. path_from = EXT_IDF_PATH
  85. path_to = session_work_dir / copy_to
  86. # if the new directory inside the original directory,
  87. # make sure not to go into recursion.
  88. ignore = shutil.ignore_patterns(
  89. path_to.name,
  90. # also ignore the build directories which may be quite large
  91. '**/build')
  92. logging.debug(f'copying {path_from} to {path_to}')
  93. shutil.copytree(path_from, path_to, ignore=ignore, symlinks=True)
  94. orig_idf_path = os.environ['IDF_PATH']
  95. yield Path(path_to)
  96. os.environ['IDF_PATH'] = orig_idf_path
  97. if should_clean_test_dir(request):
  98. logging.debug('cleaning up work directory after a successful test: {}'.format(path_to))
  99. shutil.rmtree(path_to, ignore_errors=True)
  100. @pytest.fixture(name='default_idf_env')
  101. def fixture_default_idf_env() -> EnvDict:
  102. return get_idf_build_env(os.environ['IDF_PATH']) # type: ignore
  103. @pytest.fixture
  104. def idf_py(default_idf_env: EnvDict) -> IdfPyFunc:
  105. def result(*args: str) -> subprocess.CompletedProcess:
  106. return run_idf_py(*args, env=default_idf_env, workdir=os.getcwd()) # type: ignore
  107. return result