Merge branch 'feature/add_windows_pytest' into 'master'

Tools: Add pytest build system on Windows runner

Closes IDF-7691, IDF-8214, and IDF-1193

See merge request espressif/esp-idf!26603
This commit is contained in:
Roland Dobai
2023-12-01 16:40:13 +08:00
11 changed files with 98 additions and 9 deletions

View File

@ -550,6 +550,38 @@ pytest_build_system_macos:
reports:
junit: XUNIT_RESULT.xml
.test_build_system_template_win:
stage: host_test
variables:
# Enable ccache for all build jobs. See configure_ci_environment.sh for more ccache related settings.
IDF_CCACHE_ENABLE: "1"
PYTHONPATH: "$PYTHONPATH;$IDF_PATH\\tools;$IDF_PATH\\tools\\esp_app_trace;$IDF_PATH\\components\\partition_table;$IDF_PATH\\tools\\ci\\python_packages"
before_script: []
after_script: []
timeout: 4 hours
script:
- .\install.ps1 --enable-ci --enable-pytest
- . .\export.ps1
- python "${SUBMODULE_FETCH_TOOL}" -s "all"
- cd ${IDF_PATH}\tools\test_build_system
- pytest --junitxml=${CI_PROJECT_DIR}\XUNIT_RESULT.xml
pytest_build_system_win:
extends:
- .test_build_system_template_win
- .rules:test:windows_pytest_build_system
needs: []
tags:
- windows-target
artifacts:
paths:
- XUNIT_RESULT.xml
- test_build_system
when: always
expire_in: 2 days
reports:
junit: XUNIT_RESULT.xml
build_docker:
extends:
- .before_script:minimal

View File

@ -175,6 +175,12 @@
patterns:
- submodule
"test:windows_pytest_build_system":
labels:
- windows
specific_rules:
- if-schedule-test-build-system-windows
#################################
# Triggered Only By Labels Jobs #
#################################

View File

@ -351,6 +351,9 @@
.if-schedule: &if-schedule
if: '$CI_PIPELINE_SOURCE == "schedule"'
.if-schedule-test-build-system-windows: &if-schedule-test-build-system-windows
if: '$CI_PIPELINE_SOURCE == "schedule" && $SCHEDULED_BUILD_SYSTEM_TEST_WIN == "true"'
.if-trigger: &if-trigger
if: '$CI_PIPELINE_SOURCE == "trigger"'
@ -562,6 +565,9 @@
.if-label-target_test: &if-label-target_test
if: '$BOT_LABEL_TARGET_TEST || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*target_test(?:,[^,\n\r]+)*$/i'
.if-label-windows: &if-label-windows
if: '$BOT_LABEL_WINDOWS || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*windows(?:,[^,\n\r]+)*$/i'
.rules:build:
rules:
- <<: *if-revert-branch
@ -2575,3 +2581,13 @@
- <<: *if-label-submodule
- <<: *if-dev-push
changes: *patterns-submodule
.rules:test:windows_pytest_build_system:
rules:
- <<: *if-revert-branch
when: never
- <<: *if-protected
- <<: *if-label-build-only
when: never
- <<: *if-schedule-test-build-system-windows
- <<: *if-label-windows

View File

@ -4,6 +4,7 @@ import argparse
import logging
import os
import re
import sys
import tarfile
import tempfile
import time
@ -230,9 +231,19 @@ class Gitlab(object):
@staticmethod
def decompress_archive(path: str, destination: str) -> str:
with tarfile.open(path, 'r') as archive_file:
root_name = archive_file.getnames()[0]
archive_file.extractall(destination)
full_destination = os.path.abspath(destination)
# By default max path lenght is set to 260 characters
# Prefix `\\?\` extends it to 32,767 characters
if sys.platform == 'win32':
full_destination = '\\\\?\\' + full_destination
try:
with tarfile.open(path, 'r') as archive_file:
root_name = archive_file.getnames()[0]
archive_file.extractall(full_destination)
except tarfile.TarError as e:
logging.error(f'Error while decompressing archive {path}')
raise e
return os.path.join(os.path.realpath(destination), root_name)

View File

@ -28,7 +28,7 @@ def test_bootloader_custom_overrides_original(test_app_copy: Path, idf_py: IdfPy
shutil.copytree(idf_path / 'components' / 'esp_bootloader_format', test_app_copy / 'components' / 'esp_bootloader_format')
idf_py('bootloader')
assert file_contains(test_app_copy / 'build' / 'bootloader' / 'compile_commands.json',
str(test_app_copy / 'components' / 'bootloader' / 'subproject' / 'main' / 'bootloader_start.c'))
(test_app_copy / 'components' / 'bootloader' / 'subproject' / 'main' / 'bootloader_start.c'))
def test_bootloader_custom_ignores_extra_component(test_app_copy: Path, idf_py: IdfPyFunc, default_idf_env: EnvDict) -> None:

View File

@ -7,7 +7,7 @@ import shutil
import subprocess
import sys
import typing
from pathlib import Path
from pathlib import Path, WindowsPath
from typing import Pattern, Union
try:
@ -137,7 +137,7 @@ def run_cmake_and_build(*cmake_args: str, env: typing.Optional[EnvDict] = None)
run_cmake('--build', '.')
def file_contains(filename: Union[str, Path], what: Union[str, Pattern]) -> bool:
def file_contains(filename: Union[str, Path], what: Union[Union[str, Path], Pattern]) -> bool:
"""
Returns true if file contains required object
:param filename: path to file where lookup is executed
@ -145,10 +145,16 @@ def file_contains(filename: Union[str, Path], what: Union[str, Pattern]) -> bool
"""
with open(filename, 'r', encoding='utf-8') as f:
data = f.read()
if isinstance(what, str):
return what in data
else:
if isinstance(what, Pattern):
return re.search(what, data) is not None
else:
what_str = str(what)
# In case of windows path, try both single-slash `\` and double-slash '\\' paths
if isinstance(what, WindowsPath):
what_double_slash = what_str.replace('\\', '\\\\')
return what_str in data or what_double_slash in data
return what_str in data
def bin_file_contains(filename: Union[str, Path], what: bytearray) -> bool:

View File

@ -4,12 +4,14 @@ import logging
import os
import re
import shutil
import sys
from pathlib import Path
import pytest
from test_build_system_helpers import EnvDict, IdfPyFunc, append_to_file, file_contains, run_cmake, run_cmake_and_build
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
def test_build_custom_cmake_project(test_app_copy: Path) -> None:
for target in ['esp32', 'esp32s3', 'esp32c6', 'esp32h2']:
logging.info(f'Test build ESP-IDF as a library to a custom CMake projects for {target}')
@ -50,6 +52,7 @@ def test_build_cmake_library_psram_strategies(idf_py: IdfPyFunc, test_app_copy:
(test_app_copy / 'sdkconfig').unlink()
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
@pytest.mark.usefixtures('test_app_copy')
@pytest.mark.usefixtures('idf_copy')
def test_defaults_for_unspecified_idf_build_process_args(default_idf_env: EnvDict) -> None:

View File

@ -207,6 +207,7 @@ def test_fallback_to_build_system_target(idf_py: IdfPyFunc, test_app_copy: Path)
assert msg in ret.stdout, 'Custom target did not produce expected output'
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
def test_create_component_and_project_plus_build(idf_copy: Path) -> None:
logging.info('Create project and component using idf.py and build it')
run_idf_py('-C', 'projects', 'create-project', 'temp_test_project', workdir=idf_copy)

View File

@ -4,12 +4,14 @@
import json
import logging
import shutil
import sys
from pathlib import Path
import pytest
from test_build_system_helpers import EnvDict, IdfPyFunc, append_to_file, replace_in_file
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
def test_component_extra_dirs(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('Setting EXTRA_COMPONENT_DIRS works')
shutil.move(test_app_copy / 'main', test_app_copy / 'different_main' / 'main')
@ -42,6 +44,7 @@ def test_component_can_not_be_empty_dir(idf_py: IdfPyFunc, test_app_copy: Path)
assert str(empty_component_dir) not in data.get('build_component_paths')
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
def test_component_subdirs_not_added_to_component_dirs(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('If a component directory is added to COMPONENT_DIRS, its subdirectories are not added')
(test_app_copy / 'main' / 'test').mkdir(parents=True)
@ -52,6 +55,7 @@ def test_component_subdirs_not_added_to_component_dirs(idf_py: IdfPyFunc, test_a
assert str(test_app_copy / 'main') in data.get('build_component_paths')
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
def test_component_sibling_dirs_not_added_to_component_dirs(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('If a component directory is added to COMPONENT_DIRS, its sibling directories are not added')
mycomponents_subdir = (test_app_copy / 'mycomponents')
@ -76,6 +80,7 @@ def test_component_sibling_dirs_not_added_to_component_dirs(idf_py: IdfPyFunc, t
assert str(mycomponents_subdir / 'mycomponent') in data.get('build_component_paths')
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
def test_component_properties_are_set(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('Component properties are set')
append_to_file(test_app_copy / 'CMakeLists.txt', '\n'.join(['',
@ -85,6 +90,7 @@ def test_component_properties_are_set(idf_py: IdfPyFunc, test_app_copy: Path) ->
assert 'SRCS:{}'.format(test_app_copy / 'main' / 'build_test_app.c') in ret.stdout, 'Component properties should be set'
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
def test_component_overriden_dir(idf_py: IdfPyFunc, test_app_copy: Path, default_idf_env: EnvDict) -> None:
logging.info('Getting component overriden dir')
(test_app_copy / 'components' / 'hal').mkdir(parents=True)
@ -104,6 +110,7 @@ def test_component_overriden_dir(idf_py: IdfPyFunc, test_app_copy: Path, default
assert 'kconfig:{}'.format(idf_path / 'components' / 'hal') in ret.stdout, 'Failed to verify original `main` directory'
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
def test_components_prioritizer_over_extra_components_dir(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('Project components prioritized over EXTRA_COMPONENT_DIRS')
(test_app_copy / 'extra_dir' / 'my_component').mkdir(parents=True)

View File

@ -3,6 +3,7 @@
import logging
import shutil
import sys
from pathlib import Path
from typing import List, Optional
@ -31,6 +32,7 @@ def test_target_from_environment_cmake(default_idf_env: EnvDict) -> None:
assert file_contains('build/CMakeCache.txt', 'IDF_TARGET:STRING={}'.format(ESP32S2_TARGET))
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
def test_target_from_environment_idf_py(idf_py: IdfPyFunc, default_idf_env: EnvDict, test_app_copy: Path) -> None:
def reconfigure_and_check_return_values(errmsg: str, opts: Optional[List[str]] = None) -> None:
opts = opts or []
@ -72,6 +74,7 @@ def test_target_from_environment_idf_py(idf_py: IdfPyFunc, default_idf_env: EnvD
['-D', 'IDF_TARGET={}'.format(ESP32_TARGET)])
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
def test_target_consistency_cmake(default_idf_env: EnvDict, test_app_copy: Path) -> None:
def reconfigure_and_check_return_values(errmsg: str, opts: Optional[List[str]] = None) -> None:
opts = opts or []

View File

@ -20,6 +20,7 @@ def clean_app_dir(app_path: Path) -> None:
shutil.rmtree(app_path / 'build', ignore_errors=True)
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
@pytest.mark.idf_copy('esp idf with spaces')
def test_spaces_bundle1(idf_copy: Path) -> None:
logging.info('Running test spaces bundle 1')
@ -33,6 +34,7 @@ def test_spaces_bundle1(idf_copy: Path) -> None:
run_idf_py('build', workdir=(idf_copy / 'examples' / 'storage' / 'spiffsgen'))
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
@pytest.mark.idf_copy('esp idf with spaces')
def test_spaces_bundle2(idf_copy: Path) -> None:
logging.info('Running test spaces bundle 2')
@ -48,6 +50,7 @@ def test_spaces_bundle2(idf_copy: Path) -> None:
run_idf_py('uf2', workdir=hello_world_app_path)
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
@pytest.mark.idf_copy('esp idf with spaces')
def test_spaces_bundle3(idf_copy: Path) -> None:
logging.info('Running test spaces bundle 3')
@ -86,6 +89,7 @@ def test_install_export_unix(idf_copy: Path) -> None:
subprocess.check_call(export_cmd, env=env, shell=True, cwd=idf_copy, executable='/bin/bash')
@pytest.mark.skipif(sys.platform == 'win32', reason='Failing on Windows runner. TODO')
@pytest.mark.skipif(sys.platform != 'win32', reason='Windows test')
@pytest.mark.idf_copy('esp idf with spaces')
def test_install_export_win(idf_copy: Path) -> None: