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: reports:
junit: XUNIT_RESULT.xml 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: build_docker:
extends: extends:
- .before_script:minimal - .before_script:minimal

View File

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

View File

@@ -351,6 +351,9 @@
.if-schedule: &if-schedule .if-schedule: &if-schedule
if: '$CI_PIPELINE_SOURCE == "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-trigger: &if-trigger
if: '$CI_PIPELINE_SOURCE == "trigger"' if: '$CI_PIPELINE_SOURCE == "trigger"'
@@ -562,6 +565,9 @@
.if-label-target_test: &if-label-target_test .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: '$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:build:
rules: rules:
- <<: *if-revert-branch - <<: *if-revert-branch
@@ -2575,3 +2581,13 @@
- <<: *if-label-submodule - <<: *if-label-submodule
- <<: *if-dev-push - <<: *if-dev-push
changes: *patterns-submodule 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 logging
import os import os
import re import re
import sys
import tarfile import tarfile
import tempfile import tempfile
import time import time
@@ -230,9 +231,19 @@ class Gitlab(object):
@staticmethod @staticmethod
def decompress_archive(path: str, destination: str) -> str: def decompress_archive(path: str, destination: str) -> str:
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: with tarfile.open(path, 'r') as archive_file:
root_name = archive_file.getnames()[0] root_name = archive_file.getnames()[0]
archive_file.extractall(destination) 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) 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') shutil.copytree(idf_path / 'components' / 'esp_bootloader_format', test_app_copy / 'components' / 'esp_bootloader_format')
idf_py('bootloader') idf_py('bootloader')
assert file_contains(test_app_copy / 'build' / 'bootloader' / 'compile_commands.json', 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: 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 subprocess
import sys import sys
import typing import typing
from pathlib import Path from pathlib import Path, WindowsPath
from typing import Pattern, Union from typing import Pattern, Union
try: try:
@@ -137,7 +137,7 @@ def run_cmake_and_build(*cmake_args: str, env: typing.Optional[EnvDict] = None)
run_cmake('--build', '.') 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 Returns true if file contains required object
:param filename: path to file where lookup is executed :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: with open(filename, 'r', encoding='utf-8') as f:
data = f.read() data = f.read()
if isinstance(what, str): if isinstance(what, Pattern):
return what in data
else:
return re.search(what, data) is not None 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: def bin_file_contains(filename: Union[str, Path], what: bytearray) -> bool:

View File

@@ -4,12 +4,14 @@ import logging
import os import os
import re import re
import shutil import shutil
import sys
from pathlib import Path from pathlib import Path
import pytest import pytest
from test_build_system_helpers import EnvDict, IdfPyFunc, append_to_file, file_contains, run_cmake, run_cmake_and_build 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: def test_build_custom_cmake_project(test_app_copy: Path) -> None:
for target in ['esp32', 'esp32s3', 'esp32c6', 'esp32h2']: for target in ['esp32', 'esp32s3', 'esp32c6', 'esp32h2']:
logging.info(f'Test build ESP-IDF as a library to a custom CMake projects for {target}') 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() (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('test_app_copy')
@pytest.mark.usefixtures('idf_copy') @pytest.mark.usefixtures('idf_copy')
def test_defaults_for_unspecified_idf_build_process_args(default_idf_env: EnvDict) -> None: 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' 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: def test_create_component_and_project_plus_build(idf_copy: Path) -> None:
logging.info('Create project and component using idf.py and build it') 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) run_idf_py('-C', 'projects', 'create-project', 'temp_test_project', workdir=idf_copy)

View File

@@ -4,12 +4,14 @@
import json import json
import logging import logging
import shutil import shutil
import sys
from pathlib import Path from pathlib import Path
import pytest import pytest
from test_build_system_helpers import EnvDict, IdfPyFunc, append_to_file, replace_in_file 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: def test_component_extra_dirs(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('Setting EXTRA_COMPONENT_DIRS works') logging.info('Setting EXTRA_COMPONENT_DIRS works')
shutil.move(test_app_copy / 'main', test_app_copy / 'different_main' / 'main') 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') 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: 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') logging.info('If a component directory is added to COMPONENT_DIRS, its subdirectories are not added')
(test_app_copy / 'main' / 'test').mkdir(parents=True) (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') 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: 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') logging.info('If a component directory is added to COMPONENT_DIRS, its sibling directories are not added')
mycomponents_subdir = (test_app_copy / 'mycomponents') 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') 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: def test_component_properties_are_set(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
logging.info('Component properties are set') logging.info('Component properties are set')
append_to_file(test_app_copy / 'CMakeLists.txt', '\n'.join(['', 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' 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: def test_component_overriden_dir(idf_py: IdfPyFunc, test_app_copy: Path, default_idf_env: EnvDict) -> None:
logging.info('Getting component overriden dir') logging.info('Getting component overriden dir')
(test_app_copy / 'components' / 'hal').mkdir(parents=True) (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' 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: 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') logging.info('Project components prioritized over EXTRA_COMPONENT_DIRS')
(test_app_copy / 'extra_dir' / 'my_component').mkdir(parents=True) (test_app_copy / 'extra_dir' / 'my_component').mkdir(parents=True)

View File

@@ -3,6 +3,7 @@
import logging import logging
import shutil import shutil
import sys
from pathlib import Path from pathlib import Path
from typing import List, Optional 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)) 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 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: def reconfigure_and_check_return_values(errmsg: str, opts: Optional[List[str]] = None) -> None:
opts = opts or [] 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)]) ['-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 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: def reconfigure_and_check_return_values(errmsg: str, opts: Optional[List[str]] = None) -> None:
opts = opts or [] 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) 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') @pytest.mark.idf_copy('esp idf with spaces')
def test_spaces_bundle1(idf_copy: Path) -> None: def test_spaces_bundle1(idf_copy: Path) -> None:
logging.info('Running test spaces bundle 1') 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')) 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') @pytest.mark.idf_copy('esp idf with spaces')
def test_spaces_bundle2(idf_copy: Path) -> None: def test_spaces_bundle2(idf_copy: Path) -> None:
logging.info('Running test spaces bundle 2') 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) 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') @pytest.mark.idf_copy('esp idf with spaces')
def test_spaces_bundle3(idf_copy: Path) -> None: def test_spaces_bundle3(idf_copy: Path) -> None:
logging.info('Running test spaces bundle 3') 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') 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.skipif(sys.platform != 'win32', reason='Windows test')
@pytest.mark.idf_copy('esp idf with spaces') @pytest.mark.idf_copy('esp idf with spaces')
def test_install_export_win(idf_copy: Path) -> None: def test_install_export_win(idf_copy: Path) -> None: