vedro-step-tracer 0.1.0a4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vedro_step_tracer-0.1.0a4/PKG-INFO +6 -0
- vedro_step_tracer-0.1.0a4/README.md +92 -0
- vedro_step_tracer-0.1.0a4/pyproject.toml +15 -0
- vedro_step_tracer-0.1.0a4/setup.cfg +4 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/__init__.py +19 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/config.py +99 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/filenames.py +71 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/gitlab.py +124 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/plugin.py +548 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/__init__.py +7 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/base.py +29 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/__init__.py +5 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/assets/__init__.py +52 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/escaping.py +11 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/layout.py +101 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/palette.py +49 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/traceback.py +144 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/values.py +220 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/html/writer.py +223 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/llm/__init__.py +6 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/llm/traceback.py +101 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/llm/writer.py +73 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/secrets.py +20 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/shorten.py +111 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/source.py +74 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/status.py +27 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/teamcity/__init__.py +5 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/teamcity/messages.py +94 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/teamcity/writer.py +303 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/terminal/__init__.py +5 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/terminal/markup.py +225 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/terminal/writer.py +259 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/reporting/unwrap.py +33 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/settings.py +36 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/tracing/__init__.py +10 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/tracing/nodes.py +114 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/tracing/tracer.py +408 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer/tracing/tree.py +252 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer.egg-info/PKG-INFO +6 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer.egg-info/SOURCES.txt +41 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer.egg-info/dependency_links.txt +1 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer.egg-info/requires.txt +1 -0
- vedro_step_tracer-0.1.0a4/vedro_step_tracer.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# VedroStepTracer
|
|
2
|
+
|
|
3
|
+
Плагин для [vedro](https://vedro.io/), который автоматически трейсит вызовы функций во время выполнения тестов и генерирует детальные HTML-отчёты с деревом вызовов, переменными и трейсбеком ошибок.
|
|
4
|
+
|
|
5
|
+
## Для чего нужен
|
|
6
|
+
|
|
7
|
+
При падении e2e-теста сложно понять **что именно произошло** — какие функции вызывались, с какими аргументами, что вернули и где упало. Плагин перехватывает все вызовы из указанных директорий через `sys.settrace()`, строит иерархическое дерево и сохраняет его как интерактивный HTML-файл и/или выводит в stdout.
|
|
8
|
+
|
|
9
|
+
- 🌳 **Видно весь путь до ошибки** — дерево вложенных вызовов `helpers/contexts/interfaces` с аргументами, локальными переменными и возвращаемыми значениями каждого шага, поэтому не нужно расставлять `print`/`breakpoint` и перезапускать тест, чтобы понять, где и с какими данными всё пошло не так
|
|
10
|
+
- 📄 **Отчёт файлом рядом с артефактами сценария** — пишется в `.vedro/artifacts/...` (раскладка `vedro.plugins.artifacted`) при падении сценария, а при `--tr` — и для успешных; в результат сценария добавляется кликабельная ссылка `🔎 Trace report: …`. Блоки с деталями упавшей ветки развёрнуты сразу, остальное свёрнуто, чтобы не тонуть в шуме
|
|
11
|
+
- ✂️ **Длинные значения сокращаются, но не теряются** — в HTML хвост скрыт под `…` и раскрывается по клику (и находится поиском по отчёту); в stdout и дереве тестов IDE обрезка жёсткая, полная версия остаётся в артефакте
|
|
12
|
+
- 🔗 **Прямая ссылка на артефакты job'а GitLab CI** — не нужно вручную искать нужный запуск и скачивать файл, чтобы посмотреть трейс
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## CLI-флаги
|
|
17
|
+
|
|
18
|
+
| Флаг | Тип | По умолчанию | Описание |
|
|
19
|
+
|------|-----|-------------|----------|
|
|
20
|
+
| `--tr [LEVEL]` | `int` (0–3) | `1` (если указан без значения) | Включает интерактивный трейс в stdout. Уровни детализации: |
|
|
21
|
+
| | | | `0` — только return из target_dirs |
|
|
22
|
+
| | | | `1` — return + scope |
|
|
23
|
+
| | | | `2` — scope + args/return всех уровней |
|
|
24
|
+
| | | | `3` — всё (scope + args + locals + return) |
|
|
25
|
+
| `--tr-no-color` | `flag` | `false` | Отключить ANSI-раскраску в выводе трейса |
|
|
26
|
+
| `--tr-gitlab` | `flag` | auto | Добавить маркеры схлопываемых секций GitLab CI (автоопределяется по `CI_JOB_ID`) |
|
|
27
|
+
|
|
28
|
+
### Примеры запуска
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Базовый трейс (return + scope)
|
|
32
|
+
python -m vedro run --tr
|
|
33
|
+
|
|
34
|
+
# Максимальная детализация
|
|
35
|
+
python -m vedro run --tr 3
|
|
36
|
+
|
|
37
|
+
# Без цветов (для логов)
|
|
38
|
+
python -m vedro run --tr --tr-no-color
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Параметры конфигурации
|
|
44
|
+
|
|
45
|
+
| Параметр | Тип | По умолчанию | Описание |
|
|
46
|
+
|----------|-----|-------------|----------|
|
|
47
|
+
| `enabled` | `bool` | `True` | Включить/выключить плагин |
|
|
48
|
+
| `target_dirs` | `list[str]` | `["helpers", "contexts", "interfaces"]` | Директории для трейсинга (относительно workdir) |
|
|
49
|
+
| `exclude_paths` | `list[str]` | `[]` | Glob-паттерны файлов внутри `target_dirs`, вызовы из которых не попадают в дерево |
|
|
50
|
+
| `exclude_functions` | `list[str]` | `[]` | Имена функций, которые не попадают в дерево |
|
|
51
|
+
| `attach_artifacts` | `bool` | `True` | Сохранять отчёт в директорию артефактов сценария |
|
|
52
|
+
| `save_to_dir` | `bool` | `False` | Дополнительно сохранять HTML в `output_dir` |
|
|
53
|
+
| `output_dir` | `str` | `"logs/traces"` | Папка для HTML-отчётов (нужна только при `save_to_dir`) |
|
|
54
|
+
| `print_to_stdout` | `bool` | `False` | Печатать полное дерево в stdout (иначе — только ссылка на отчёт) |
|
|
55
|
+
| `max_str_len` | `int` | `1000` | Макс. длина строкового значения; применяется к каждой физической строке |
|
|
56
|
+
| `max_line_len` | `int` | `1000` | Макс. длина одной физической строки в выводе нестандартного объекта (`__str__`/`repr`/rich) |
|
|
57
|
+
| `collapse_passthrough` | `bool` | `True` | Схлопывать транзитные цепочки вызовов в одну хлебную крошку |
|
|
58
|
+
| `job_id` | `str` | `""` | ID задачи CI (для ссылок на артефакты) |
|
|
59
|
+
| `artifact_url_pattern` | `str` | `""` | Шаблон URL артефакта: `{job_id}`, `{path}` |
|
|
60
|
+
| `workdir_subpath` | `str` | `""` | Подпуть рабочей директории в репозитории; задайте, если тесты запускаются из вложенной директории |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Пример конфигурации
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# vedro.cfg.py
|
|
68
|
+
import os
|
|
69
|
+
import vedro
|
|
70
|
+
|
|
71
|
+
from vedro_step_tracer import VedroStepTracer
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Config(vedro.Config):
|
|
75
|
+
|
|
76
|
+
class Plugins(vedro.Config.Plugins):
|
|
77
|
+
|
|
78
|
+
class StepTracer(VedroStepTracer):
|
|
79
|
+
enabled = True
|
|
80
|
+
target_dirs = ['helpers', 'contexts', 'interfaces']
|
|
81
|
+
exclude_paths = ['interfaces/generated_client.py']
|
|
82
|
+
|
|
83
|
+
# Отчёт уходит файлом в артефакты сценария; stdout остаётся читаемым.
|
|
84
|
+
attach_artifacts = True
|
|
85
|
+
print_to_stdout = False
|
|
86
|
+
|
|
87
|
+
output_dir = 'logs/traces'
|
|
88
|
+
save_to_dir = bool(os.environ.get('CI_JOB_ID'))
|
|
89
|
+
|
|
90
|
+
job_id = os.environ.get('CI_JOB_ID')
|
|
91
|
+
artifact_url_pattern = 'https://gitlab.example.com/group/SomeProject/-/jobs/{job_id}/artifacts/file/{path}'
|
|
92
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "vedro-step-tracer"
|
|
3
|
+
version = "0.1.0a4"
|
|
4
|
+
description = "Vedro plugin that traces function calls in e2e steps and renders call-tree reports"
|
|
5
|
+
requires-python = ">=3.9"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"vedro>=1.10,<2",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["setuptools>=68"]
|
|
12
|
+
build-backend = "setuptools.build_meta"
|
|
13
|
+
|
|
14
|
+
[tool.setuptools.packages.find]
|
|
15
|
+
include = ["vedro_step_tracer*"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Vedro plugin: автоматический трейсинг вызовов функций из helpers/, contexts/, interfaces/.
|
|
3
|
+
|
|
4
|
+
Использует sys.settrace() для перехвата всех вызовов и возвратов из целевых
|
|
5
|
+
директорий, собирает локальные переменные и возвращаемые значения, выводит
|
|
6
|
+
иерархическое дерево по спецификации формата вывода шагов теста.
|
|
7
|
+
|
|
8
|
+
Структура пакета:
|
|
9
|
+
* ``settings`` — изменяемые в рантайме настройки (лимит длины строк);
|
|
10
|
+
* ``tracing`` — модель дерева, sys.settrace-трейсер, пост-обработка;
|
|
11
|
+
* ``reporting`` — рендереры отчётов: ``terminal`` (stdout) и ``html``;
|
|
12
|
+
* ``gitlab`` — специфика GitLab CI: секции лога, ссылки на артефакты;
|
|
13
|
+
* ``plugin`` / ``config`` — интеграция с vedro.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .config import VedroStepTracer
|
|
17
|
+
from .plugin import VedroStepTracerPlugin
|
|
18
|
+
|
|
19
|
+
__all__ = ("VedroStepTracer", "VedroStepTracerPlugin",)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Конфиг плагина (vedro PluginConfig)."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from vedro.core import PluginConfig
|
|
6
|
+
|
|
7
|
+
from .plugin import VedroStepTracerPlugin
|
|
8
|
+
|
|
9
|
+
__all__ = ("VedroStepTracer",)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VedroStepTracer(PluginConfig):
|
|
13
|
+
plugin = VedroStepTracerPlugin
|
|
14
|
+
|
|
15
|
+
# Директории для отслеживания (относительно рабочей папки)
|
|
16
|
+
target_dirs: List[str] = ["helpers", "contexts", "interfaces"]
|
|
17
|
+
|
|
18
|
+
# Файлы внутри target_dirs, вызовы из которых не попадают в дерево
|
|
19
|
+
# (glob-паттерны, например "interfaces/api_client.py").
|
|
20
|
+
# Исключение означает «не создавать узел для вызовов из этого файла»:
|
|
21
|
+
# вложенные целевые вызовы сохраняются и прицепляются к ближайшему
|
|
22
|
+
# не исключённому предку.
|
|
23
|
+
exclude_paths: List[str] = []
|
|
24
|
+
|
|
25
|
+
# Имена функций, которые не попадают в дерево (по тому же правилу).
|
|
26
|
+
exclude_functions: List[str] = []
|
|
27
|
+
|
|
28
|
+
# Куда сохранять логи (используется только при save_to_dir = True)
|
|
29
|
+
output_dir: str = "logs/traces"
|
|
30
|
+
|
|
31
|
+
# Сохранять отчёт в директорию артефактов сценария (.vedro/artifacts,
|
|
32
|
+
# раскладка plugins.artifacted) — рядом с prompt-файлами и дампами.
|
|
33
|
+
# Файл пишется напрямую, артефактом vedro не прикрепляется: в результате
|
|
34
|
+
# сценария остаётся одна строка со ссылкой на отчёт.
|
|
35
|
+
attach_artifacts: bool = True
|
|
36
|
+
|
|
37
|
+
# Дополнительно сохранять HTML-отчёт в output_dir. Прежнее поведение;
|
|
38
|
+
# нужно там, где артефакты vedro не собираются (например, часть job'ов CI).
|
|
39
|
+
save_to_dir: bool = False
|
|
40
|
+
|
|
41
|
+
# Печатать полное дерево трейса в stdout. По умолчанию выключено: отчёт
|
|
42
|
+
# уходит в артефакт, а в stdout остаётся только ссылка на него. Полное
|
|
43
|
+
# дерево печатается при явном --tr.
|
|
44
|
+
print_to_stdout: bool = False
|
|
45
|
+
|
|
46
|
+
# Максимальная длина строкового значения перед сокращением.
|
|
47
|
+
# Применяется к каждой физической строке отдельно.
|
|
48
|
+
max_str_len: int = 1000
|
|
49
|
+
|
|
50
|
+
# Максимальная длина одной физической строки в выводе нестандартного
|
|
51
|
+
# объекта (рендер через __str__/repr/rich, например HTTP-трейс).
|
|
52
|
+
max_line_len: int = 1000
|
|
53
|
+
|
|
54
|
+
# Схлопывать транзитные цепочки вызовов: обёртка без собственных
|
|
55
|
+
# переменных, с одним вложенным вызовом и тем же возвращаемым объектом
|
|
56
|
+
# печаталась отдельным уровнем с тем же значением. Схлопывание без
|
|
57
|
+
# потерь — имена звеньев остаются в хлебной крошке
|
|
58
|
+
# "SomeProject ▸ load_data → DataClient ▸ fetch".
|
|
59
|
+
collapse_passthrough: bool = True
|
|
60
|
+
|
|
61
|
+
# ID job'а GitLab CI (например, job_id = os.environ.get('CI_JOB_ID')).
|
|
62
|
+
# По умолчанию не заполнен (локальный запуск). Его наличие используется
|
|
63
|
+
# для автоопределения запуска в GitLab CI, если флаг --tr-gitlab не
|
|
64
|
+
# передан явно.
|
|
65
|
+
job_id: str = ""
|
|
66
|
+
|
|
67
|
+
# Паттерн ссылки на артефакт GitLab CI с плейсхолдерами {job_id} и {path},
|
|
68
|
+
# например:
|
|
69
|
+
# "https://gitlab.example.com/group/SomeProject/-/jobs/{job_id}/artifacts/file/{path}"
|
|
70
|
+
# Используется для ссылки на Trace report только если job_id заполнен
|
|
71
|
+
# (запуск в GitLab CI); при локальном запуске ссылка формируется через локальный file:// путь.
|
|
72
|
+
artifact_url_pattern: str = ""
|
|
73
|
+
|
|
74
|
+
# Подпуть рабочей директории относительно корня репозитория,
|
|
75
|
+
# используемый при формировании хостовых и артефактных ссылок.
|
|
76
|
+
# Пустая строка означает, что рабочая директория — корень репозитория.
|
|
77
|
+
workdir_subpath: str = ""
|
|
78
|
+
|
|
79
|
+
# Удалять ранее сохранённые HTML-отчёты из output_dir перед прогоном.
|
|
80
|
+
# Иначе директория копится от запуска к запуску, и среди старых файлов
|
|
81
|
+
# трудно найти отчёт текущего прогона. Удаляются только *.html в самой
|
|
82
|
+
# output_dir, без рекурсии (см. filenames.cleanup_reports).
|
|
83
|
+
cleanup_before: bool = True
|
|
84
|
+
|
|
85
|
+
# Выводить дерево шагов service-сообщениями TeamCity — тогда шаги и
|
|
86
|
+
# вложенные вызовы видны деревом в тест-раннере PyCharm (встроенный
|
|
87
|
+
# репортёр vedro сообщает IDE только о сценариях).
|
|
88
|
+
# None — определять автоматически: выбран репортёр `-r pycharm` либо
|
|
89
|
+
# прогон идёт под IDE/TeamCity (см. teamcity_env_vars).
|
|
90
|
+
teamcity: Optional[bool] = None
|
|
91
|
+
|
|
92
|
+
# Переменные окружения, наличие которых означает запуск под
|
|
93
|
+
# PyCharm/TeamCity (используются при teamcity = None).
|
|
94
|
+
teamcity_env_vars: List[str] = ["PYCHARM_HOSTED", "TEAMCITY_VERSION", "PYCHARM_HELPERS_DIR"]
|
|
95
|
+
|
|
96
|
+
# Рисовать глифы дерева ("║ ┃ │ ├╴ ") перед строками в режиме TeamCity.
|
|
97
|
+
# False — печатать только заголовки и содержимое: внутри развёрнутого
|
|
98
|
+
# узла вложенность видна и без них, а отступы съедают ширину под значения.
|
|
99
|
+
teamcity_tree_prefix: bool = False
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Имена файлов отчётов: санитизация и очистка старых отчётов.
|
|
2
|
+
|
|
3
|
+
Имя отчёта строится из subject сценария, а там встречается что угодно —
|
|
4
|
+
двоеточия, слэши, кавычки. Такие символы ломают путь (`/`), несовместимы с
|
|
5
|
+
Windows/сетевыми ФС (`:`, `?`, `*`, `"`, `<`, `>`, `|`) и требуют
|
|
6
|
+
экранирования в ссылках, которые печатаются в терминал и в GitLab CI.
|
|
7
|
+
Поэтому имя всегда приводится к безопасному виду.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
from typing import List
|
|
13
|
+
|
|
14
|
+
__all__ = ("sanitize_filename", "cleanup_reports",)
|
|
15
|
+
|
|
16
|
+
# Символы, недопустимые в именах файлов хотя бы на одной из целевых ФС,
|
|
17
|
+
# плюс управляющие символы.
|
|
18
|
+
_UNSAFE_RE = re.compile(r'[<>:"/\\|?*\x00-\x1f]+')
|
|
19
|
+
# Пробельные последовательности схлопываются в одно подчёркивание: имена
|
|
20
|
+
# отчётов часто попадают в ссылки, где пробелы приходится экранировать.
|
|
21
|
+
_SPACE_RE = re.compile(r"\s+")
|
|
22
|
+
_UNDERSCORE_RE = re.compile(r"_{2,}")
|
|
23
|
+
|
|
24
|
+
_FALLBACK_NAME = "scenario"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def sanitize_filename(name: str, max_length: int = 80) -> str:
|
|
28
|
+
"""
|
|
29
|
+
Привести произвольную строку к безопасному имени файла (без расширения).
|
|
30
|
+
|
|
31
|
+
Небезопасные символы и пробелы заменяются подчёркиванием, повторы
|
|
32
|
+
схлопываются, точки и подчёркивания по краям убираются (имя, начинающееся
|
|
33
|
+
с точки, скрыто в файловых менеджерах). Пустой результат заменяется на
|
|
34
|
+
``scenario``, чтобы отчёт не превратился в файл без имени.
|
|
35
|
+
"""
|
|
36
|
+
cleaned = _UNSAFE_RE.sub("_", name)
|
|
37
|
+
cleaned = _SPACE_RE.sub("_", cleaned)
|
|
38
|
+
cleaned = _UNDERSCORE_RE.sub("_", cleaned)
|
|
39
|
+
cleaned = cleaned.strip("._ ")
|
|
40
|
+
if max_length > 0:
|
|
41
|
+
cleaned = cleaned[:max_length].strip("._ ")
|
|
42
|
+
return cleaned or _FALLBACK_NAME
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def cleanup_reports(output_dir: str, suffix: str = ".html") -> List[str]:
|
|
46
|
+
"""
|
|
47
|
+
Удалить ранее сохранённые отчёты из директории вывода.
|
|
48
|
+
|
|
49
|
+
Удаляются только файлы с нужным расширением и только в самой директории
|
|
50
|
+
(без рекурсии): она может быть смонтирована рядом с другими артефактами,
|
|
51
|
+
и сносить её целиком небезопасно. Возвращает список удалённых путей;
|
|
52
|
+
ошибки доступа игнорируются — очистка не должна ронять прогон тестов.
|
|
53
|
+
"""
|
|
54
|
+
removed: List[str] = []
|
|
55
|
+
try:
|
|
56
|
+
entries = os.listdir(output_dir)
|
|
57
|
+
except OSError:
|
|
58
|
+
return removed
|
|
59
|
+
|
|
60
|
+
for entry in entries:
|
|
61
|
+
if not entry.endswith(suffix):
|
|
62
|
+
continue
|
|
63
|
+
path = os.path.join(output_dir, entry)
|
|
64
|
+
if not os.path.isfile(path):
|
|
65
|
+
continue
|
|
66
|
+
try:
|
|
67
|
+
os.remove(path)
|
|
68
|
+
except OSError:
|
|
69
|
+
continue
|
|
70
|
+
removed.append(path)
|
|
71
|
+
return removed
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""GitLab CI-специфика: схлопываемые секции лога и ссылки на артефакты."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from urllib.parse import quote
|
|
7
|
+
|
|
8
|
+
__all__ = (
|
|
9
|
+
"SectionIdGenerator", "section_start", "section_end",
|
|
10
|
+
"to_host_path", "to_relative_path", "to_artifact_url", "make_terminal_link",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SectionIdGenerator:
|
|
15
|
+
"""Выдаёт уникальные идентификаторы секций в пределах одного отчёта."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, prefix: str = "tr") -> None:
|
|
18
|
+
self._prefix = prefix
|
|
19
|
+
self._counter = 0
|
|
20
|
+
|
|
21
|
+
def next_id(self) -> str:
|
|
22
|
+
self._counter += 1
|
|
23
|
+
return f"{self._prefix}{self._counter}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def section_start(section_id: str, collapsed: bool = True) -> str:
|
|
27
|
+
"""Маркер начала схлопываемой секции GitLab CI (пишется перед заголовком)."""
|
|
28
|
+
ts = int(time.time())
|
|
29
|
+
opts = "[collapsed=true]" if collapsed else ""
|
|
30
|
+
return f"\033[0Ksection_start:{ts}:{section_id}{opts}\r\033[0K"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def section_end(section_id: str) -> str:
|
|
34
|
+
"""Маркер конца схлопываемой секции GitLab CI."""
|
|
35
|
+
ts = int(time.time())
|
|
36
|
+
return f"\033[0Ksection_end:{ts}:{section_id}\r\033[0K\n"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def to_host_path(filepath: str, project_root: str, workdir_subpath: str) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Преобразовать путь в рабочей директории в путь на хостовой машине.
|
|
42
|
+
|
|
43
|
+
Если задан `HOST_PROJECT_ROOT_DIRECTORY`, к корню проекта на хосте
|
|
44
|
+
добавляются подпуть рабочей директории и относительный путь к файлу.
|
|
45
|
+
"""
|
|
46
|
+
host_root = os.environ.get("HOST_PROJECT_ROOT_DIRECTORY")
|
|
47
|
+
if not host_root:
|
|
48
|
+
return filepath
|
|
49
|
+
try:
|
|
50
|
+
rel_path = os.path.relpath(filepath, project_root)
|
|
51
|
+
except ValueError:
|
|
52
|
+
return filepath
|
|
53
|
+
if rel_path.startswith(".."):
|
|
54
|
+
return filepath
|
|
55
|
+
return os.path.join(host_root, workdir_subpath, rel_path)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def to_relative_path(filepath: str, project_root: str) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Преобразовать путь внутри контейнера в путь относительно рабочей
|
|
61
|
+
директории (workdir) тестов — `project_root`.
|
|
62
|
+
|
|
63
|
+
Используется для блока с кодом из traceback: там удобнее видеть короткий
|
|
64
|
+
путь относительно workdir, а не абсолютный путь на хосте. Файлы вне
|
|
65
|
+
workdir (например, сторонние библиотеки) остаются нетронутыми
|
|
66
|
+
(абсолютный путь как есть).
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
rel_path = os.path.relpath(filepath, project_root)
|
|
70
|
+
except ValueError:
|
|
71
|
+
return filepath
|
|
72
|
+
if rel_path.startswith(".."):
|
|
73
|
+
return filepath
|
|
74
|
+
return rel_path
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def to_artifact_url(filepath: str, project_root: str, job_id: str,
|
|
78
|
+
url_pattern: str, workdir_subpath: str) -> Optional[str]:
|
|
79
|
+
"""
|
|
80
|
+
Построить ссылку на прикреплённый артефакт GitLab CI (job_id известен —
|
|
81
|
+
сборка запущена именно в GitLab CI).
|
|
82
|
+
|
|
83
|
+
Паттерн задаётся переменной окружения PROJECT_ARTIFACT_PATTERN (см.
|
|
84
|
+
VedroStepTracer.artifact_url_pattern) с плейсхолдерами {job_id} и {path},
|
|
85
|
+
например:
|
|
86
|
+
"https://gitlab.example.com/group/SomeProject/-/jobs/{job_id}/artifacts/file/{path}"
|
|
87
|
+
|
|
88
|
+
Если job_id или паттерн не заданы (локальный запуск) — возвращает None,
|
|
89
|
+
и вызывающий код должен использовать локальную file:// ссылку.
|
|
90
|
+
"""
|
|
91
|
+
if not job_id or not url_pattern:
|
|
92
|
+
return None
|
|
93
|
+
try:
|
|
94
|
+
rel_path = os.path.relpath(filepath, project_root)
|
|
95
|
+
except ValueError:
|
|
96
|
+
return None
|
|
97
|
+
if rel_path.startswith(".."):
|
|
98
|
+
return None
|
|
99
|
+
artifact_path = os.path.join(workdir_subpath, rel_path)
|
|
100
|
+
try:
|
|
101
|
+
return url_pattern.format(job_id=job_id, path=artifact_path)
|
|
102
|
+
except (KeyError, IndexError):
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def make_terminal_link(path: str) -> str:
|
|
107
|
+
"""
|
|
108
|
+
Вернуть путь как file:// URI, чтобы терминал сам распознал его как
|
|
109
|
+
кликабельную ссылку на файл.
|
|
110
|
+
|
|
111
|
+
Изначально использовалась OSC 8 hyperlink escape-последовательность
|
|
112
|
+
(ESC]8;;URI ESC\\ TEXT ESC]8;;ESC\\), но репортёр vedro выводит
|
|
113
|
+
extra_details через rich/logging-конвейер, который не пропускает
|
|
114
|
+
"сырые" управляющие последовательности — в терминале виден только
|
|
115
|
+
обычный текст без ссылки.
|
|
116
|
+
|
|
117
|
+
Более надёжный кроссплатформенный вариант — просто вывести сам file://
|
|
118
|
+
URI: большинство терминалов (Terminal.app и iTerm2 на macOS; GNOME
|
|
119
|
+
Terminal, Konsole, kitty, VS Code на Linux) автоматически подсвечивают
|
|
120
|
+
и делают кликабельными строки, распознанные как URL, без необходимости
|
|
121
|
+
в OSC 8.
|
|
122
|
+
"""
|
|
123
|
+
abs_path = os.path.abspath(path)
|
|
124
|
+
return "file://" + quote(abs_path, safe="/:")
|