CI demo: metrics + Gitea Actions
This commit is contained in:
43
.gitea/workflows/ci.yml
Normal file
43
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: pip
|
||||
- run: pip install -r requirements.txt
|
||||
- run: ruff check .
|
||||
|
||||
test:
|
||||
needs: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: pip
|
||||
- run: pip install -r requirements.txt
|
||||
- run: pytest -q --cov=app --cov-branch --cov-fail-under=60
|
||||
|
||||
metrics:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: pip
|
||||
- run: pip install -r requirements.txt
|
||||
- run: radon cc app -s -a
|
||||
- run: xenon --max-absolute C app/
|
||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
coverage.xml
|
||||
.venv/
|
||||
83
README.md
Normal file
83
README.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# ci-demo (Gitea) — учебный стенд CI к лекции 9
|
||||
|
||||
Мини-проект к лекции «Метрики сложности и CI» (неделя 9, `../09l_л_Метрики_сложности.md`).
|
||||
Домен — «калькулятор скидок», чтобы студенты узнавали код из практики.
|
||||
|
||||
[](https://gitea.example.com/OWNER/ci-demo/actions)
|
||||
|
||||
> Замените `gitea.example.com` и `OWNER` на ваш сервер и логин.
|
||||
|
||||
## Что внутри
|
||||
|
||||
```
|
||||
app/
|
||||
discounts.py # стратегии скидок: regular / vip / employee
|
||||
service.py # OrderService — контекст, делегирует скидке
|
||||
legacy_report.py # функция ранга C — для метрического гейта
|
||||
tests/
|
||||
test_discounts.py # 5 тестов, включая проверку версии Python для матрицы
|
||||
test_legacy_report.py
|
||||
.gitea/workflows/
|
||||
ci.yml # lint → test (coverage) → metrics (radon + xenon)
|
||||
workflows_заготовки/
|
||||
ci-metrics-strict.yml # тот же пайплайн, но xenon --max-absolute B (красный)
|
||||
ci-matrix.yml # матрица 3.11/3.12/3.13, fail-fast: false
|
||||
ci-artifacts.yml # артефакт coverage.xml
|
||||
bad.yml # «плохой» workflow с || true
|
||||
```
|
||||
|
||||
## Локальный запуск
|
||||
|
||||
```powershell
|
||||
python -m venv .venv
|
||||
.venv\Scripts\Activate.ps1
|
||||
pip install -r requirements.txt
|
||||
pytest -q --cov=app --cov-branch --cov-fail-under=60
|
||||
ruff check .
|
||||
radon cc app -s -a
|
||||
xenon --max-absolute C app/ # зелёный: legacy_report.py ранга C
|
||||
xenon --max-absolute B app/ # красный: гейт не пропускает C
|
||||
```
|
||||
|
||||
## Требования к серверу Gitea
|
||||
|
||||
Gitea 1.19+ с включёнными Actions (`app.ini`: `[actions] ENABLED = true`); act_runner онлайн с меткой `ubuntu-latest`.
|
||||
|
||||
## Как развернуть стенд
|
||||
|
||||
1. Создайте пустой репозиторий `ci-demo` на сервере Gitea.
|
||||
2. Содержимое этой папки — корень репозитория:
|
||||
|
||||
```powershell
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Стартовый каркас демо"
|
||||
git branch -M main
|
||||
git remote add origin https://gitea.example.com/OWNER/ci-demo.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
3. Замените `gitea.example.com` и `OWNER` в бейдже README.
|
||||
4. Дождитесь первого прогона CI (act_runner прогреет кеш pip).
|
||||
5. Настройте защиту ветки `main`: обязательные проверки `lint`, `test`,
|
||||
`metrics` (Status Check Contexts), запрет force-push.
|
||||
|
||||
## Отличия Gitea-варианта
|
||||
|
||||
- workflow лежит в `.gitea/workflows/ci.yml`;
|
||||
- прогоны выполняет act_runner;
|
||||
- кеш и артефакты зависят от версии Gitea: заготовки это учитывают.
|
||||
|
||||
## Метрический гейт
|
||||
|
||||
`app/legacy_report.py` намеренно содержит функцию ранга C (V(G) = 17).
|
||||
Основной workflow использует `xenon --max-absolute C` и остаётся зелёным.
|
||||
Строгая заготовка `ci-metrics-strict.yml` поднимает планку до B — сборка
|
||||
красная. Это и есть демонстрация «метрика работает, когда её нельзя
|
||||
игнорировать»: порог в CI делает рост сложности блокирующим.
|
||||
|
||||
## Важно
|
||||
|
||||
Стенд учебный: секреты, деплой и реальные данные здесь не используются.
|
||||
«Плохой» workflow из `workflows_заготовки/bad.yml` запускать только в этом
|
||||
демо-репозитории.
|
||||
1
app/__init__.py
Normal file
1
app/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Пакет приложения ci-demo (Gitea edition)."""
|
||||
35
app/discounts.py
Normal file
35
app/discounts.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""Скидки демо-проекта ci-demo (СТРПО, неделя 9, Gitea edition)."""
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class Discount(ABC):
|
||||
@abstractmethod
|
||||
def apply(self, total: float) -> float:
|
||||
"""Вернуть сумму с учётом скидки."""
|
||||
|
||||
|
||||
class NoDiscount(Discount):
|
||||
def apply(self, total: float) -> float:
|
||||
return total
|
||||
|
||||
|
||||
class VIPDiscount(Discount):
|
||||
def apply(self, total: float) -> float:
|
||||
return total * 0.9
|
||||
|
||||
|
||||
class EmployeeDiscount(Discount):
|
||||
def apply(self, total: float) -> float:
|
||||
return total * 0.7
|
||||
|
||||
|
||||
DISCOUNTS: dict[str, type[Discount]] = {
|
||||
"regular": NoDiscount,
|
||||
"vip": VIPDiscount,
|
||||
"employee": EmployeeDiscount,
|
||||
}
|
||||
|
||||
|
||||
def discount_for(customer_type: str) -> Discount:
|
||||
"""Выбрать стратегию скидки по типу клиента."""
|
||||
return DISCOUNTS.get(customer_type, NoDiscount)()
|
||||
41
app/legacy_report.py
Normal file
41
app/legacy_report.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""Учебный модуль с высокой сложностью для метрического гейта CI.
|
||||
|
||||
V(G) функции classify_order — ранг C (11–20). Поэтому:
|
||||
|
||||
xenon --max-absolute C app/ # зелёный
|
||||
xenon --max-absolute B app/ # красный — гейт не пропускает
|
||||
|
||||
Мораль: порог в CI делает рост сложности видимым и блокирующим.
|
||||
"""
|
||||
|
||||
|
||||
def classify_order(order, cfg):
|
||||
"""Классифицировать заказ по десятку правил — намеренно сложно."""
|
||||
if order is None:
|
||||
return "missing"
|
||||
if "total" not in order:
|
||||
return "no_total"
|
||||
total = order["total"]
|
||||
if total < 0:
|
||||
return "negative"
|
||||
if total == 0:
|
||||
return "empty"
|
||||
if total > 100000:
|
||||
return "huge"
|
||||
if cfg.get("maintenance"):
|
||||
return "maintenance"
|
||||
if cfg.get("blocked"):
|
||||
return "blocked"
|
||||
if cfg.get("regions") and order.get("region") not in cfg["regions"]:
|
||||
return "region"
|
||||
if order.get("coupon") and not cfg.get("coupons", False):
|
||||
return "coupon"
|
||||
if total > cfg.get("limit", 10000):
|
||||
return "limit"
|
||||
if order.get("fraud") and order.get("manual_review"):
|
||||
return "fraud_review"
|
||||
elif order.get("fraud"):
|
||||
return "fraud"
|
||||
if order.get("vip") and total > 5000:
|
||||
return "vip_review"
|
||||
return "ok"
|
||||
21
app/service.py
Normal file
21
app/service.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""Сервис заказов демо-проекта ci-demo (СТРПО, неделя 9, Gitea edition)."""
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .discounts import Discount
|
||||
|
||||
|
||||
@dataclass
|
||||
class Order:
|
||||
items: dict[str, float]
|
||||
|
||||
@property
|
||||
def total(self) -> float:
|
||||
return sum(self.items.values())
|
||||
|
||||
|
||||
class OrderService:
|
||||
def __init__(self, discount: Discount) -> None:
|
||||
self.discount = discount
|
||||
|
||||
def total(self, order: Order) -> float:
|
||||
return round(self.discount.apply(order.total), 2)
|
||||
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = ["."]
|
||||
testpaths = ["tests"]
|
||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
pytest>=8.0,<9
|
||||
pytest-cov>=5.0,<7
|
||||
ruff>=0.6,<1
|
||||
radon>=6.0,<7
|
||||
xenon>=0.9,<1
|
||||
36
tests/test_discounts.py
Normal file
36
tests/test_discounts.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from app.discounts import NoDiscount, discount_for
|
||||
from app.service import Order, OrderService
|
||||
|
||||
|
||||
def make_order() -> Order:
|
||||
return Order({"чай": 300, "кофе": 700})
|
||||
|
||||
|
||||
def test_vip_discount() -> None:
|
||||
service = OrderService(discount_for("vip"))
|
||||
assert service.total(make_order()) == pytest.approx(900.0)
|
||||
|
||||
|
||||
def test_employee_discount() -> None:
|
||||
service = OrderService(discount_for("employee"))
|
||||
assert service.total(make_order()) == pytest.approx(700.0)
|
||||
|
||||
|
||||
def test_regular_has_no_discount() -> None:
|
||||
service = OrderService(discount_for("regular"))
|
||||
assert service.total(make_order()) == pytest.approx(1000.0)
|
||||
|
||||
|
||||
def test_unknown_type_falls_back_to_regular() -> None:
|
||||
assert isinstance(discount_for("unknown"), NoDiscount)
|
||||
|
||||
|
||||
def test_python_version_supported() -> None:
|
||||
# Для акта с матрицей: на Python 3.11 этот тест падает
|
||||
assert sys.version_info >= (3, 12), (
|
||||
f"нужен Python 3.12+, получено {sys.version.split()[0]}"
|
||||
)
|
||||
25
tests/test_legacy_report.py
Normal file
25
tests/test_legacy_report.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import pytest
|
||||
|
||||
from app.legacy_report import classify_order
|
||||
|
||||
CASES = [
|
||||
(None, {}, "missing"),
|
||||
({}, {}, "no_total"),
|
||||
({"total": -1}, {}, "negative"),
|
||||
({"total": 0}, {}, "empty"),
|
||||
({"total": 200000}, {}, "huge"),
|
||||
({"total": 100}, {"maintenance": True}, "maintenance"),
|
||||
({"total": 100}, {"blocked": True}, "blocked"),
|
||||
({"total": 100, "region": "eu"}, {"regions": ["us"]}, "region"),
|
||||
({"total": 100, "coupon": "X"}, {"coupons": False}, "coupon"),
|
||||
({"total": 20000}, {"limit": 10000}, "limit"),
|
||||
({"total": 100, "fraud": True, "manual_review": True}, {}, "fraud_review"),
|
||||
({"total": 100, "fraud": True}, {}, "fraud"),
|
||||
({"total": 6000, "vip": True}, {}, "vip_review"),
|
||||
({"total": 100}, {}, "ok"),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("order,cfg,expected", CASES)
|
||||
def test_classify_order(order, cfg, expected):
|
||||
assert classify_order(order, cfg) == expected
|
||||
15
workflows_заготовки/bad.yml
Normal file
15
workflows_заготовки/bad.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
# ВНИМАНИЕ: демонстрационно плохой workflow — только для учебного репозитория!
|
||||
# Используется в бонусном акте: показать «декоративный» CI (|| true).
|
||||
name: mega
|
||||
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
everything:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: pip install -r requirements.txt
|
||||
- run: ruff check . || true
|
||||
- run: pytest -q || true
|
||||
- run: echo "deploy --token=ghp_xxxxxxxxxxxxxxxxxxxx"
|
||||
26
workflows_заготовки/ci-artifacts.yml
Normal file
26
workflows_заготовки/ci-artifacts.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
# Заготовка для акта «Артефакты» (Gitea Actions): скопировать поверх .gitea/workflows/ci.yml
|
||||
# upload-artifact@v3 — максимально совместим с Gitea Actions;
|
||||
# на новых версиях Gitea можно заменить на @v4.
|
||||
name: CI (artifacts)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: pip
|
||||
- run: pip install -r requirements.txt
|
||||
- run: pytest -q --cov=app --cov-branch --cov-fail-under=60 --cov-report=xml
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: coverage-report
|
||||
path: coverage.xml
|
||||
23
workflows_заготовки/ci-matrix.yml
Normal file
23
workflows_заготовки/ci-matrix.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
# Заготовка для акта «Матрица» (Gitea Actions): скопировать поверх .gitea/workflows/ci.yml
|
||||
name: CI (matrix)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11", "3.12", "3.13"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: pip
|
||||
- run: pip install -r requirements.txt
|
||||
- run: pytest -q
|
||||
43
workflows_заготовки/ci-metrics-strict.yml
Normal file
43
workflows_заготовки/ci-metrics-strict.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
name: CI (strict metrics)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: pip
|
||||
- run: pip install -r requirements.txt
|
||||
- run: ruff check .
|
||||
|
||||
test:
|
||||
needs: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: pip
|
||||
- run: pip install -r requirements.txt
|
||||
- run: pytest -q --cov=app --cov-branch --cov-fail-under=60
|
||||
|
||||
metrics:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: pip
|
||||
- run: pip install -r requirements.txt
|
||||
- run: radon cc app -s -a
|
||||
- run: xenon --max-absolute B app/
|
||||
158
Сценарий_демонстрации.md
Normal file
158
Сценарий_демонстрации.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Сценарий демонстрации CI на лекции (Gitea Actions)
|
||||
|
||||
**К дисциплине**: «Современные технологии разработки программного обеспечения» (СТРПО), неделя 9
|
||||
**Основа**: лекция `../09l_л_Метрики_сложности.md`, блок CI (слайды 18–24)
|
||||
**Стенд**: эта папка `Демо_CI_Gitea`; GitHub-версия — `../Демо_CI` (мини-проект «калькулятор скидок»)
|
||||
**Длительность**: 10–12 минут; бонусные акты — ещё 4 минуты
|
||||
**Формат**: живой показ на проекторе; реплики лектора — в блоках «Что говорить»
|
||||
|
||||
## Карта актов ↔ слайды лекции
|
||||
|
||||
| Акт | Что показываем | Слайд |
|
||||
|---|---|---|
|
||||
| 0 | Подготовка стенда (до лекции) | — |
|
||||
| 1 | Зелёный локальный прогон, устройство пайплайна | «GitHub Actions: минимальный workflow» |
|
||||
| 2 | Красный PR, лог падения, merge заблокирован | «Падай рано: правило 10 минут» |
|
||||
| 3 | Лечение теста, зелёный прогон, merge, бейдж | «GitHub Actions: минимальный workflow» |
|
||||
| 4 | Метрический гейт: `radon` + `xenon` | «Метрики в CI» |
|
||||
| Бонус | Кеш, матрица, артефакты, «плохой» CI | «Стадии пайплайна» |
|
||||
|
||||
## Отличия Gitea-варианта
|
||||
|
||||
- workflow лежит в `.gitea/workflows/ci.yml`;
|
||||
- прогоны выполняет act_runner;
|
||||
- защита ветки — Status Check Contexts в Settings → Branches.
|
||||
|
||||
## Акт 0. Подготовка (до лекции, 15–20 минут)
|
||||
|
||||
1. Создайте пустой репозиторий `ci-demo` на сервере Gitea.
|
||||
2. Запушьте содержимое этой папки как корень репозитория:
|
||||
|
||||
```powershell
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Стартовый каркас демо"
|
||||
git branch -M main
|
||||
git remote add origin https://gitea.example.com/OWNER/ci-demo.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
3. Замените `gitea.example.com` и `OWNER` в бейдже `README.md`.
|
||||
4. Прогрейте кеш: дождитесь зелёного первого прогона act_runner.
|
||||
5. Защита ветки: обязательные Status Check Contexts `lint`, `test`, `metrics`.
|
||||
|
||||
**Чек-лист готовности:** первый прогон зелёный; проверки `lint`, `test`, `metrics`
|
||||
видны в branch protection; кеш прогрет.
|
||||
|
||||
## Акт 1. Зелёный старт (1–2 мин)
|
||||
|
||||
Покажите структуру: `app/`, `tests/`, `.gitea/workflows/ci.yml`. Запустите локально:
|
||||
|
||||
```powershell
|
||||
pytest -q
|
||||
```
|
||||
|
||||
**Ожидаемо:** все тесты зелёные (сейчас 19). Откройте Actions — последний прогон на `main` зелёный.
|
||||
|
||||
**Что говорить.** «Локально зелёный — и в CI зелёный. CI не делает ничего волшебного:
|
||||
он повторяет те же шаги — checkout, Python, зависимости, pytest. Workflow — ваш
|
||||
локальный ритуал, записанный в YAML».
|
||||
|
||||
Покажите `ci.yml`: `on:` — триггер, `jobs.lint` — стиль, `jobs.test` — тесты
|
||||
с порогом покрытия, `jobs.metrics` — `radon` и `xenon`, `needs` — порядок и гейт.
|
||||
|
||||
## Акт 2. Красный PR (3 мин)
|
||||
|
||||
```powershell
|
||||
git switch -c feature/vip-095
|
||||
```
|
||||
|
||||
В `app/discounts.py` измените скидку VIP: `return total * 0.9` → `return total * 0.95`.
|
||||
Тесты не трогайте.
|
||||
|
||||
```powershell
|
||||
git add -A
|
||||
git commit -m "VIP: скидка 0.95 (требование изменено)"
|
||||
git push -u origin feature/vip-095
|
||||
```
|
||||
|
||||
Откройте PR: `lint` — зелёный, `test` — красный, `metrics` — не запускается.
|
||||
В логе видно `assert ... pytest.approx(900.0)` и фактическое `950.0`.
|
||||
Кнопка merge заблокирована.
|
||||
|
||||
**Что говорить.** «Тест красный — merge нельзя. Это подарок: ошибка поймана за минуту.
|
||||
Красная main — stop-the-line. Упал только `test`; `lint` прошёл и не ждал тестов:
|
||||
быстрые проверки — первыми».
|
||||
|
||||
## Акт 3. Лечим тест, мёржим (2 мин)
|
||||
|
||||
**Спросите:** что правильнее — вернуть код или обновить тест? Ответ: требование
|
||||
изменилось, значит меняется тест.
|
||||
|
||||
В `tests/test_discounts.py`: `pytest.approx(900.0)` → `pytest.approx(950.0)`.
|
||||
|
||||
```powershell
|
||||
git commit -am "Тест VIP приведён к новому требованию 0.95"
|
||||
git push
|
||||
```
|
||||
|
||||
Дождитесь зелёного прогона и нажмите Merge. Покажите бейдж в README.
|
||||
|
||||
**Что говорить.** «Зелёная галочка на PR — Definition of Done в действии: код,
|
||||
ревью, зелёный CI, merge. Без гейта DoD — просто список пожеланий».
|
||||
|
||||
## Акт 4. Метрический гейт (2–3 мин)
|
||||
|
||||
**Действия.**
|
||||
|
||||
```powershell
|
||||
radon cc app -s -a
|
||||
xenon --max-absolute C app/
|
||||
```
|
||||
|
||||
**Ожидаемо:** `legacy_report.py:12 classify_order — C (17)`, гейт C зелёный.
|
||||
Теперь скопируйте строгую заготовку:
|
||||
|
||||
```powershell
|
||||
Copy-Item workflows_заготовки/ci-metrics-strict.yml .gitea/workflows/ci.yml
|
||||
git switch -c demo/strict-metrics
|
||||
git add .
|
||||
git commit -m "Демо: строгий метрический гейт"
|
||||
git push -u origin demo/strict-metrics
|
||||
```
|
||||
|
||||
В PR `metrics` красный: `xenon --max-absolute B` не пропускает ранг C.
|
||||
PR закройте, верните основной workflow.
|
||||
|
||||
**Что говорить.** «Метрика работает, когда её нельзя игнорировать. Порог C
|
||||
пропускает функцию сложности 17; порог B — нет. Рост сложности становится
|
||||
видимым и блокирующим, а не темой для спора на ревью».
|
||||
|
||||
## Бонусные акты (по времени)
|
||||
|
||||
- **Кеш**: сравните Duration первого и последнего прогонов; найдите `Cache hit`.
|
||||
- **Матрица**: `Copy-Item workflows_заготовки/ci-matrix.yml ...` → 3.11 красный,
|
||||
3.12/3.13 зелёные; PR не мёржить.
|
||||
- **Артефакты**: `ci-artifacts.yml` → скачайте `coverage-report`.
|
||||
- **Плохой CI**: `bad.yml` с `|| true` — тест падает, а проверка зелёная.
|
||||
|
||||
## Если что-то пошло не так
|
||||
|
||||
- Первый прогон медленный — кеша ещё нет; прогрейте заранее.
|
||||
- Branch protection не видит проверки — обновите страницу после первого прогона.
|
||||
- Раннеры тормозят — покажите заранее сделанные прогоны по закладкам.
|
||||
|
||||
## Уборка
|
||||
|
||||
```powershell
|
||||
git switch main
|
||||
git checkout main -- .gitea/workflows/ci.yml
|
||||
git push origin --delete feature/vip-095 demo/strict-metrics
|
||||
```
|
||||
|
||||
## Шпаргалка ответов
|
||||
|
||||
- **Почему отдельные job'ы?** Параллельность + `needs` задаёт порядок и гейт.
|
||||
- **Что такое runner?** Чистая машина под прогон; состояние не сохраняется.
|
||||
- **Можно ли доверять зелёному CI?** Только если проверки настоящие: без `|| true`.
|
||||
- **Чем CI отличается от CD?** CI — интеграция и проверки; CD — доставка, отдельная тема.
|
||||
BIN
Сценарий_демонстрации.pdf
Normal file
BIN
Сценарий_демонстрации.pdf
Normal file
Binary file not shown.
Reference in New Issue
Block a user