diff --git a/.gitlab-ci/lava/lava_job_submitter.py b/.gitlab-ci/lava/lava_job_submitter.py index 9eba34c8820..3f5a7f3a816 100755 --- a/.gitlab-ci/lava/lava_job_submitter.py +++ b/.gitlab-ci/lava/lava_job_submitter.py @@ -46,6 +46,7 @@ from lava.exceptions import ( MesaCIRetryError, MesaCITimeoutError, ) +from lava.utils.lava_log import GitlabSection from lavacli.utils import loader # Timeout in seconds to decide if the device from the dispatched LAVA job has @@ -140,7 +141,10 @@ def generate_lava_yaml(args): # skeleton test definition: only declaring each job as a single 'test' # since LAVA's test parsing is not useful to us - run_steps = [] + setup_section = GitlabSection( + id="lava_setup", header="LAVA setup log", start_collapsed=True + ) + run_steps = [f"printf '{setup_section.start()}'"] test = { 'timeout': { 'minutes': args.job_timeout }, 'failure_retry': 1, @@ -191,6 +195,8 @@ def generate_lava_yaml(args): 'mkdir -p {}'.format(args.ci_project_dir), 'wget -S --progress=dot:giga -O- {} | tar -xz -C {}'.format(args.build_url, args.ci_project_dir), 'wget -S --progress=dot:giga -O- {} | tar -xz -C /'.format(args.job_rootfs_overlay_url), + f"printf '{setup_section.end()}'", + # Putting CI_JOB name as the testcase name, it may help LAVA farm # maintainers with monitoring f"lava-test-case 'mesa-ci_{args.mesa_job_name}' --shell /init-stage2.sh", diff --git a/.gitlab-ci/lava/utils/lava_log.py b/.gitlab-ci/lava/utils/lava_log.py new file mode 100644 index 00000000000..655ca4def19 --- /dev/null +++ b/.gitlab-ci/lava/utils/lava_log.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 Collabora Limited +# Author: Guilherme Gallo +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +Some utilities to analyse logs, create gitlab sections and other quality of life +improvements +""" + +from dataclasses import dataclass +from datetime import datetime + +# Helper constants to colorize the job output +CONSOLE_LOG = { + "COLOR_GREEN": "\x1b[1;32;5;197m", + "COLOR_RED": "\x1b[1;38;5;197m", + "RESET": "\x1b[0m", + "UNDERLINED": "\x1b[3m", + "BOLD": "\x1b[1m", + "DIM": "\x1b[2m", +} + + +@dataclass +class GitlabSection: + id: str + header: str + start_collapsed: bool = False + escape: str = "\x1b[0K" + colour: str = f"{CONSOLE_LOG['BOLD']}{CONSOLE_LOG['COLOR_GREEN']}" + + def get_timestamp(self) -> str: + unix_ts = datetime.timestamp(datetime.now()) + return str(int(unix_ts)) + + def section(self, marker: str, header: str) -> str: + preamble = f"{self.escape}section_{marker}" + collapse = marker == "start" and self.start_collapsed + collapsed = "[collapsed=true]" if collapse else "" + section_id = f"{self.id}{collapsed}" + + timestamp = self.get_timestamp() + before_header = ":".join([preamble, timestamp, section_id]) + colored_header = ( + f"{self.colour}{header}{CONSOLE_LOG['RESET']}" if header else "" + ) + header_wrapper = "\r" + f"{self.escape}{colored_header}" + + return f"{before_header}{header_wrapper}" + + def __enter__(self): + print(self.start()) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + print(self.end()) + + def start(self) -> str: + return self.section(marker="start", header=self.header) + + def end(self) -> str: + return self.section(marker="end", header="") diff --git a/.gitlab-ci/tests/utils/__init__.py b/.gitlab-ci/tests/utils/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.gitlab-ci/tests/utils/test_lava_log.py b/.gitlab-ci/tests/utils/test_lava_log.py new file mode 100644 index 00000000000..f81c49f5d5d --- /dev/null +++ b/.gitlab-ci/tests/utils/test_lava_log.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 Collabora Limited +# Author: Guilherme Gallo +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import pytest +from lava.utils.lava_log import GitlabSection + +GITLAB_SECTION_SCENARIOS = { + "start collapsed": ( + "start", + True, + f"\x1b[0Ksection_start:mock_date:my_first_section[collapsed=true]\r\x1b[0K{GitlabSection.colour}my_header\x1b[0m", + ), + "start non_collapsed": ( + "start", + False, + f"\x1b[0Ksection_start:mock_date:my_first_section\r\x1b[0K{GitlabSection.colour}my_header\x1b[0m", + ), + "end collapsed": ( + "end", + True, + "\x1b[0Ksection_end:mock_date:my_first_section\r\x1b[0K", + ), + "end non_collapsed": ( + "end", + False, + "\x1b[0Ksection_end:mock_date:my_first_section\r\x1b[0K", + ), +} + +@pytest.mark.parametrize( + "method, collapsed, expectation", + GITLAB_SECTION_SCENARIOS.values(), + ids=GITLAB_SECTION_SCENARIOS.keys(), +) +def test_gitlab_section(method, collapsed, expectation): + gs = GitlabSection(id="my_first_section", header="my_header", start_collapsed=collapsed) + gs.get_timestamp = lambda: "mock_date" + result = getattr(gs, method)() + assert result == expectation