Merge branch 'feature/download_only_required_ut_bin' into 'master'

CI: Download only required bin for unit tests

Closes IDF-1608

See merge request espressif/esp-idf!9266
This commit is contained in:
Ivan Grokhotkov 2020-08-06 10:50:59 +08:00
commit 20cbdeab65
13 changed files with 277 additions and 278 deletions

View File

@ -6,7 +6,6 @@ import ttfw_idf
@ttfw_idf.idf_example_test(env_tag="test_jtag_arm")
def test_examples_app_trace_to_host(env, extra_data):
rel_project_path = os.path.join('examples', 'system', 'app_trace_to_host')
dut = env.get_dut('app_trace_to_host', rel_project_path)
idf_path = dut.app.get_sdk_path()
@ -28,8 +27,7 @@ def test_examples_app_trace_to_host(env, extra_data):
with ttfw_idf.CustomProcess(' '.join([os.path.join(idf_path, 'tools/esp_app_trace/logtrace_proc.py'),
'adc.log',
os.path.join(dut.app.get_binary_path(rel_project_path),
'app_trace_to_host.elf')]),
os.path.join(dut.app.binary_path, 'app_trace_to_host.elf')]),
logfile='logtrace_proc.log') as logtrace:
logtrace.pexpect_proc.expect_exact('Parse trace file')
logtrace.pexpect_proc.expect_exact('Parsing completed.')

View File

@ -13,9 +13,8 @@ def test_examples_sysview_tracing(env, extra_data):
rel_project_path = os.path.join('examples', 'system', 'sysview_tracing')
dut = env.get_dut('sysview_tracing', rel_project_path)
idf_path = dut.app.get_sdk_path()
proj_path = os.path.join(idf_path, rel_project_path)
elf_path = os.path.join(dut.app.get_binary_path(rel_project_path), 'sysview_tracing.elf')
proj_path = os.path.join(dut.app.idf_path, rel_project_path)
elf_path = os.path.join(dut.app.binary_path, 'sysview_tracing.elf')
def get_temp_file():
with tempfile.NamedTemporaryFile(delete=False) as f:

View File

@ -12,9 +12,8 @@ def test_examples_sysview_tracing_heap_log(env, extra_data):
rel_project_path = os.path.join('examples', 'system', 'sysview_tracing_heap_log')
dut = env.get_dut('sysview_tracing_heap_log', rel_project_path)
idf_path = dut.app.get_sdk_path()
proj_path = os.path.join(idf_path, rel_project_path)
elf_path = os.path.join(dut.app.get_binary_path(rel_project_path), 'sysview_tracing_heap_log.elf')
proj_path = os.path.join(dut.app.idf_path, rel_project_path)
elf_path = os.path.join(dut.app.binary_path, 'sysview_tracing_heap_log.elf')
def get_temp_file():
with tempfile.NamedTemporaryFile(delete=False) as f:
@ -45,7 +44,7 @@ def test_examples_sysview_tracing_heap_log(env, extra_data):
# dut has been restarted by gdb since the last dut.expect()
dut.expect('esp_apptrace: Initialized TRAX on CPU0')
with ttfw_idf.CustomProcess(' '.join([os.path.join(idf_path, 'tools/esp_app_trace/sysviewtrace_proc.py'),
with ttfw_idf.CustomProcess(' '.join([os.path.join(dut.app.idf_path, 'tools/esp_app_trace/sysviewtrace_proc.py'),
'-p',
'-b', elf_path,
tempfiles[1]]),

View File

@ -24,6 +24,7 @@ assign_test:
- $TEST_APP_CONFIG_OUTPUT_PATH
- build_examples/artifact_index.json
- build_test_apps/artifact_index.json
- tools/unit-test-app/builds/artifact_index.json
expire_in: 1 week
only:
variables:
@ -35,11 +36,11 @@ assign_test:
- $BOT_LABEL_CUSTOM_TEST
script:
# assign example tests
- python tools/ci/python_packages/ttfw_idf/CIAssignExampleTest.py $IDF_PATH/examples $CI_TARGET_TEST_CONFIG_FILE $EXAMPLE_CONFIG_OUTPUT_PATH
- python tools/ci/python_packages/ttfw_idf/IDFAssignTest.py example_test $IDF_PATH/examples $CI_TARGET_TEST_CONFIG_FILE $EXAMPLE_CONFIG_OUTPUT_PATH
# assign test apps
- python tools/ci/python_packages/ttfw_idf/CIAssignExampleTest.py --custom-group test-apps --job-prefix test_app_test_ $IDF_PATH/tools/test_apps $CI_TARGET_TEST_CONFIG_FILE $TEST_APP_CONFIG_OUTPUT_PATH
- python tools/ci/python_packages/ttfw_idf/IDFAssignTest.py custom_test $IDF_PATH/tools/test_apps $CI_TARGET_TEST_CONFIG_FILE $TEST_APP_CONFIG_OUTPUT_PATH
# assign unit test cases
- python tools/ci/python_packages/ttfw_idf/CIAssignUnitTest.py $UNIT_TEST_CASE_FILE $CI_TARGET_TEST_CONFIG_FILE $IDF_PATH/components/idf_test/unit_test/CIConfigs
- python tools/ci/python_packages/ttfw_idf/IDFAssignTest.py unit_test $UNIT_TEST_CASE_FILE $CI_TARGET_TEST_CONFIG_FILE $IDF_PATH/components/idf_test/unit_test/CIConfigs
# clone test script to assign tests
- ./tools/ci/retry_failed.sh git clone $TEST_SCRIPT_REPOSITORY
- python $CHECKOUT_REF_SCRIPT auto_test_script auto_test_script

View File

@ -45,7 +45,7 @@ build_ssc_esp32s2:
artifacts:
paths:
- tools/unit-test-app/output/${IDF_TARGET}
- tools/unit-test-app/builds/${IDF_TARGET}/*.json
- tools/unit-test-app/builds/*.json
- tools/unit-test-app/builds/${IDF_TARGET}/*/size.json
- components/idf_test/unit_test/*.yml
- $LOG_PATH

View File

@ -109,7 +109,6 @@
stage: target_test
dependencies:
- assign_test
- build_esp_idf_tests_cmake_esp32
only:
refs:
- master
@ -546,7 +545,6 @@ UT_034:
extends: .unit_test_template
dependencies:
- assign_test
- build_esp_idf_tests_cmake_esp32s2
only:
refs:
# Due to lack of runners, the tests are only done by manual trigger

View File

@ -169,4 +169,7 @@ if [ "${TEST_TYPE}" = "unit_test" ]; then
mkdir -p ${dst}/partition_table
cp ${src}/partition_table/*.bin ${dst}/partition_table/
done
# Copy app list json files to build path
mv ${BUILD_PATH}/${IDF_TARGET}/*.json ${BUILD_PATH}
fi

View File

@ -109,7 +109,7 @@ class Gitlab(object):
try:
data = job.artifact(a_path)
except gitlab.GitlabGetError as e:
print("Failed to download '{}' form job {}".format(a_path, job_id))
print("Failed to download '{}' from job {}".format(a_path, job_id))
raise e
raw_data_list.append(data)
if destination:

View File

@ -1,114 +0,0 @@
# Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Command line tool to assign example tests to CI test jobs.
"""
# TODO: Need to handle running examples on different chips
import os
import re
import argparse
import json
import gitlab_api
from tiny_test_fw.Utility import CIAssignTest
IDF_PATH_FROM_ENV = os.getenv("IDF_PATH")
class ExampleGroup(CIAssignTest.Group):
SORT_KEYS = CI_JOB_MATCH_KEYS = ["env_tag", "target"]
BUILD_LOCAL_DIR = "build_examples"
BUILD_JOB_NAMES = ["build_examples_cmake_esp32", "build_examples_cmake_esp32s2"]
class TestAppsGroup(ExampleGroup):
BUILD_LOCAL_DIR = "build_test_apps"
BUILD_JOB_NAMES = ["build_test_apps_esp32", "build_test_apps_esp32s2"]
class CIExampleAssignTest(CIAssignTest.AssignTest):
CI_TEST_JOB_PATTERN = re.compile(r"^example_test_.+")
def get_artifact_index_file(case_group=ExampleGroup):
if IDF_PATH_FROM_ENV:
artifact_index_file = os.path.join(IDF_PATH_FROM_ENV,
case_group.BUILD_LOCAL_DIR, "artifact_index.json")
else:
artifact_index_file = "artifact_index.json"
return artifact_index_file
def create_artifact_index_file(project_id=None, pipeline_id=None, case_group=ExampleGroup):
if project_id is None:
project_id = os.getenv("CI_PROJECT_ID")
if pipeline_id is None:
pipeline_id = os.getenv("CI_PIPELINE_ID")
gitlab_inst = gitlab_api.Gitlab(project_id)
artifact_index_list = []
def format_build_log_path():
parallel = job_info["parallel_num"] # Could be None if "parallel_num" not defined for the job
return "{}/list_job_{}.json".format(case_group.BUILD_LOCAL_DIR, parallel or 1)
for build_job_name in case_group.BUILD_JOB_NAMES:
job_info_list = gitlab_inst.find_job_id(build_job_name, pipeline_id=pipeline_id)
for job_info in job_info_list:
raw_data = gitlab_inst.download_artifact(job_info["id"], [format_build_log_path()])[0]
build_info_list = [json.loads(line) for line in raw_data.decode().splitlines()]
for build_info in build_info_list:
build_info["ci_job_id"] = job_info["id"]
artifact_index_list.append(build_info)
artifact_index_file = get_artifact_index_file(case_group=case_group)
try:
os.makedirs(os.path.dirname(artifact_index_file))
except OSError:
# already created
pass
with open(artifact_index_file, "w") as f:
json.dump(artifact_index_list, f)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("test_case",
help="test case folder or file")
parser.add_argument("ci_config_file",
help="gitlab ci config file")
parser.add_argument("output_path",
help="output path of config files")
parser.add_argument("--pipeline_id", "-p", type=int, default=None,
help="pipeline_id")
parser.add_argument("--job-prefix",
help="prefix of the test job name in CI yml file")
parser.add_argument("--test-case-file-pattern",
help="file name pattern used to find Python test case files")
parser.add_argument('--custom-group',
help='select custom-group for the test cases, if other than ExampleTest',
choices=['example', 'test-apps'], default='example')
args = parser.parse_args()
if args.job_prefix:
CIExampleAssignTest.CI_TEST_JOB_PATTERN = re.compile(r"^{}.+".format(args.job_prefix))
case_group = ExampleGroup if args.custom_group == 'example' else TestAppsGroup
assign_test = CIExampleAssignTest(args.test_case, args.ci_config_file, case_group=case_group)
assign_test.assign_cases()
assign_test.output_configs(args.output_path)
create_artifact_index_file(case_group=case_group)

View File

@ -3,12 +3,11 @@ import errno
import json
import logging
import os
import re
from collections import defaultdict
from find_apps import find_apps
from find_build_apps import BUILD_SYSTEMS, BUILD_SYSTEM_CMAKE
from ttfw_idf.CIAssignExampleTest import CIExampleAssignTest, TestAppsGroup, ExampleGroup
from ttfw_idf.IDFAssignTest import ExampleAssignTest, TestAppsAssignTest
VALID_TARGETS = [
'esp32',
@ -98,10 +97,9 @@ def main():
test_cases = []
for path in set(args.paths):
if args.test_type == 'example_test':
assign = CIExampleAssignTest(path, args.ci_config_file, ExampleGroup)
assign = ExampleAssignTest(path, args.ci_config_file)
elif args.test_type == 'test_apps':
CIExampleAssignTest.CI_TEST_JOB_PATTERN = re.compile(r'^test_app_test_.+')
assign = CIExampleAssignTest(path, args.ci_config_file, TestAppsGroup)
assign = TestAppsAssignTest(path, args.ci_config_file)
else:
raise SystemExit(1) # which is impossible

View File

@ -13,14 +13,16 @@
# limitations under the License.
""" IDF Test Applications """
import subprocess
import hashlib
import json
import os
import re
import subprocess
import sys
from abc import abstractmethod
from tiny_test_fw import App
from . import CIAssignExampleTest
from .IDFAssignTest import ExampleGroup, TestAppsGroup, UnitTestGroup, IDFCaseGroup
try:
import gitlab_api
@ -90,32 +92,46 @@ class Artifacts(object):
ret = None
return ret
def download_artifacts(self):
def _get_app_base_path(self):
if self.artifact_info:
base_path = os.path.join(self.artifact_info["work_dir"], self.artifact_info["build_dir"])
job_id = self.artifact_info["ci_job_id"]
# 1. download flash args file
if self.artifact_info["build_system"] == "cmake":
flash_arg_file = os.path.join(base_path, "flasher_args.json")
else:
flash_arg_file = os.path.join(base_path, "download.config")
self.gitlab_inst.download_artifact(job_id, [flash_arg_file], self.dest_root_path)
# 2. download all binary files
flash_files, flash_settings, app_name = parse_flash_settings(os.path.join(self.dest_root_path,
flash_arg_file))
artifact_files = [os.path.join(base_path, p[1]) for p in flash_files]
artifact_files.append(os.path.join(base_path, app_name + ".elf"))
self.gitlab_inst.download_artifact(job_id, artifact_files, self.dest_root_path)
# 3. download sdkconfig file
self.gitlab_inst.download_artifact(job_id, [os.path.join(os.path.dirname(base_path), "sdkconfig")],
self.dest_root_path)
return os.path.join(self.artifact_info["work_dir"], self.artifact_info["build_dir"])
else:
base_path = None
return None
def _get_flash_arg_file(self, base_path, job_id):
if self.artifact_info["build_system"] == "cmake":
flash_arg_file = os.path.join(base_path, "flasher_args.json")
else:
flash_arg_file = os.path.join(base_path, "download.config")
self.gitlab_inst.download_artifact(job_id, [flash_arg_file], self.dest_root_path)
return flash_arg_file
def _download_binary_files(self, base_path, job_id, flash_arg_file):
flash_files, flash_settings, app_name = parse_flash_settings(os.path.join(self.dest_root_path,
flash_arg_file))
artifact_files = [os.path.join(base_path, p[1]) for p in flash_files]
artifact_files.append(os.path.join(base_path, app_name + ".elf"))
self.gitlab_inst.download_artifact(job_id, artifact_files, self.dest_root_path)
def _download_sdkconfig_file(self, base_path, job_id):
self.gitlab_inst.download_artifact(job_id, [os.path.join(os.path.dirname(base_path), "sdkconfig")],
self.dest_root_path)
def download_artifacts(self):
if not self.artifact_info:
return None
base_path = self._get_app_base_path()
job_id = self.artifact_info["ci_job_id"]
# 1. download flash args file
flash_arg_file = self._get_flash_arg_file(base_path, job_id)
# 2. download all binary files
self._download_binary_files(base_path, job_id, flash_arg_file)
# 3. download sdkconfig file
self._download_sdkconfig_file(base_path, job_id)
return base_path
def download_artifact_files(self, file_names):
@ -135,6 +151,20 @@ class Artifacts(object):
return base_path
class UnitTestArtifacts(Artifacts):
BUILDS_DIR_RE = re.compile(r'^builds/')
def _get_app_base_path(self):
if self.artifact_info:
output_dir = self.BUILDS_DIR_RE.sub('output/', self.artifact_info["build_dir"])
return os.path.join(self.artifact_info["app_dir"], output_dir)
else:
return None
def _download_sdkconfig_file(self, base_path, job_id):
self.gitlab_inst.download_artifact(job_id, [os.path.join(base_path, "sdkconfig")], self.dest_root_path)
class IDFApp(App.BaseApp):
"""
Implements common esp-idf application behavior.
@ -144,13 +174,16 @@ class IDFApp(App.BaseApp):
IDF_DOWNLOAD_CONFIG_FILE = "download.config"
IDF_FLASH_ARGS_FILE = "flasher_args.json"
def __init__(self, app_path, config_name=None, target=None):
def __init__(self, app_path, config_name=None, target=None, case_group=IDFCaseGroup, artifact_cls=Artifacts):
super(IDFApp, self).__init__(app_path)
self.app_path = app_path
self.config_name = config_name
self.target = target
self.idf_path = self.get_sdk_path()
self.binary_path = self.get_binary_path(app_path, config_name, target)
self.elf_file = self._get_elf_file_path(self.binary_path)
self.case_group = case_group
self.artifact_cls = artifact_cls
self.binary_path = self.get_binary_path()
self.elf_file = self._get_elf_file_path()
self._elf_file_sha256 = None
assert os.path.exists(self.binary_path)
if self.IDF_DOWNLOAD_CONFIG_FILE not in os.listdir(self.binary_path):
@ -166,9 +199,16 @@ class IDFApp(App.BaseApp):
self.flash_files, self.flash_settings = self._parse_flash_download_config()
self.partition_table = self._parse_partition_table()
def __str__(self):
parts = ['app<{}>'.format(self.app_path)]
if self.config_name:
parts.extend('config<{}>'.format(self.config_name))
if self.target:
parts.extend('target<{}>'.format(self.target))
return ' '.join(parts)
@classmethod
def get_sdk_path(cls):
# type: () -> str
def get_sdk_path(cls): # type: () -> str
idf_path = os.getenv("IDF_PATH")
assert idf_path
assert os.path.exists(idf_path)
@ -184,7 +224,7 @@ class IDFApp(App.BaseApp):
def get_sdkconfig(self):
"""
reads sdkconfig and returns a dictionary with all configuredvariables
reads sdkconfig and returns a dictionary with all configured variables
:raise: AssertionError: if sdkconfig file does not exist in defined paths
"""
@ -202,27 +242,35 @@ class IDFApp(App.BaseApp):
d[configs[0]] = configs[1].rstrip()
return d
def get_binary_path(self, app_path, config_name=None, target=None):
# type: (str, str, str) -> str
"""
get binary path according to input app_path.
subclass must overwrite this method.
:param app_path: path of application
:param config_name: name of the application build config. Will match any config if None
:param target: target name. Will match for target if None
:return: abs app binary path
"""
@abstractmethod
def _try_get_binary_from_local_fs(self):
pass
@staticmethod
def _get_elf_file_path(binary_path):
def get_binary_path(self):
path = self._try_get_binary_from_local_fs()
if path:
return path
artifacts = self.artifact_cls(self.idf_path,
self.case_group.get_artifact_index_file(),
self.app_path, self.config_name, self.target)
if isinstance(self, LoadableElfTestApp):
assert self.app_files
path = artifacts.download_artifact_files(self.app_files)
else:
path = artifacts.download_artifacts()
if path:
return os.path.join(self.idf_path, path)
else:
raise OSError("Failed to get binary for {}".format(self))
def _get_elf_file_path(self):
ret = ""
file_names = os.listdir(binary_path)
file_names = os.listdir(self.binary_path)
for fn in file_names:
if os.path.splitext(fn)[1] == ".elf":
ret = os.path.join(binary_path, fn)
ret = os.path.join(self.binary_path, fn)
return ret
def _parse_flash_download_config(self):
@ -281,7 +329,7 @@ class IDFApp(App.BaseApp):
if isinstance(raw_error, bytes):
raw_error = raw_error.decode()
if 'Traceback' in raw_error:
# Some exception occured. It is possible that we've tried the wrong binary file.
# Some exception occurred. It is possible that we've tried the wrong binary file.
errors.append((path, raw_error))
continue
@ -333,62 +381,52 @@ class IDFApp(App.BaseApp):
class Example(IDFApp):
def __init__(self, app_path, config_name='default', target='esp32', case_group=ExampleGroup, artifacts_cls=Artifacts):
if not config_name:
config_name = 'default'
if not target:
target = 'esp32'
super(Example, self).__init__(app_path, config_name, target, case_group, artifacts_cls)
def _get_sdkconfig_paths(self):
"""
overrides the parent method to provide exact path of sdkconfig for example tests
"""
return [os.path.join(self.binary_path, "..", "sdkconfig")]
def _try_get_binary_from_local_fs(self, app_path, config_name=None, target=None, local_build_dir="build_examples"):
def _try_get_binary_from_local_fs(self):
# build folder of example path
path = os.path.join(self.idf_path, app_path, "build")
path = os.path.join(self.idf_path, self.app_path, "build")
if os.path.exists(path):
return path
if not config_name:
config_name = "default"
if not target:
target = "esp32"
# Search for CI build folders.
# Path format: $IDF_PATH/build_examples/app_path_with_underscores/config/target
# (see tools/ci/build_examples_cmake.sh)
# (see tools/ci/build_examples.sh)
# For example: $IDF_PATH/build_examples/examples_get-started_blink/default/esp32
app_path_underscored = app_path.replace(os.path.sep, "_")
example_path = os.path.join(self.idf_path, local_build_dir)
app_path_underscored = self.app_path.replace(os.path.sep, "_")
example_path = os.path.join(self.idf_path, self.case_group.LOCAL_BUILD_DIR)
for dirpath in os.listdir(example_path):
if os.path.basename(dirpath) == app_path_underscored:
path = os.path.join(example_path, dirpath, config_name, target, "build")
path = os.path.join(example_path, dirpath, self.config_name, self.target, "build")
if os.path.exists(path):
return path
else:
return None
def get_binary_path(self, app_path, config_name=None, target=None):
path = self._try_get_binary_from_local_fs(app_path, config_name, target)
if path:
return path
else:
artifacts = Artifacts(self.idf_path,
CIAssignExampleTest.get_artifact_index_file(case_group=CIAssignExampleTest.ExampleGroup),
app_path, config_name, target)
path = artifacts.download_artifacts()
if path:
return os.path.join(self.idf_path, path)
else:
raise OSError("Failed to find example binary")
class UT(IDFApp):
def get_binary_path(self, app_path, config_name=None, target=None):
def __init__(self, app_path, config_name='default', target='esp32', case_group=UnitTestGroup, artifacts_cls=UnitTestArtifacts):
if not config_name:
config_name = "default"
config_name = 'default'
if not target:
target = 'esp32'
super(UT, self).__init__(app_path, config_name, target, case_group, artifacts_cls)
path = os.path.join(self.idf_path, app_path)
default_build_path = os.path.join(path, "build")
if os.path.exists(default_build_path):
return default_build_path
def _try_get_binary_from_local_fs(self):
path = os.path.join(self.idf_path, self.app_path, "build")
if os.path.exists(path):
return path
# first try to get from build folder of unit-test-app
path = os.path.join(self.idf_path, "tools", "unit-test-app", "build")
@ -396,66 +434,44 @@ class UT(IDFApp):
# found, use bin in build path
return path
# ``make ut-build-all-configs`` or ``make ut-build-CONFIG`` will copy binary to output folder
path = os.path.join(self.idf_path, "tools", "unit-test-app", "output", target, config_name)
# ``build_unit_test.sh`` will copy binary to output folder
path = os.path.join(self.idf_path, "tools", "unit-test-app", "output", self.target, self.config_name)
if os.path.exists(path):
return path
raise OSError("Failed to get unit-test-app binary path")
return None
class TestApp(Example):
def get_binary_path(self, app_path, config_name=None, target=None):
path = self._try_get_binary_from_local_fs(app_path, config_name, target, local_build_dir="build_test_apps")
if path:
return path
else:
artifacts = Artifacts(self.idf_path,
CIAssignExampleTest.get_artifact_index_file(case_group=CIAssignExampleTest.TestAppsGroup),
app_path, config_name, target)
path = artifacts.download_artifacts()
if path:
return os.path.join(self.idf_path, path)
else:
raise OSError("Failed to find example binary")
def __init__(self, app_path, config_name='default', target='esp32', case_group=TestAppsGroup, artifacts_cls=Artifacts):
super(TestApp, self).__init__(app_path, config_name, target, case_group, artifacts_cls)
class LoadableElfTestApp(TestApp):
def __init__(self, app_path, app_files, config_name=None, target=None):
def __init__(self, app_path, app_files, config_name='default', target='esp32', case_group=TestAppsGroup, artifacts_cls=Artifacts):
# add arg `app_files` for loadable elf test_app.
# Such examples only build elf files, so it doesn't generate flasher_args.json.
# So we can't get app files from config file. Test case should pass it to application.
super(IDFApp, self).__init__(app_path)
self.app_path = app_path
self.app_files = app_files
self.config_name = config_name
self.target = target
self.config_name = config_name or 'default'
self.target = target or 'esp32'
self.idf_path = self.get_sdk_path()
self.binary_path = self.get_binary_path(app_path, config_name, target)
self.elf_file = self._get_elf_file_path(self.binary_path)
self.case_group = case_group
self.artifact_cls = artifacts_cls
self.binary_path = self.get_binary_path()
self.elf_file = self._get_elf_file_path()
assert os.path.exists(self.binary_path)
def get_binary_path(self, app_path, config_name=None, target=None):
path = self._try_get_binary_from_local_fs(app_path, config_name, target, local_build_dir="build_test_apps")
if path:
return path
else:
artifacts = Artifacts(self.idf_path,
CIAssignExampleTest.get_artifact_index_file(case_group=CIAssignExampleTest.TestAppsGroup),
app_path, config_name, target)
path = artifacts.download_artifact_files(self.app_files)
if path:
return os.path.join(self.idf_path, path)
else:
raise OSError("Failed to find the loadable ELF file")
class SSC(IDFApp):
def get_binary_path(self, app_path, config_name=None, target=None):
def get_binary_path(self):
# TODO: to implement SSC get binary path
return app_path
return self.app_path
class AT(IDFApp):
def get_binary_path(self, app_path, config_name=None, target=None):
def get_binary_path(self):
# TODO: to implement AT get binary path
return app_path
return self.app_path

View File

@ -1,9 +1,11 @@
"""
Command line tool to assign unit tests to CI test jobs.
Command line tool to assign tests to CI test jobs.
"""
import argparse
import errno
import json
import os
import re
import argparse
import yaml
@ -12,16 +14,88 @@ try:
except ImportError:
from yaml import Loader as Loader
import gitlab_api
from tiny_test_fw.Utility import CIAssignTest
IDF_PATH_FROM_ENV = os.getenv("IDF_PATH")
class Group(CIAssignTest.Group):
class IDFCaseGroup(CIAssignTest.Group):
LOCAL_BUILD_DIR = None
BUILD_JOB_NAMES = None
@classmethod
def get_artifact_index_file(cls):
assert cls.LOCAL_BUILD_DIR
if IDF_PATH_FROM_ENV:
artifact_index_file = os.path.join(IDF_PATH_FROM_ENV, cls.LOCAL_BUILD_DIR, "artifact_index.json")
else:
artifact_index_file = "artifact_index.json"
return artifact_index_file
class IDFAssignTest(CIAssignTest.AssignTest):
def format_build_log_path(self, parallel_num):
return "{}/list_job_{}.json".format(self.case_group.LOCAL_BUILD_DIR, parallel_num)
def create_artifact_index_file(self, project_id=None, pipeline_id=None):
if project_id is None:
project_id = os.getenv("CI_PROJECT_ID")
if pipeline_id is None:
pipeline_id = os.getenv("CI_PIPELINE_ID")
gitlab_inst = gitlab_api.Gitlab(project_id)
artifact_index_list = []
for build_job_name in self.case_group.BUILD_JOB_NAMES:
job_info_list = gitlab_inst.find_job_id(build_job_name, pipeline_id=pipeline_id)
for job_info in job_info_list:
parallel_num = job_info["parallel_num"] or 1 # Could be None if "parallel_num" not defined for the job
raw_data = gitlab_inst.download_artifact(job_info["id"],
[self.format_build_log_path(parallel_num)])[0]
build_info_list = [json.loads(line) for line in raw_data.decode().splitlines()]
for build_info in build_info_list:
build_info["ci_job_id"] = job_info["id"]
artifact_index_list.append(build_info)
artifact_index_file = self.case_group.get_artifact_index_file()
try:
os.makedirs(os.path.dirname(artifact_index_file))
except OSError as e:
if e.errno != errno.EEXIST:
raise e
with open(artifact_index_file, "w") as f:
json.dump(artifact_index_list, f)
SUPPORTED_TARGETS = [
'esp32',
'esp32s2',
]
class ExampleGroup(IDFCaseGroup):
SORT_KEYS = CI_JOB_MATCH_KEYS = ["env_tag", "target"]
LOCAL_BUILD_DIR = "build_examples"
BUILD_JOB_NAMES = ["build_examples_cmake_{}".format(target) for target in SUPPORTED_TARGETS]
class TestAppsGroup(ExampleGroup):
LOCAL_BUILD_DIR = "build_test_apps"
BUILD_JOB_NAMES = ["build_test_apps_{}".format(target) for target in SUPPORTED_TARGETS]
class UnitTestGroup(IDFCaseGroup):
SORT_KEYS = ["test environment", "tags", "chip_target"]
CI_JOB_MATCH_KEYS = ["test environment"]
LOCAL_BUILD_DIR = "tools/unit-test-app/builds"
BUILD_JOB_NAMES = ["build_esp_idf_tests_cmake_{}".format(target) for target in SUPPORTED_TARGETS]
MAX_CASE = 50
ATTR_CONVERT_TABLE = {
"execution_time": "execution time"
}
CI_JOB_MATCH_KEYS = ["test environment"]
DUT_CLS_NAME = {
"esp32": "ESP32DUT",
"esp32s2": "ESP32S2DUT",
@ -29,14 +103,14 @@ class Group(CIAssignTest.Group):
}
def __init__(self, case):
super(Group, self).__init__(case)
super(UnitTestGroup, self).__init__(case)
for tag in self._get_case_attr(case, "tags"):
self.ci_job_match_keys.add(tag)
@staticmethod
def _get_case_attr(case, attr):
if attr in Group.ATTR_CONVERT_TABLE:
attr = Group.ATTR_CONVERT_TABLE[attr]
if attr in UnitTestGroup.ATTR_CONVERT_TABLE:
attr = UnitTestGroup.ATTR_CONVERT_TABLE[attr]
return case[attr]
def add_extra_case(self, case):
@ -133,11 +207,25 @@ class Group(CIAssignTest.Group):
return output_data
class UnitTestAssignTest(CIAssignTest.AssignTest):
CI_TEST_JOB_PATTERN = re.compile(r"^UT_.+")
class ExampleAssignTest(IDFAssignTest):
CI_TEST_JOB_PATTERN = re.compile(r'^example_test_.+')
def __init__(self, test_case_path, ci_config_file):
CIAssignTest.AssignTest.__init__(self, test_case_path, ci_config_file, case_group=Group)
def __init__(self, est_case_path, ci_config_file):
super(ExampleAssignTest, self).__init__(est_case_path, ci_config_file, case_group=ExampleGroup)
class TestAppsAssignTest(IDFAssignTest):
CI_TEST_JOB_PATTERN = re.compile(r'^test_app_test_.+')
def __init__(self, est_case_path, ci_config_file):
super(TestAppsAssignTest, self).__init__(est_case_path, ci_config_file, case_group=TestAppsGroup)
class UnitTestAssignTest(IDFAssignTest):
CI_TEST_JOB_PATTERN = re.compile(r'^UT_.+')
def __init__(self, est_case_path, ci_config_file):
super(UnitTestAssignTest, self).__init__(est_case_path, ci_config_file, case_group=UnitTestGroup)
def search_cases(self, case_filter=None):
"""
@ -203,14 +291,27 @@ class UnitTestAssignTest(CIAssignTest.AssignTest):
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("test_case",
help="test case folder or file")
parser.add_argument("ci_config_file",
help="gitlab ci config file")
parser.add_argument("output_path",
help="output path of config files")
parser.add_argument("case_group", choices=["example_test", "custom_test", "unit_test"])
parser.add_argument("test_case", help="test case folder or file")
parser.add_argument("ci_config_file", help="gitlab ci config file")
parser.add_argument("output_path", help="output path of config files")
parser.add_argument("--pipeline_id", "-p", type=int, default=None, help="pipeline_id")
parser.add_argument("--test-case-file-pattern", help="file name pattern used to find Python test case files")
args = parser.parse_args()
assign_test = UnitTestAssignTest(args.test_case, args.ci_config_file)
assign_test.assign_cases()
assign_test.output_configs(args.output_path)
args_list = [args.test_case, args.ci_config_file]
if args.case_group == 'example_test':
assigner = ExampleAssignTest(*args_list)
elif args.case_group == 'custom_test':
assigner = TestAppsAssignTest(*args_list)
elif args.case_group == 'unit_test':
assigner = UnitTestAssignTest(*args_list)
else:
raise SystemExit(1) # which is impossible
if args.test_case_file_pattern:
assigner.CI_TEST_JOB_PATTERN = re.compile(r'{}'.format(args.test_case_file_pattern))
assigner.assign_cases()
assigner.output_configs(args.output_path)
assigner.create_artifact_index_file()

View File

@ -65,8 +65,8 @@ def test_monitor_ide_integration(env, extra_data):
for name in config_names:
Utility.console_log('Checking config "{}"... '.format(name), 'green', end='')
dut = env.get_dut('panic', rel_proj_path, app_config_name=name)
monitor_path = os.path.join(dut.app.get_sdk_path(), 'tools/idf_monitor.py')
elf_path = os.path.join(dut.app.get_binary_path(rel_proj_path), 'panic.elf')
monitor_path = os.path.join(dut.app.idf_path, 'tools/idf_monitor.py')
elf_path = os.path.join(dut.app.binary_path, 'panic.elf')
dut.start_app()
# Closing the DUT because we will reconnect with IDF Monitor
env.close_dut(dut.name)