-
Notifications
You must be signed in to change notification settings - Fork 0
feat: web progress bar #423
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c9b4e69
954c1fe
8f9c751
0e1f871
8c55a30
abb4f08
9d63005
5ecf26d
14ef27c
e684c97
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,31 @@ | ||
| import json | ||
| import logging | ||
| import re | ||
| import sys | ||
| from typing import TYPE_CHECKING, Literal, Self, TextIO | ||
| from enum import StrEnum | ||
| from pathlib import Path | ||
| from typing import Literal, Self, TextIO | ||
|
|
||
| from pythonjsonlogger.json import JsonFormatter | ||
|
|
||
| if TYPE_CHECKING: | ||
| from pathlib import Path | ||
|
|
||
| from imgtests.constant import LIB_DATA_DIR | ||
|
|
||
| LogLevel = Literal["debug", "info", "warning", "error", "critical"] | ||
|
|
||
|
|
||
| class LoggingPatterns(StrEnum): | ||
| TASK_STARTED_STATUS = "RUNNING" | ||
| TASK_STATUS = r"Task id=([\w-]+) path=[\w|\.]+ state=(\w+)" | ||
| TESTS_COUNT = r"Total amount of tests per run: (\d+)" | ||
| RUNS_COUNT = r"Starting test run (\d+) of (\d+)" | ||
| DEFAULT_TEST_START = r"Starting '(.*\.)'.*" | ||
| DEFAULT_TEST_FINISH = r"'(.*\.)' test finished." | ||
| SUITE_START = r"Running suite (.*)\." | ||
| PROFILED_TEST_START = r"\[PLAN\] run stage=([\w-]+) tool=([\w-]+) subsystem=([\w-]+).*" | ||
| PROFILED_TEST_FINISH = r"\[PLAN\] done .*" | ||
| PROFILE_DONE = r"\[PROFILED\] DONE profile=(\w+) pattern=(\w+) .*" | ||
|
|
||
|
|
||
| class StreamFormatter(logging.Formatter): | ||
| def __init__(self: Self) -> None: | ||
| self._level_fmt = "[%(levelname)s]" | ||
|
|
@@ -23,6 +38,99 @@ def format(self: Self, record: logging.LogRecord) -> str: | |
| return super().format(record) | ||
|
|
||
|
|
||
| class ProgressHandler(logging.Handler): | ||
| progress_template = { # noqa: RUF012 | ||
| "total_test_count": 0, | ||
| "test_count": 0, | ||
| "total_run_count": 0, | ||
| "current_test_run": 0, | ||
| "current_suite": "Not started yet", | ||
| "last_profile_done": "Not done yet", | ||
| "current_test": "Not started yet", | ||
| } | ||
|
|
||
| def __init__(self, level: logging._Level = logging.DEBUG): | ||
| super().__init__(level) | ||
| self.progress_data = {} | ||
| self.proc_to_task = {} | ||
|
|
||
| def emit(self, record: logging.LogRecord): # noqa: PLR0915 | ||
| proc = str(record.process) | ||
| msg = self.format(record) | ||
|
|
||
| # detect task started or finished | ||
| match = re.search(LoggingPatterns.TASK_STATUS, msg) | ||
| if match: | ||
| task_id = match.group(1) | ||
| status = match.group(2) | ||
| # task started | ||
| if status == LoggingPatterns.TASK_STARTED_STATUS: | ||
| self.proc_to_task[proc] = task_id | ||
| # task finished or broke | ||
| else: | ||
| self.proc_to_task[proc] = None | ||
| # flush progress_data for process | ||
| self.progress_data[proc] = self.progress_template.copy() | ||
|
|
||
| match = re.search(LoggingPatterns.TESTS_COUNT, msg) | ||
| if match and self.progress_data[proc]: | ||
| total = int(match.group(1)) | ||
| self.progress_data[proc]["total_test_count"] = total | ||
|
|
||
| match = re.search(LoggingPatterns.RUNS_COUNT, msg) | ||
| if match and self.progress_data[proc]: | ||
| # set current run | ||
| cur = int(match.group(1)) | ||
| total = int(match.group(2)) | ||
| self.progress_data[proc]["current_test_run"] = cur | ||
| self.progress_data[proc]["total_run_count"] = total | ||
| # reset tests count | ||
| self.progress_data[proc]["test_count"] = 0 | ||
|
|
||
| # default runner matches | ||
| match = re.search(LoggingPatterns.SUITE_START, msg) | ||
| if match and self.progress_data[proc]: | ||
| suite = match.group(1) | ||
| self.progress_data[proc]["current_suite"] = suite | ||
|
|
||
| match = re.search(LoggingPatterns.DEFAULT_TEST_START, msg) | ||
| if match and self.progress_data[proc]: | ||
| test = match.group(1) | ||
| self.progress_data[proc]["current_test"] = test | ||
|
|
||
| match = re.search(LoggingPatterns.DEFAULT_TEST_FINISH, msg) | ||
| if match and self.progress_data[proc]: | ||
| self.progress_data[proc]["test_count"] += 1 | ||
| self.progress_data[proc]["current_test"] = "Not started yet" | ||
|
|
||
| # profiled runner matches | ||
| match = re.search(LoggingPatterns.PROFILE_DONE, msg) | ||
| if match and self.progress_data[proc]: | ||
| profile = "-".join([match.group(1), match.group(2)]) | ||
| self.progress_data[proc]["last_profile_done"] = profile | ||
|
|
||
| match = re.search(LoggingPatterns.PROFILED_TEST_START, msg) | ||
| if match and self.progress_data[proc]: | ||
| subsystem = match.group(3) | ||
| profile = match.group(1) | ||
| tool = match.group(2) | ||
| self.progress_data[proc]["current_test"] = f"{subsystem}-{profile} via {tool}" | ||
|
|
||
| match = re.search(LoggingPatterns.PROFILED_TEST_FINISH, msg) | ||
| if match and self.progress_data[proc]: | ||
| self.progress_data[proc]["test_count"] += 1 | ||
| self.progress_data[proc]["current_test"] = "Not started yet" | ||
|
|
||
|
Artanias marked this conversation as resolved.
|
||
| if proc in self.proc_to_task and self.proc_to_task[proc] is not None: | ||
| task_id = self.proc_to_task[proc] | ||
| with Path.open( | ||
| LIB_DATA_DIR / (task_id + "_progress.log"), | ||
| "w", | ||
| encoding="utf-8", | ||
| ) as file: | ||
| json.dump(self.progress_data[proc], file, indent=4) | ||
|
|
||
|
|
||
| def set_handlers( | ||
| logger: logging.Logger, | ||
| filename: Path, | ||
|
|
@@ -45,6 +153,7 @@ def set_handlers( | |
| """ | ||
| levelno = getattr(logging, log_level.upper()) | ||
| logger.setLevel(levelno) | ||
| logger.addHandler(__get_progress_handler()) | ||
| logger.addHandler(__get_file_handler(filename)) | ||
| if levelno in [logging.INFO, logging.DEBUG]: | ||
| logger.addHandler(__get_stdout_handler()) | ||
|
|
@@ -82,3 +191,12 @@ def filter(self: Self, record: logging.LogRecord) -> bool: | |
| stdout_handler.setFormatter(StreamFormatter()) | ||
|
|
||
| return stdout_handler | ||
|
|
||
|
|
||
| def __get_progress_handler() -> ProgressHandler: | ||
| progress_handle = ProgressHandler() | ||
| progress_handle.setLevel(logging.DEBUG) | ||
| progress_handle.setFormatter(StreamFormatter()) | ||
| progress_handle.set_name("progress_handler") | ||
|
|
||
| return progress_handle | ||
|
Comment on lines
+196
to
+202
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Лучше бы это всё проворачивать более явно, нежели чем через парсинг логов, т.к кажется, что поменяются логи, а они соответственно меняются, то всё сломается и будет сходу не понятно, что делать, где чинить.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Также в БД попадают уже сейчас результаты тестов, может получится их расширить и оттуда вычислять значения? |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Тут потецниальная "утечка" памяти, т.к. от каждого запуска будет забиваться и не вичищаться после тестов.