forked from espressif/esp-idf
ci: rename local idf_ci folder, avoid name collision
This commit is contained in:
35
conftest.py
35
conftest.py
@@ -36,20 +36,23 @@ from _pytest.config import Config
|
|||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import FixtureRequest
|
||||||
from artifacts_handler import ArtifactType
|
from artifacts_handler import ArtifactType
|
||||||
from dynamic_pipelines.constants import TEST_RELATED_APPS_DOWNLOAD_URLS_FILENAME
|
from dynamic_pipelines.constants import TEST_RELATED_APPS_DOWNLOAD_URLS_FILENAME
|
||||||
from idf_ci.app import import_apps_from_txt
|
from idf_ci_local.app import import_apps_from_txt
|
||||||
from idf_ci.uploader import AppDownloader, AppUploader
|
from idf_ci_local.uploader import AppDownloader
|
||||||
from idf_ci_utils import IDF_PATH, idf_relpath
|
from idf_ci_local.uploader import AppUploader
|
||||||
from idf_pytest.constants import (
|
from idf_ci_utils import IDF_PATH
|
||||||
DEFAULT_SDKCONFIG,
|
from idf_ci_utils import idf_relpath
|
||||||
ENV_MARKERS,
|
from idf_pytest.constants import DEFAULT_LOGDIR
|
||||||
SPECIAL_MARKERS,
|
from idf_pytest.constants import DEFAULT_SDKCONFIG
|
||||||
TARGET_MARKERS,
|
from idf_pytest.constants import ENV_MARKERS
|
||||||
PytestCase,
|
from idf_pytest.constants import SPECIAL_MARKERS
|
||||||
DEFAULT_LOGDIR,
|
from idf_pytest.constants import TARGET_MARKERS
|
||||||
)
|
from idf_pytest.constants import PytestCase
|
||||||
from idf_pytest.plugin import IDF_PYTEST_EMBEDDED_KEY, ITEM_PYTEST_CASE_KEY, IdfPytestEmbedded
|
from idf_pytest.plugin import IDF_PYTEST_EMBEDDED_KEY
|
||||||
|
from idf_pytest.plugin import ITEM_PYTEST_CASE_KEY
|
||||||
|
from idf_pytest.plugin import IdfPytestEmbedded
|
||||||
from idf_pytest.utils import format_case_id
|
from idf_pytest.utils import format_case_id
|
||||||
from pytest_embedded.plugin import multi_dut_argument, multi_dut_fixture
|
from pytest_embedded.plugin import multi_dut_argument
|
||||||
|
from pytest_embedded.plugin import multi_dut_fixture
|
||||||
from pytest_embedded_idf.dut import IdfDut
|
from pytest_embedded_idf.dut import IdfDut
|
||||||
from pytest_embedded_idf.unity_tester import CaseTester
|
from pytest_embedded_idf.unity_tester import CaseTester
|
||||||
|
|
||||||
@@ -426,8 +429,10 @@ def pytest_addoption(parser: pytest.Parser) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def pytest_configure(config: Config) -> None:
|
def pytest_configure(config: Config) -> None:
|
||||||
from pytest_embedded_idf.utils import supported_targets, preview_targets
|
from idf_pytest.constants import PREVIEW_TARGETS
|
||||||
from idf_pytest.constants import SUPPORTED_TARGETS, PREVIEW_TARGETS
|
from idf_pytest.constants import SUPPORTED_TARGETS
|
||||||
|
from pytest_embedded_idf.utils import preview_targets
|
||||||
|
from pytest_embedded_idf.utils import supported_targets
|
||||||
|
|
||||||
supported_targets.set(SUPPORTED_TARGETS)
|
supported_targets.set(SUPPORTED_TARGETS)
|
||||||
preview_targets.set(PREVIEW_TARGETS)
|
preview_targets.set(PREVIEW_TARGETS)
|
||||||
|
@@ -15,8 +15,8 @@ from gitlab import GitlabUpdateError
|
|||||||
from gitlab_api import Gitlab
|
from gitlab_api import Gitlab
|
||||||
from idf_build_apps import App
|
from idf_build_apps import App
|
||||||
from idf_build_apps.constants import BuildStatus
|
from idf_build_apps.constants import BuildStatus
|
||||||
from idf_ci.app import AppWithMetricsInfo
|
from idf_ci_local.app import AppWithMetricsInfo
|
||||||
from idf_ci.uploader import AppUploader
|
from idf_ci_local.uploader import AppUploader
|
||||||
from prettytable import PrettyTable
|
from prettytable import PrettyTable
|
||||||
|
|
||||||
from .constants import BINARY_SIZE_METRIC_NAME
|
from .constants import BINARY_SIZE_METRIC_NAME
|
||||||
@@ -211,7 +211,7 @@ class ReportGenerator:
|
|||||||
items: t.List[t.Union[TestCase, GitlabJob, AppWithMetricsInfo]],
|
items: t.List[t.Union[TestCase, GitlabJob, AppWithMetricsInfo]],
|
||||||
key: t.Union[str, t.Callable[[t.Union[TestCase, GitlabJob, AppWithMetricsInfo]], t.Any]],
|
key: t.Union[str, t.Callable[[t.Union[TestCase, GitlabJob, AppWithMetricsInfo]], t.Any]],
|
||||||
order: str = 'asc',
|
order: str = 'asc',
|
||||||
sort_function: t.Optional[t.Callable[[t.Any], t.Any]] = None
|
sort_function: t.Optional[t.Callable[[t.Any], t.Any]] = None,
|
||||||
) -> t.List[t.Union[TestCase, GitlabJob, AppWithMetricsInfo]]:
|
) -> t.List[t.Union[TestCase, GitlabJob, AppWithMetricsInfo]]:
|
||||||
"""
|
"""
|
||||||
Sort items based on a given key, order, and optional custom sorting function.
|
Sort items based on a given key, order, and optional custom sorting function.
|
||||||
@@ -219,11 +219,13 @@ class ReportGenerator:
|
|||||||
:param items: List of items to sort.
|
:param items: List of items to sort.
|
||||||
:param key: A string representing the attribute name or a function to extract the sorting key.
|
:param key: A string representing the attribute name or a function to extract the sorting key.
|
||||||
:param order: Order of sorting ('asc' for ascending, 'desc' for descending).
|
:param order: Order of sorting ('asc' for ascending, 'desc' for descending).
|
||||||
:param sort_function: A custom function to control sorting logic (e.g., prioritizing positive/negative/zero values).
|
:param sort_function: A custom function to control sorting logic
|
||||||
|
(e.g., prioritizing positive/negative/zero values).
|
||||||
:return: List of sorted instances.
|
:return: List of sorted instances.
|
||||||
"""
|
"""
|
||||||
key_func = None
|
key_func = None
|
||||||
if isinstance(key, str):
|
if isinstance(key, str):
|
||||||
|
|
||||||
def key_func(item: t.Any) -> t.Any:
|
def key_func(item: t.Any) -> t.Any:
|
||||||
return getattr(item, key)
|
return getattr(item, key)
|
||||||
|
|
||||||
@@ -249,7 +251,7 @@ class ReportGenerator:
|
|||||||
return comment
|
return comment
|
||||||
|
|
||||||
def _update_mr_comment(self, comment: str, print_retry_jobs_message: bool) -> None:
|
def _update_mr_comment(self, comment: str, print_retry_jobs_message: bool) -> None:
|
||||||
retry_job_picture_comment = (f'{RETRY_JOB_TITLE}\n\n' f'{RETRY_JOB_PICTURE_LINK}').format(
|
retry_job_picture_comment = (f'{RETRY_JOB_TITLE}\n\n{RETRY_JOB_PICTURE_LINK}').format(
|
||||||
pic_url=get_repository_file_url(RETRY_JOB_PICTURE_PATH)
|
pic_url=get_repository_file_url(RETRY_JOB_PICTURE_PATH)
|
||||||
)
|
)
|
||||||
del_retry_job_pic_pattern = re.escape(RETRY_JOB_TITLE) + r'.*?' + re.escape(f'{RETRY_JOB_PICTURE_PATH})')
|
del_retry_job_pic_pattern = re.escape(RETRY_JOB_TITLE) + r'.*?' + re.escape(f'{RETRY_JOB_PICTURE_PATH})')
|
||||||
@@ -332,7 +334,12 @@ class BuildReportGenerator(ReportGenerator):
|
|||||||
2. Sort other items by absolute size_difference_percentage.
|
2. Sort other items by absolute size_difference_percentage.
|
||||||
"""
|
"""
|
||||||
# Priority: 0 for zero binaries, 1 for non-zero binaries
|
# Priority: 0 for zero binaries, 1 for non-zero binaries
|
||||||
zero_binary_priority = 1 if item.metrics[BINARY_SIZE_METRIC_NAME].source_value != 0 or item.metrics[BINARY_SIZE_METRIC_NAME].target_value != 0 else 0
|
zero_binary_priority = (
|
||||||
|
1
|
||||||
|
if item.metrics[BINARY_SIZE_METRIC_NAME].source_value != 0
|
||||||
|
or item.metrics[BINARY_SIZE_METRIC_NAME].target_value != 0
|
||||||
|
else 0
|
||||||
|
)
|
||||||
# Secondary sort: Negative absolute size_difference_percentage for descending order
|
# Secondary sort: Negative absolute size_difference_percentage for descending order
|
||||||
size_difference_sort = abs(item.metrics[BINARY_SIZE_METRIC_NAME].difference_percentage)
|
size_difference_sort = abs(item.metrics[BINARY_SIZE_METRIC_NAME].difference_percentage)
|
||||||
return zero_binary_priority, size_difference_sort
|
return zero_binary_priority, size_difference_sort
|
||||||
@@ -342,19 +349,23 @@ class BuildReportGenerator(ReportGenerator):
|
|||||||
Generate a markdown table for the top N apps by size difference.
|
Generate a markdown table for the top N apps by size difference.
|
||||||
Only includes apps with size differences greater than 500 bytes.
|
Only includes apps with size differences greater than 500 bytes.
|
||||||
"""
|
"""
|
||||||
filtered_apps = [app for app in self.apps if abs(app.metrics[BINARY_SIZE_METRIC_NAME].difference) > SIZE_DIFFERENCE_BYTES_THRESHOLD]
|
filtered_apps = [
|
||||||
|
app
|
||||||
|
for app in self.apps
|
||||||
|
if abs(app.metrics[BINARY_SIZE_METRIC_NAME].difference) > SIZE_DIFFERENCE_BYTES_THRESHOLD
|
||||||
|
]
|
||||||
|
|
||||||
top_apps = sorted(
|
top_apps = sorted(
|
||||||
filtered_apps,
|
filtered_apps, key=lambda app: abs(app.metrics[BINARY_SIZE_METRIC_NAME].difference_percentage), reverse=True
|
||||||
key=lambda app: abs(app.metrics[BINARY_SIZE_METRIC_NAME].difference_percentage),
|
|
||||||
reverse=True
|
|
||||||
)[:TOP_N_APPS_BY_SIZE_DIFF]
|
)[:TOP_N_APPS_BY_SIZE_DIFF]
|
||||||
|
|
||||||
if not top_apps:
|
if not top_apps:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
table = (f'\n⚠️⚠️⚠️ Top {len(top_apps)} Apps with Binary Size Sorted by Size Difference\n'
|
table = (
|
||||||
f'Note: Apps with changes of less than {SIZE_DIFFERENCE_BYTES_THRESHOLD} bytes are not shown.\n')
|
f'\n⚠️⚠️⚠️ Top {len(top_apps)} Apps with Binary Size Sorted by Size Difference\n'
|
||||||
|
f'Note: Apps with changes of less than {SIZE_DIFFERENCE_BYTES_THRESHOLD} bytes are not shown.\n'
|
||||||
|
)
|
||||||
table += '| App Dir | Build Dir | Size Diff (bytes) | Size Diff (%) |\n'
|
table += '| App Dir | Build Dir | Size Diff (bytes) | Size Diff (%) |\n'
|
||||||
table += '|---------|-----------|-------------------|---------------|\n'
|
table += '|---------|-----------|-------------------|---------------|\n'
|
||||||
for app in top_apps:
|
for app in top_apps:
|
||||||
@@ -363,13 +374,17 @@ class BuildReportGenerator(ReportGenerator):
|
|||||||
f'{app.metrics[BINARY_SIZE_METRIC_NAME].difference} | '
|
f'{app.metrics[BINARY_SIZE_METRIC_NAME].difference} | '
|
||||||
f'{app.metrics[BINARY_SIZE_METRIC_NAME].difference_percentage}% |\n'
|
f'{app.metrics[BINARY_SIZE_METRIC_NAME].difference_percentage}% |\n'
|
||||||
)
|
)
|
||||||
table += ('\n**For more details, please click on the numbers in the summary above '
|
table += (
|
||||||
'to view the corresponding report files.** ⬆️⬆️⬆️\n\n')
|
'\n**For more details, please click on the numbers in the summary above '
|
||||||
|
'to view the corresponding report files.** ⬆️⬆️⬆️\n\n'
|
||||||
|
)
|
||||||
|
|
||||||
return table
|
return table
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def split_new_and_existing_apps(apps: t.Iterable[AppWithMetricsInfo]) -> t.Tuple[t.List[AppWithMetricsInfo], t.List[AppWithMetricsInfo]]:
|
def split_new_and_existing_apps(
|
||||||
|
apps: t.Iterable[AppWithMetricsInfo],
|
||||||
|
) -> t.Tuple[t.List[AppWithMetricsInfo], t.List[AppWithMetricsInfo]]:
|
||||||
"""
|
"""
|
||||||
Splits apps into new apps and existing apps.
|
Splits apps into new apps and existing apps.
|
||||||
|
|
||||||
@@ -388,10 +403,7 @@ class BuildReportGenerator(ReportGenerator):
|
|||||||
:param preserve: Whether to filter preserved apps.
|
:param preserve: Whether to filter preserved apps.
|
||||||
:return: Filtered list of apps.
|
:return: Filtered list of apps.
|
||||||
"""
|
"""
|
||||||
return [
|
return [app for app in self.apps if app.build_status == build_status and app.preserve == preserve]
|
||||||
app for app in self.apps
|
|
||||||
if app.build_status == build_status and app.preserve == preserve
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_built_apps_report_parts(self) -> t.List[str]:
|
def get_built_apps_report_parts(self) -> t.List[str]:
|
||||||
"""
|
"""
|
||||||
@@ -430,14 +442,13 @@ class BuildReportGenerator(ReportGenerator):
|
|||||||
'build_dir',
|
'build_dir',
|
||||||
],
|
],
|
||||||
value_functions=[
|
value_functions=[
|
||||||
(
|
('Your Branch App Size', lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].source_value)),
|
||||||
'Your Branch App Size',
|
|
||||||
lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].source_value)
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
'Bin Files with Build Log (without map and elf)',
|
'Bin Files with Build Log (without map and elf)',
|
||||||
lambda app: self.get_download_link_for_url(
|
lambda app: self.get_download_link_for_url(
|
||||||
self.app_presigned_urls_dict[app.build_path][ArtifactType.BUILD_DIR_WITHOUT_MAP_AND_ELF_FILES.value]
|
self.app_presigned_urls_dict[app.build_path][
|
||||||
|
ArtifactType.BUILD_DIR_WITHOUT_MAP_AND_ELF_FILES.value
|
||||||
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -481,26 +492,16 @@ class BuildReportGenerator(ReportGenerator):
|
|||||||
'build_dir',
|
'build_dir',
|
||||||
],
|
],
|
||||||
value_functions=[
|
value_functions=[
|
||||||
(
|
('Your Branch App Size', lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].source_value)),
|
||||||
'Your Branch App Size',
|
('Target Branch App Size', lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].target_value)),
|
||||||
lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].source_value)
|
('Size Diff', lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].difference)),
|
||||||
),
|
('Size Diff, %', lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].difference_percentage)),
|
||||||
(
|
|
||||||
'Target Branch App Size',
|
|
||||||
lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].target_value)
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'Size Diff',
|
|
||||||
lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].difference)
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'Size Diff, %',
|
|
||||||
lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].difference_percentage)
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
'Bin Files with Build Log (without map and elf)',
|
'Bin Files with Build Log (without map and elf)',
|
||||||
lambda app: self.get_download_link_for_url(
|
lambda app: self.get_download_link_for_url(
|
||||||
self.app_presigned_urls_dict[app.build_path][ArtifactType.BUILD_DIR_WITHOUT_MAP_AND_ELF_FILES.value]
|
self.app_presigned_urls_dict[app.build_path][
|
||||||
|
ArtifactType.BUILD_DIR_WITHOUT_MAP_AND_ELF_FILES.value
|
||||||
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -528,12 +529,13 @@ class BuildReportGenerator(ReportGenerator):
|
|||||||
'build_dir',
|
'build_dir',
|
||||||
],
|
],
|
||||||
value_functions=[
|
value_functions=[
|
||||||
|
('Your Branch App Size', lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].source_value)),
|
||||||
(
|
(
|
||||||
'Your Branch App Size',
|
'Build Log',
|
||||||
lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].source_value)
|
lambda app: self.get_download_link_for_url(
|
||||||
|
self._uploader.get_app_presigned_url(app, ArtifactType.LOGS)
|
||||||
|
),
|
||||||
),
|
),
|
||||||
('Build Log', lambda app: self.get_download_link_for_url(
|
|
||||||
self._uploader.get_app_presigned_url(app, ArtifactType.LOGS))),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
sections.extend(new_non_test_related_apps_table_section)
|
sections.extend(new_non_test_related_apps_table_section)
|
||||||
@@ -562,24 +564,16 @@ class BuildReportGenerator(ReportGenerator):
|
|||||||
'build_dir',
|
'build_dir',
|
||||||
],
|
],
|
||||||
value_functions=[
|
value_functions=[
|
||||||
|
('Your Branch App Size', lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].source_value)),
|
||||||
|
('Target Branch App Size', lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].target_value)),
|
||||||
|
('Size Diff', lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].difference)),
|
||||||
|
('Size Diff, %', lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].difference_percentage)),
|
||||||
(
|
(
|
||||||
'Your Branch App Size',
|
'Build Log',
|
||||||
lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].source_value)
|
lambda app: self.get_download_link_for_url(
|
||||||
|
self._uploader.get_app_presigned_url(app, ArtifactType.LOGS)
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
|
||||||
'Target Branch App Size',
|
|
||||||
lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].target_value)
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'Size Diff',
|
|
||||||
lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].difference)
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'Size Diff, %',
|
|
||||||
lambda app: str(app.metrics[BINARY_SIZE_METRIC_NAME].difference_percentage)
|
|
||||||
),
|
|
||||||
('Build Log', lambda app: self.get_download_link_for_url(
|
|
||||||
self._uploader.get_app_presigned_url(app, ArtifactType.LOGS))),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
sections.extend(built_non_test_related_apps_table_section)
|
sections.extend(built_non_test_related_apps_table_section)
|
||||||
@@ -631,7 +625,12 @@ class BuildReportGenerator(ReportGenerator):
|
|||||||
headers=['App Dir', 'Build Dir', 'Failed Reason', 'Build Log'],
|
headers=['App Dir', 'Build Dir', 'Failed Reason', 'Build Log'],
|
||||||
row_attrs=['app_dir', 'build_dir', 'build_comment'],
|
row_attrs=['app_dir', 'build_dir', 'build_comment'],
|
||||||
value_functions=[
|
value_functions=[
|
||||||
('Build Log', lambda app: self.get_download_link_for_url(self._uploader.get_app_presigned_url(app, ArtifactType.LOGS))),
|
(
|
||||||
|
'Build Log',
|
||||||
|
lambda app: self.get_download_link_for_url(
|
||||||
|
self._uploader.get_app_presigned_url(app, ArtifactType.LOGS)
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
failed_apps_report_url = self.write_report_to_file(
|
failed_apps_report_url = self.write_report_to_file(
|
||||||
@@ -655,7 +654,12 @@ class BuildReportGenerator(ReportGenerator):
|
|||||||
headers=['App Dir', 'Build Dir', 'Skipped Reason', 'Build Log'],
|
headers=['App Dir', 'Build Dir', 'Skipped Reason', 'Build Log'],
|
||||||
row_attrs=['app_dir', 'build_dir', 'build_comment'],
|
row_attrs=['app_dir', 'build_dir', 'build_comment'],
|
||||||
value_functions=[
|
value_functions=[
|
||||||
('Build Log', lambda app: self.get_download_link_for_url(self._uploader.get_app_presigned_url(app, ArtifactType.LOGS))),
|
(
|
||||||
|
'Build Log',
|
||||||
|
lambda app: self.get_download_link_for_url(
|
||||||
|
self._uploader.get_app_presigned_url(app, ArtifactType.LOGS)
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
skipped_apps_report_url = self.write_report_to_file(
|
skipped_apps_report_url = self.write_report_to_file(
|
||||||
@@ -790,7 +794,7 @@ class TargetTestReportGenerator(ReportGenerator):
|
|||||||
'Test Case',
|
'Test Case',
|
||||||
'Test App Path',
|
'Test App Path',
|
||||||
'Failure Reason',
|
'Failure Reason',
|
||||||
f'Failures on your branch (40 latest testcases)',
|
'Failures on your branch (40 latest testcases)',
|
||||||
'Dut Log URL',
|
'Dut Log URL',
|
||||||
'Create Known Failure Case Jira',
|
'Create Known Failure Case Jira',
|
||||||
'Job URL',
|
'Job URL',
|
||||||
@@ -800,12 +804,10 @@ class TargetTestReportGenerator(ReportGenerator):
|
|||||||
value_functions=[
|
value_functions=[
|
||||||
(
|
(
|
||||||
'Failures on your branch (40 latest testcases)',
|
'Failures on your branch (40 latest testcases)',
|
||||||
lambda item: f"{getattr(item, 'latest_failed_count', '')} / {getattr(item, 'latest_total_count', '')}",
|
lambda item: f'{getattr(item, "latest_failed_count", "")} '
|
||||||
|
f'/ {getattr(item, "latest_total_count", "")}',
|
||||||
),
|
),
|
||||||
(
|
('Create Known Failure Case Jira', known_failure_issue_jira_fast_link),
|
||||||
'Create Known Failure Case Jira',
|
|
||||||
known_failure_issue_jira_fast_link
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
other_branch_cases_table_section = self.create_table_section(
|
other_branch_cases_table_section = self.create_table_section(
|
||||||
@@ -825,12 +827,10 @@ class TargetTestReportGenerator(ReportGenerator):
|
|||||||
value_functions=[
|
value_functions=[
|
||||||
(
|
(
|
||||||
'Cases that failed in other branches as well (40 latest testcases)',
|
'Cases that failed in other branches as well (40 latest testcases)',
|
||||||
lambda item: f"{getattr(item, 'latest_failed_count', '')} / {getattr(item, 'latest_total_count', '')}",
|
lambda item: f'{getattr(item, "latest_failed_count", "")} '
|
||||||
|
f'/ {getattr(item, "latest_total_count", "")}',
|
||||||
),
|
),
|
||||||
(
|
('Create Known Failure Case Jira', known_failure_issue_jira_fast_link),
|
||||||
'Create Known Failure Case Jira',
|
|
||||||
known_failure_issue_jira_fast_link
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
known_failures_cases_table_section = self.create_table_section(
|
known_failures_cases_table_section = self.create_table_section(
|
||||||
@@ -986,7 +986,8 @@ class JobReportGenerator(ReportGenerator):
|
|||||||
value_functions=[
|
value_functions=[
|
||||||
(
|
(
|
||||||
'Failures across all other branches (10 latest jobs)',
|
'Failures across all other branches (10 latest jobs)',
|
||||||
lambda item: f"{getattr(item, 'latest_failed_count', '')} / {getattr(item, 'latest_total_count', '')}",
|
lambda item: f'{getattr(item, "latest_failed_count", "")} '
|
||||||
|
f'/ {getattr(item, "latest_total_count", "")}',
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@@ -4,13 +4,14 @@ import argparse
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import __init__ # noqa: F401 # inject the system path
|
import __init__ # noqa: F401 # inject the system path
|
||||||
from dynamic_pipelines.constants import TEST_RELATED_APPS_FILENAME
|
|
||||||
from idf_build_apps import build_apps
|
from idf_build_apps import build_apps
|
||||||
from idf_build_apps import setup_logging
|
from idf_build_apps import setup_logging
|
||||||
from idf_build_apps.utils import semicolon_separated_str_to_list
|
from idf_build_apps.utils import semicolon_separated_str_to_list
|
||||||
from idf_ci.app import import_apps_from_txt
|
from idf_ci_local.app import import_apps_from_txt
|
||||||
from idf_pytest.constants import DEFAULT_IGNORE_WARNING_FILEPATH
|
from idf_pytest.constants import DEFAULT_IGNORE_WARNING_FILEPATH
|
||||||
|
|
||||||
|
from dynamic_pipelines.constants import TEST_RELATED_APPS_FILENAME
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser(description='Build Apps for Dynamic Pipeline')
|
parser = argparse.ArgumentParser(description='Build Apps for Dynamic Pipeline')
|
||||||
parser.add_argument('app_list_file', default=TEST_RELATED_APPS_FILENAME, help='List of apps to build')
|
parser.add_argument('app_list_file', default=TEST_RELATED_APPS_FILENAME, help='List of apps to build')
|
||||||
|
@@ -1,12 +1,22 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
"""This file is used for generating the child pipeline for build jobs."""
|
"""This file is used for generating the child pipeline for build jobs."""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
import __init__ # noqa: F401 # inject the system path
|
import __init__ # noqa: F401 # inject the system path
|
||||||
import yaml
|
import yaml
|
||||||
|
from idf_build_apps.utils import semicolon_separated_str_to_list
|
||||||
|
from idf_ci_local.app import dump_apps_to_txt
|
||||||
|
from idf_ci_utils import IDF_PATH
|
||||||
|
from idf_pytest.constants import DEFAULT_CONFIG_RULES_STR
|
||||||
|
from idf_pytest.constants import DEFAULT_FULL_BUILD_TEST_COMPONENTS
|
||||||
|
from idf_pytest.constants import DEFAULT_FULL_BUILD_TEST_FILEPATTERNS
|
||||||
|
from idf_pytest.constants import CollectMode
|
||||||
|
from idf_pytest.script import get_all_apps
|
||||||
|
|
||||||
from dynamic_pipelines.constants import DEFAULT_APPS_BUILD_PER_JOB
|
from dynamic_pipelines.constants import DEFAULT_APPS_BUILD_PER_JOB
|
||||||
from dynamic_pipelines.constants import DEFAULT_BUILD_CHILD_PIPELINE_FILEPATH
|
from dynamic_pipelines.constants import DEFAULT_BUILD_CHILD_PIPELINE_FILEPATH
|
||||||
from dynamic_pipelines.constants import DEFAULT_BUILD_CHILD_PIPELINE_NAME
|
from dynamic_pipelines.constants import DEFAULT_BUILD_CHILD_PIPELINE_NAME
|
||||||
@@ -18,14 +28,6 @@ from dynamic_pipelines.constants import TEST_RELATED_BUILD_JOB_NAME
|
|||||||
from dynamic_pipelines.models import BuildJob
|
from dynamic_pipelines.models import BuildJob
|
||||||
from dynamic_pipelines.models import EmptyJob
|
from dynamic_pipelines.models import EmptyJob
|
||||||
from dynamic_pipelines.utils import dump_jobs_to_yaml
|
from dynamic_pipelines.utils import dump_jobs_to_yaml
|
||||||
from idf_build_apps.utils import semicolon_separated_str_to_list
|
|
||||||
from idf_ci.app import dump_apps_to_txt
|
|
||||||
from idf_ci_utils import IDF_PATH
|
|
||||||
from idf_pytest.constants import CollectMode
|
|
||||||
from idf_pytest.constants import DEFAULT_CONFIG_RULES_STR
|
|
||||||
from idf_pytest.constants import DEFAULT_FULL_BUILD_TEST_COMPONENTS
|
|
||||||
from idf_pytest.constants import DEFAULT_FULL_BUILD_TEST_FILEPATTERNS
|
|
||||||
from idf_pytest.script import get_all_apps
|
|
||||||
|
|
||||||
|
|
||||||
def _separate_str_to_list(s: str) -> t.List[str]:
|
def _separate_str_to_list(s: str) -> t.List[str]:
|
||||||
@@ -101,7 +103,8 @@ def main(arguments: argparse.Namespace) -> None:
|
|||||||
print(f'Generate test related apps file {TEST_RELATED_APPS_FILENAME} with {len(test_related_apps)} apps')
|
print(f'Generate test related apps file {TEST_RELATED_APPS_FILENAME} with {len(test_related_apps)} apps')
|
||||||
dump_apps_to_txt(sorted(non_test_related_apps), NON_TEST_RELATED_APPS_FILENAME)
|
dump_apps_to_txt(sorted(non_test_related_apps), NON_TEST_RELATED_APPS_FILENAME)
|
||||||
print(
|
print(
|
||||||
f'Generate non-test related apps file {NON_TEST_RELATED_APPS_FILENAME} with {len(non_test_related_apps)} apps'
|
f'Generate non-test related apps file {NON_TEST_RELATED_APPS_FILENAME} '
|
||||||
|
f'with {len(non_test_related_apps)} apps'
|
||||||
)
|
)
|
||||||
|
|
||||||
if test_related_apps:
|
if test_related_apps:
|
||||||
@@ -171,7 +174,7 @@ if __name__ == '__main__':
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--compare-manifest-sha-filepath',
|
'--compare-manifest-sha-filepath',
|
||||||
default=os.path.join(IDF_PATH, '.manifest_sha'),
|
default=os.path.join(IDF_PATH, '.manifest_sha'),
|
||||||
help='Path to the recorded manifest sha file generated by `idf-build-apps dump-manifest-sha`'
|
help='Path to the recorded manifest sha file generated by `idf-build-apps dump-manifest-sha`',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--modified-components',
|
'--modified-components',
|
||||||
|
@@ -6,14 +6,15 @@ import os
|
|||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
import __init__ # noqa: F401 # inject the system path
|
import __init__ # noqa: F401 # inject the system path
|
||||||
|
from idf_ci_local.app import enrich_apps_with_metrics_info
|
||||||
|
from idf_ci_local.app import import_apps_from_txt
|
||||||
|
|
||||||
from dynamic_pipelines.report import BuildReportGenerator
|
from dynamic_pipelines.report import BuildReportGenerator
|
||||||
from dynamic_pipelines.report import JobReportGenerator
|
from dynamic_pipelines.report import JobReportGenerator
|
||||||
from dynamic_pipelines.report import TargetTestReportGenerator
|
from dynamic_pipelines.report import TargetTestReportGenerator
|
||||||
from dynamic_pipelines.utils import fetch_app_metrics
|
from dynamic_pipelines.utils import fetch_app_metrics
|
||||||
from dynamic_pipelines.utils import fetch_failed_jobs
|
from dynamic_pipelines.utils import fetch_failed_jobs
|
||||||
from dynamic_pipelines.utils import parse_testcases_from_filepattern
|
from dynamic_pipelines.utils import parse_testcases_from_filepattern
|
||||||
from idf_ci.app import enrich_apps_with_metrics_info
|
|
||||||
from idf_ci.app import import_apps_from_txt
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
2. Post the Build Report if it's running in an MR pipeline.
|
2. Post the Build Report if it's running in an MR pipeline.
|
||||||
3. Generate the child pipeline for target test jobs.
|
3. Generate the child pipeline for target test jobs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
@@ -15,24 +16,23 @@ from collections import defaultdict
|
|||||||
|
|
||||||
import __init__ # noqa: F401 # inject the system path
|
import __init__ # noqa: F401 # inject the system path
|
||||||
import yaml
|
import yaml
|
||||||
|
from idf_build_apps import App
|
||||||
|
from idf_ci_local.app import import_apps_from_txt
|
||||||
|
from idf_pytest.constants import TIMEOUT_4H_MARKERS
|
||||||
|
from idf_pytest.script import get_pytest_cases
|
||||||
|
|
||||||
from dynamic_pipelines.constants import BUILD_ONLY_LABEL
|
from dynamic_pipelines.constants import BUILD_ONLY_LABEL
|
||||||
from dynamic_pipelines.constants import DEFAULT_CASES_TEST_PER_JOB
|
from dynamic_pipelines.constants import DEFAULT_CASES_TEST_PER_JOB
|
||||||
from dynamic_pipelines.constants import DEFAULT_TARGET_TEST_CHILD_PIPELINE_FILEPATH
|
from dynamic_pipelines.constants import DEFAULT_TARGET_TEST_CHILD_PIPELINE_FILEPATH
|
||||||
from dynamic_pipelines.constants import DEFAULT_TARGET_TEST_CHILD_PIPELINE_NAME
|
from dynamic_pipelines.constants import DEFAULT_TARGET_TEST_CHILD_PIPELINE_NAME
|
||||||
from dynamic_pipelines.constants import DEFAULT_TARGET_TEST_JOB_TEMPLATE_NAME
|
from dynamic_pipelines.constants import DEFAULT_TARGET_TEST_JOB_TEMPLATE_NAME
|
||||||
from dynamic_pipelines.constants import DEFAULT_TEST_PATHS
|
from dynamic_pipelines.constants import DEFAULT_TEST_PATHS
|
||||||
from dynamic_pipelines.constants import (
|
from dynamic_pipelines.constants import KNOWN_GENERATE_TEST_CHILD_PIPELINE_WARNINGS_FILEPATH
|
||||||
KNOWN_GENERATE_TEST_CHILD_PIPELINE_WARNINGS_FILEPATH,
|
|
||||||
)
|
|
||||||
from dynamic_pipelines.constants import TIMEOUT_4H_TEMPLATE_NAME
|
from dynamic_pipelines.constants import TIMEOUT_4H_TEMPLATE_NAME
|
||||||
from dynamic_pipelines.models import EmptyJob
|
from dynamic_pipelines.models import EmptyJob
|
||||||
from dynamic_pipelines.models import Job
|
from dynamic_pipelines.models import Job
|
||||||
from dynamic_pipelines.models import TargetTestJob
|
from dynamic_pipelines.models import TargetTestJob
|
||||||
from dynamic_pipelines.utils import dump_jobs_to_yaml
|
from dynamic_pipelines.utils import dump_jobs_to_yaml
|
||||||
from idf_build_apps import App
|
|
||||||
from idf_ci.app import import_apps_from_txt
|
|
||||||
from idf_pytest.constants import TIMEOUT_4H_MARKERS
|
|
||||||
from idf_pytest.script import get_pytest_cases
|
|
||||||
|
|
||||||
|
|
||||||
def get_tags_with_amount(s: str) -> t.List[str]:
|
def get_tags_with_amount(s: str) -> t.List[str]:
|
||||||
|
@@ -11,18 +11,28 @@ from unittest.mock import patch
|
|||||||
sys.path.insert(0, os.path.join(f'{os.environ.get("IDF_PATH")}', 'tools', 'ci', 'python_packages'))
|
sys.path.insert(0, os.path.join(f'{os.environ.get("IDF_PATH")}', 'tools', 'ci', 'python_packages'))
|
||||||
sys.path.insert(0, os.path.join(f'{os.environ.get("IDF_PATH")}', 'tools', 'ci'))
|
sys.path.insert(0, os.path.join(f'{os.environ.get("IDF_PATH")}', 'tools', 'ci'))
|
||||||
|
|
||||||
from dynamic_pipelines.models import GitlabJob # noqa: E402
|
|
||||||
from dynamic_pipelines.report import JobReportGenerator, TargetTestReportGenerator, BuildReportGenerator # noqa: E402
|
|
||||||
from dynamic_pipelines.utils import load_file, parse_testcases_from_filepattern # noqa: E402
|
|
||||||
from idf_build_apps.constants import BuildStatus # noqa: E402
|
from idf_build_apps.constants import BuildStatus # noqa: E402
|
||||||
from idf_ci.app import import_apps_from_txt # noqa: E402
|
from idf_ci_local.app import enrich_apps_with_metrics_info # noqa: E402
|
||||||
from idf_ci.app import enrich_apps_with_metrics_info # noqa: E402
|
from idf_ci_local.app import import_apps_from_txt # noqa: E402
|
||||||
|
|
||||||
|
from dynamic_pipelines.models import GitlabJob # noqa: E402
|
||||||
|
from dynamic_pipelines.report import BuildReportGenerator # noqa: E402
|
||||||
|
from dynamic_pipelines.report import JobReportGenerator # noqa: E402
|
||||||
|
from dynamic_pipelines.report import TargetTestReportGenerator # noqa: E402
|
||||||
|
from dynamic_pipelines.utils import load_file # noqa: E402
|
||||||
|
from dynamic_pipelines.utils import parse_testcases_from_filepattern # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
class TestReportGeneration(unittest.TestCase):
|
class TestReportGeneration(unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.reports_sample_data_path = os.path.join(
|
self.reports_sample_data_path = os.path.join(
|
||||||
os.environ.get('IDF_PATH', ''), 'tools', 'ci', 'dynamic_pipelines', 'tests', 'test_report_generator', 'reports_sample_data'
|
os.environ.get('IDF_PATH', ''),
|
||||||
|
'tools',
|
||||||
|
'ci',
|
||||||
|
'dynamic_pipelines',
|
||||||
|
'tests',
|
||||||
|
'test_report_generator',
|
||||||
|
'reports_sample_data',
|
||||||
)
|
)
|
||||||
self.setup_patches()
|
self.setup_patches()
|
||||||
self.load_test_and_job_reports()
|
self.load_test_and_job_reports()
|
||||||
@@ -32,12 +42,15 @@ class TestReportGeneration(unittest.TestCase):
|
|||||||
self.gitlab_patcher = patch('dynamic_pipelines.report.Gitlab')
|
self.gitlab_patcher = patch('dynamic_pipelines.report.Gitlab')
|
||||||
self.uploader_patcher = patch('dynamic_pipelines.report.AppUploader')
|
self.uploader_patcher = patch('dynamic_pipelines.report.AppUploader')
|
||||||
self.failure_rate_patcher = patch('dynamic_pipelines.report.fetch_failed_testcases_failure_ratio')
|
self.failure_rate_patcher = patch('dynamic_pipelines.report.fetch_failed_testcases_failure_ratio')
|
||||||
self.env_patcher = patch.dict('os.environ', {
|
self.env_patcher = patch.dict(
|
||||||
'CI_DASHBOARD_HOST': 'https://test_dashboard_host',
|
'os.environ',
|
||||||
'CI_PAGES_URL': 'https://artifacts_path',
|
{
|
||||||
'CI_JOB_ID': '1',
|
'CI_DASHBOARD_HOST': 'https://test_dashboard_host',
|
||||||
'JIRA_SERVER': 'https://jira.com',
|
'CI_PAGES_URL': 'https://artifacts_path',
|
||||||
})
|
'CI_JOB_ID': '1',
|
||||||
|
'JIRA_SERVER': 'https://jira.com',
|
||||||
|
},
|
||||||
|
)
|
||||||
self.yaml_dump_patcher = patch('dynamic_pipelines.report.yaml.dump')
|
self.yaml_dump_patcher = patch('dynamic_pipelines.report.yaml.dump')
|
||||||
|
|
||||||
self.MockGitlab = self.gitlab_patcher.start()
|
self.MockGitlab = self.gitlab_patcher.start()
|
||||||
@@ -86,12 +99,21 @@ class TestReportGeneration(unittest.TestCase):
|
|||||||
|
|
||||||
def create_report_generators(self) -> None:
|
def create_report_generators(self) -> None:
|
||||||
jobs_response_raw = load_file(os.path.join(self.reports_sample_data_path, 'jobs_api_response.json'))
|
jobs_response_raw = load_file(os.path.join(self.reports_sample_data_path, 'jobs_api_response.json'))
|
||||||
failure_rate_jobs_response = load_file(os.path.join(self.reports_sample_data_path, 'failure_rate_jobs_response.json'))
|
failure_rate_jobs_response = load_file(
|
||||||
built_apps_size_info_response = json.loads(load_file(os.path.join(self.reports_sample_data_path, 'apps_size_info_api_response.json')))
|
os.path.join(self.reports_sample_data_path, 'failure_rate_jobs_response.json')
|
||||||
|
)
|
||||||
|
built_apps_size_info_response = json.loads(
|
||||||
|
load_file(os.path.join(self.reports_sample_data_path, 'apps_size_info_api_response.json'))
|
||||||
|
)
|
||||||
failure_rates = {item['name']: item for item in json.loads(failure_rate_jobs_response).get('jobs', [])}
|
failure_rates = {item['name']: item for item in json.loads(failure_rate_jobs_response).get('jobs', [])}
|
||||||
jobs = [GitlabJob.from_json_data(job_json, failure_rates.get(job_json['name'], {})) for job_json in json.loads(jobs_response_raw)['jobs']]
|
jobs = [
|
||||||
|
GitlabJob.from_json_data(job_json, failure_rates.get(job_json['name'], {}))
|
||||||
|
for job_json in json.loads(jobs_response_raw)['jobs']
|
||||||
|
]
|
||||||
test_cases = parse_testcases_from_filepattern(os.path.join(self.reports_sample_data_path, 'XUNIT_*.xml'))
|
test_cases = parse_testcases_from_filepattern(os.path.join(self.reports_sample_data_path, 'XUNIT_*.xml'))
|
||||||
apps = enrich_apps_with_metrics_info(built_apps_size_info_response, import_apps_from_txt(os.path.join(self.reports_sample_data_path, 'apps')))
|
apps = enrich_apps_with_metrics_info(
|
||||||
|
built_apps_size_info_response, import_apps_from_txt(os.path.join(self.reports_sample_data_path, 'apps'))
|
||||||
|
)
|
||||||
self.target_test_report_generator = TargetTestReportGenerator(
|
self.target_test_report_generator = TargetTestReportGenerator(
|
||||||
project_id=123,
|
project_id=123,
|
||||||
mr_iid=1,
|
mr_iid=1,
|
||||||
@@ -99,25 +121,13 @@ class TestReportGeneration(unittest.TestCase):
|
|||||||
job_id=0,
|
job_id=0,
|
||||||
commit_id='cccc',
|
commit_id='cccc',
|
||||||
title='Test Report',
|
title='Test Report',
|
||||||
test_cases=test_cases
|
test_cases=test_cases,
|
||||||
)
|
)
|
||||||
self.job_report_generator = JobReportGenerator(
|
self.job_report_generator = JobReportGenerator(
|
||||||
project_id=123,
|
project_id=123, mr_iid=1, pipeline_id=456, job_id=0, commit_id='cccc', title='Job Report', jobs=jobs
|
||||||
mr_iid=1,
|
|
||||||
pipeline_id=456,
|
|
||||||
job_id=0,
|
|
||||||
commit_id='cccc',
|
|
||||||
title='Job Report',
|
|
||||||
jobs=jobs
|
|
||||||
)
|
)
|
||||||
self.build_report_generator = BuildReportGenerator(
|
self.build_report_generator = BuildReportGenerator(
|
||||||
project_id=123,
|
project_id=123, mr_iid=1, pipeline_id=456, job_id=0, commit_id='cccc', title='Build Report', apps=apps
|
||||||
mr_iid=1,
|
|
||||||
pipeline_id=456,
|
|
||||||
job_id=0,
|
|
||||||
commit_id='cccc',
|
|
||||||
title='Build Report',
|
|
||||||
apps=apps
|
|
||||||
)
|
)
|
||||||
self.target_test_report_generator._known_failure_cases_set = {
|
self.target_test_report_generator._known_failure_cases_set = {
|
||||||
'*.test_wpa_supplicant_ut',
|
'*.test_wpa_supplicant_ut',
|
||||||
@@ -189,7 +199,7 @@ class TestReportGeneration(unittest.TestCase):
|
|||||||
difference=i * 1000,
|
difference=i * 1000,
|
||||||
difference_percentage=i * 0.5,
|
difference_percentage=i * 0.5,
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
for i in range(1, 6)
|
for i in range(1, 6)
|
||||||
]
|
]
|
||||||
@@ -200,7 +210,7 @@ class TestReportGeneration(unittest.TestCase):
|
|||||||
job_id=0,
|
job_id=0,
|
||||||
commit_id='cccc',
|
commit_id='cccc',
|
||||||
title='Build Report',
|
title='Build Report',
|
||||||
apps=apps_with_size_diff
|
apps=apps_with_size_diff,
|
||||||
)
|
)
|
||||||
|
|
||||||
top_apps_table = build_report_generator._generate_top_n_apps_by_size_table()
|
top_apps_table = build_report_generator._generate_top_n_apps_by_size_table()
|
||||||
@@ -218,12 +228,7 @@ class TestReportGeneration(unittest.TestCase):
|
|||||||
size_difference_percentage=1.0,
|
size_difference_percentage=1.0,
|
||||||
build_status=BuildStatus.SUCCESS,
|
build_status=BuildStatus.SUCCESS,
|
||||||
preserve=True,
|
preserve=True,
|
||||||
metrics={
|
metrics={'binary_size': MagicMock(difference=1000, difference_percentage=1.0)},
|
||||||
'binary_size': MagicMock(
|
|
||||||
difference=1000,
|
|
||||||
difference_percentage=1.0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
MagicMock(
|
MagicMock(
|
||||||
app_dir='test_app_2',
|
app_dir='test_app_2',
|
||||||
@@ -232,23 +237,12 @@ class TestReportGeneration(unittest.TestCase):
|
|||||||
size_difference_percentage=2.0,
|
size_difference_percentage=2.0,
|
||||||
build_status=BuildStatus.SUCCESS,
|
build_status=BuildStatus.SUCCESS,
|
||||||
preserve=False,
|
preserve=False,
|
||||||
metrics={
|
metrics={'binary_size': MagicMock(difference=2000, difference_percentage=2.0)},
|
||||||
'binary_size': MagicMock(
|
|
||||||
difference=2000,
|
|
||||||
difference_percentage=2.0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
build_report_generator = BuildReportGenerator(
|
build_report_generator = BuildReportGenerator(
|
||||||
project_id=123,
|
project_id=123, mr_iid=1, pipeline_id=456, job_id=0, commit_id='cccc', title='Build Report', apps=apps
|
||||||
mr_iid=1,
|
|
||||||
pipeline_id=456,
|
|
||||||
job_id=0,
|
|
||||||
commit_id='cccc',
|
|
||||||
title='Build Report',
|
|
||||||
apps=apps
|
|
||||||
)
|
)
|
||||||
|
|
||||||
built_apps_report_parts = build_report_generator.get_built_apps_report_parts()
|
built_apps_report_parts = build_report_generator.get_built_apps_report_parts()
|
||||||
@@ -264,24 +258,14 @@ class TestReportGeneration(unittest.TestCase):
|
|||||||
build_dir='build_dir_1',
|
build_dir='build_dir_1',
|
||||||
build_comment='Compilation error',
|
build_comment='Compilation error',
|
||||||
build_status=BuildStatus.FAILED,
|
build_status=BuildStatus.FAILED,
|
||||||
metrics={
|
metrics={'binary_size': MagicMock(difference=None, difference_percentage=None)},
|
||||||
'binary_size': MagicMock(
|
|
||||||
difference=None,
|
|
||||||
difference_percentage=None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
MagicMock(
|
MagicMock(
|
||||||
app_dir='failed_app_2',
|
app_dir='failed_app_2',
|
||||||
build_dir='build_dir_2',
|
build_dir='build_dir_2',
|
||||||
build_comment='Linker error',
|
build_comment='Linker error',
|
||||||
build_status=BuildStatus.FAILED,
|
build_status=BuildStatus.FAILED,
|
||||||
metrics={
|
metrics={'binary_size': MagicMock(difference=None, difference_percentage=None)},
|
||||||
'binary_size': MagicMock(
|
|
||||||
difference=None,
|
|
||||||
difference_percentage=None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -292,7 +276,7 @@ class TestReportGeneration(unittest.TestCase):
|
|||||||
job_id=0,
|
job_id=0,
|
||||||
commit_id='cccc',
|
commit_id='cccc',
|
||||||
title='Build Report',
|
title='Build Report',
|
||||||
apps=failed_apps
|
apps=failed_apps,
|
||||||
)
|
)
|
||||||
|
|
||||||
failed_apps_report_parts = build_report_generator.get_failed_apps_report_parts()
|
failed_apps_report_parts = build_report_generator.get_failed_apps_report_parts()
|
||||||
@@ -308,24 +292,14 @@ class TestReportGeneration(unittest.TestCase):
|
|||||||
build_dir='build_dir_1',
|
build_dir='build_dir_1',
|
||||||
build_comment='Dependencies unmet',
|
build_comment='Dependencies unmet',
|
||||||
build_status=BuildStatus.SKIPPED,
|
build_status=BuildStatus.SKIPPED,
|
||||||
metrics={
|
metrics={'binary_size': MagicMock(difference=None, difference_percentage=None)},
|
||||||
'binary_size': MagicMock(
|
|
||||||
difference=None,
|
|
||||||
difference_percentage=None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
MagicMock(
|
MagicMock(
|
||||||
app_dir='skipped_app_2',
|
app_dir='skipped_app_2',
|
||||||
build_dir='build_dir_2',
|
build_dir='build_dir_2',
|
||||||
build_comment='Feature flag disabled',
|
build_comment='Feature flag disabled',
|
||||||
build_status=BuildStatus.SKIPPED,
|
build_status=BuildStatus.SKIPPED,
|
||||||
metrics={
|
metrics={'binary_size': MagicMock(difference=None, difference_percentage=None)},
|
||||||
'binary_size': MagicMock(
|
|
||||||
difference=None,
|
|
||||||
difference_percentage=None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -336,7 +310,7 @@ class TestReportGeneration(unittest.TestCase):
|
|||||||
job_id=0,
|
job_id=0,
|
||||||
commit_id='cccc',
|
commit_id='cccc',
|
||||||
title='Build Report',
|
title='Build Report',
|
||||||
apps=skipped_apps
|
apps=skipped_apps,
|
||||||
)
|
)
|
||||||
|
|
||||||
skipped_apps_report_parts = build_report_generator.get_skipped_apps_report_parts()
|
skipped_apps_report_parts = build_report_generator.get_skipped_apps_report_parts()
|
||||||
|
@@ -44,7 +44,7 @@ tools/ci/get_known_failure_cases_file.py
|
|||||||
tools/unit-test-app/**/*
|
tools/unit-test-app/**/*
|
||||||
tools/ci/gitlab_yaml_linter.py
|
tools/ci/gitlab_yaml_linter.py
|
||||||
tools/ci/dynamic_pipelines/**/*
|
tools/ci/dynamic_pipelines/**/*
|
||||||
tools/ci/idf_ci/**/*
|
tools/ci/idf_ci_local/**/*
|
||||||
tools/ci/get_supported_examples.sh
|
tools/ci/get_supported_examples.sh
|
||||||
tools/ci/python_packages/common_test_methods.py
|
tools/ci/python_packages/common_test_methods.py
|
||||||
tools/ci/python_packages/gitlab_api.py
|
tools/ci/python_packages/gitlab_api.py
|
||||||
|
@@ -9,8 +9,9 @@ from dynamic_pipelines.constants import BINARY_SIZE_METRIC_NAME
|
|||||||
from idf_build_apps import App
|
from idf_build_apps import App
|
||||||
from idf_build_apps import CMakeApp
|
from idf_build_apps import CMakeApp
|
||||||
from idf_build_apps import json_to_app
|
from idf_build_apps import json_to_app
|
||||||
from idf_ci.uploader import AppUploader
|
|
||||||
from idf_ci.uploader import get_app_uploader
|
from idf_ci_local.uploader import AppUploader
|
||||||
|
from idf_ci_local.uploader import get_app_uploader
|
||||||
|
|
||||||
|
|
||||||
class IdfCMakeApp(CMakeApp):
|
class IdfCMakeApp(CMakeApp):
|
||||||
@@ -34,6 +35,7 @@ class Metrics:
|
|||||||
"""
|
"""
|
||||||
Represents a metric and its values for source, target, and the differences.
|
Represents a metric and its values for source, target, and the differences.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
source_value: t.Optional[float] = None,
|
source_value: t.Optional[float] = None,
|
||||||
@@ -65,10 +67,7 @@ class AppWithMetricsInfo(IdfCMakeApp):
|
|||||||
def __init__(self, **kwargs: t.Any) -> None:
|
def __init__(self, **kwargs: t.Any) -> None:
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.metrics = {
|
self.metrics = {metric_name: metric_data for metric_name, metric_data in kwargs.get('metrics', {}).items()}
|
||||||
metric_name: metric_data
|
|
||||||
for metric_name, metric_data in kwargs.get('metrics', {}).items()
|
|
||||||
}
|
|
||||||
self.is_new_app = kwargs.get('is_new_app', False)
|
self.is_new_app = kwargs.get('is_new_app', False)
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
@@ -96,8 +95,7 @@ def import_apps_from_txt(input_filepath: str) -> t.List[App]:
|
|||||||
|
|
||||||
|
|
||||||
def enrich_apps_with_metrics_info(
|
def enrich_apps_with_metrics_info(
|
||||||
app_metrics_info_map: t.Dict[str, t.Dict[str, t.Any]],
|
app_metrics_info_map: t.Dict[str, t.Dict[str, t.Any]], apps: t.List[App]
|
||||||
apps: t.List[App]
|
|
||||||
) -> t.List[AppWithMetricsInfo]:
|
) -> t.List[AppWithMetricsInfo]:
|
||||||
def _get_full_attributes(obj: App) -> t.Dict[str, t.Any]:
|
def _get_full_attributes(obj: App) -> t.Dict[str, t.Any]:
|
||||||
"""
|
"""
|
||||||
@@ -130,10 +128,7 @@ def enrich_apps_with_metrics_info(
|
|||||||
key = f'{app.app_dir}_{app.config_name}_{app.target}'
|
key = f'{app.app_dir}_{app.config_name}_{app.target}'
|
||||||
app_attributes = _get_full_attributes(app)
|
app_attributes = _get_full_attributes(app)
|
||||||
|
|
||||||
metrics = {
|
metrics = {metric_name: default_metric for metric_name, default_metric in default_metrics_structure.items()}
|
||||||
metric_name: default_metric
|
|
||||||
for metric_name, default_metric in default_metrics_structure.items()
|
|
||||||
}
|
|
||||||
is_new_app = False
|
is_new_app = False
|
||||||
|
|
||||||
if key in app_metrics_info_map:
|
if key in app_metrics_info_map:
|
@@ -141,7 +141,7 @@ class AppUploader(AppDownloader):
|
|||||||
self._client.fget_object(getenv('IDF_S3_BUCKET'), obj_name, zip_filename)
|
self._client.fget_object(getenv('IDF_S3_BUCKET'), obj_name, zip_filename)
|
||||||
print(f'Downloaded to {zip_filename}')
|
print(f'Downloaded to {zip_filename}')
|
||||||
except minio.error.S3Error as e:
|
except minio.error.S3Error as e:
|
||||||
raise RuntimeError('Shouldn\'t happen, please report this bug in the CI channel' + str(e))
|
raise RuntimeError("Shouldn't happen, please report this bug in the CI channel" + str(e))
|
||||||
|
|
||||||
with ZipFile(zip_filename, 'r') as zr:
|
with ZipFile(zip_filename, 'r') as zr:
|
||||||
zr.extractall()
|
zr.extractall()
|
@@ -12,20 +12,20 @@ import pytest
|
|||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
from idf_build_apps import App
|
from idf_build_apps import App
|
||||||
from idf_build_apps import find_apps
|
from idf_build_apps import find_apps
|
||||||
from idf_build_apps.constants import BuildStatus
|
|
||||||
from idf_build_apps.constants import SUPPORTED_TARGETS
|
from idf_build_apps.constants import SUPPORTED_TARGETS
|
||||||
from idf_ci.app import IdfCMakeApp
|
from idf_build_apps.constants import BuildStatus
|
||||||
from idf_ci_utils import get_all_manifest_files
|
from idf_ci_local.app import IdfCMakeApp
|
||||||
from idf_ci_utils import IDF_PATH
|
from idf_ci_utils import IDF_PATH
|
||||||
|
from idf_ci_utils import get_all_manifest_files
|
||||||
from idf_ci_utils import idf_relpath
|
from idf_ci_utils import idf_relpath
|
||||||
from idf_ci_utils import to_list
|
from idf_ci_utils import to_list
|
||||||
from idf_py_actions.constants import PREVIEW_TARGETS as TOOLS_PREVIEW_TARGETS
|
from idf_py_actions.constants import PREVIEW_TARGETS as TOOLS_PREVIEW_TARGETS
|
||||||
from idf_py_actions.constants import SUPPORTED_TARGETS as TOOLS_SUPPORTED_TARGETS
|
from idf_py_actions.constants import SUPPORTED_TARGETS as TOOLS_SUPPORTED_TARGETS
|
||||||
|
|
||||||
from .constants import CollectMode
|
|
||||||
from .constants import DEFAULT_BUILD_LOG_FILENAME
|
from .constants import DEFAULT_BUILD_LOG_FILENAME
|
||||||
from .constants import DEFAULT_CONFIG_RULES_STR
|
from .constants import DEFAULT_CONFIG_RULES_STR
|
||||||
from .constants import DEFAULT_SIZE_JSON_FILENAME
|
from .constants import DEFAULT_SIZE_JSON_FILENAME
|
||||||
|
from .constants import CollectMode
|
||||||
from .constants import PytestCase
|
from .constants import PytestCase
|
||||||
from .plugin import IdfPytestEmbedded
|
from .plugin import IdfPytestEmbedded
|
||||||
|
|
||||||
@@ -84,7 +84,9 @@ def get_pytest_cases(
|
|||||||
return cases
|
return cases
|
||||||
|
|
||||||
def _get_pytest_cases(_target: str, _single_target_duplicate_mode: bool = False) -> t.List[PytestCase]:
|
def _get_pytest_cases(_target: str, _single_target_duplicate_mode: bool = False) -> t.List[PytestCase]:
|
||||||
collector = IdfPytestEmbedded(_target, config_name=config_name, single_target_duplicate_mode=_single_target_duplicate_mode, apps=apps)
|
collector = IdfPytestEmbedded(
|
||||||
|
_target, config_name=config_name, single_target_duplicate_mode=_single_target_duplicate_mode, apps=apps
|
||||||
|
)
|
||||||
|
|
||||||
with io.StringIO() as buf:
|
with io.StringIO() as buf:
|
||||||
with redirect_stdout(buf):
|
with redirect_stdout(buf):
|
||||||
@@ -100,9 +102,7 @@ def get_pytest_cases(
|
|||||||
print(f'WARNING: no pytest app found for target {_target} under paths {", ".join(paths)}')
|
print(f'WARNING: no pytest app found for target {_target} under paths {", ".join(paths)}')
|
||||||
else:
|
else:
|
||||||
print(buf.getvalue())
|
print(buf.getvalue())
|
||||||
raise RuntimeError(
|
raise RuntimeError(f'pytest collection failed at {", ".join(paths)} with command "{" ".join(cmd)}"')
|
||||||
f'pytest collection failed at {", ".join(paths)} with command \"{" ".join(cmd)}\"'
|
|
||||||
)
|
|
||||||
|
|
||||||
return collector.cases # type: ignore
|
return collector.cases # type: ignore
|
||||||
|
|
||||||
@@ -155,26 +155,28 @@ def get_all_apps(
|
|||||||
# target could be comma separated list
|
# target could be comma separated list
|
||||||
all_apps: t.List[App] = []
|
all_apps: t.List[App] = []
|
||||||
for _t in set(target.split(',')):
|
for _t in set(target.split(',')):
|
||||||
all_apps.extend(find_apps(
|
all_apps.extend(
|
||||||
paths,
|
find_apps(
|
||||||
_t,
|
paths,
|
||||||
build_system=IdfCMakeApp,
|
_t,
|
||||||
recursive=True,
|
build_system=IdfCMakeApp,
|
||||||
build_dir='build_@t_@w',
|
recursive=True,
|
||||||
config_rules_str=config_rules_str or DEFAULT_CONFIG_RULES_STR,
|
build_dir='build_@t_@w',
|
||||||
build_log_filename=DEFAULT_BUILD_LOG_FILENAME,
|
config_rules_str=config_rules_str or DEFAULT_CONFIG_RULES_STR,
|
||||||
size_json_filename=DEFAULT_SIZE_JSON_FILENAME,
|
build_log_filename=DEFAULT_BUILD_LOG_FILENAME,
|
||||||
check_warnings=True,
|
size_json_filename=DEFAULT_SIZE_JSON_FILENAME,
|
||||||
manifest_rootpath=IDF_PATH,
|
check_warnings=True,
|
||||||
compare_manifest_sha_filepath=compare_manifest_sha_filepath,
|
manifest_rootpath=IDF_PATH,
|
||||||
manifest_files=get_all_manifest_files(),
|
compare_manifest_sha_filepath=compare_manifest_sha_filepath,
|
||||||
default_build_targets=SUPPORTED_TARGETS + (extra_default_build_targets or []),
|
manifest_files=get_all_manifest_files(),
|
||||||
modified_components=modified_components,
|
default_build_targets=SUPPORTED_TARGETS + (extra_default_build_targets or []),
|
||||||
modified_files=modified_files,
|
modified_components=modified_components,
|
||||||
ignore_app_dependencies_components=ignore_app_dependencies_components,
|
modified_files=modified_files,
|
||||||
ignore_app_dependencies_filepatterns=ignore_app_dependencies_filepatterns,
|
ignore_app_dependencies_components=ignore_app_dependencies_components,
|
||||||
include_skipped_apps=True,
|
ignore_app_dependencies_filepatterns=ignore_app_dependencies_filepatterns,
|
||||||
))
|
include_skipped_apps=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
pytest_cases = get_pytest_cases(
|
pytest_cases = get_pytest_cases(
|
||||||
paths,
|
paths,
|
||||||
|
Reference in New Issue
Block a user