diff --git "a/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/.github/workflows/ci.yml" "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/.github/workflows/ci.yml" new file mode 100644 index 0000000..13ca483 --- /dev/null +++ "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/.github/workflows/ci.yml" @@ -0,0 +1,29 @@ +name: CI + +on: + pull_request: + branches: [main] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Build (syntax check) + run: python -m py_compile src/temperature.py + + - name: Run tests + run: python -m pytest tests/ -v diff --git "a/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/readme.md" "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/readme.md" new file mode 100644 index 0000000..39b66b9 --- /dev/null +++ "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/readme.md" @@ -0,0 +1,87 @@ +# Задание 106. Continuous Integration (CI) + +## Описание проекта + +Небольшая библиотека для конвертации температур между шкалами Цельсия, Фаренгейта и Кельвина, с проверкой корректности входных значений (запрет температур ниже абсолютного нуля). + +Репозиторий проекта: https://github.com/kir1903/ci-demo-temperature + +## Структура + +```text +src/temperature.py — функции конвертации +tests/test_temperature.py — юнит-тесты (pytest) +requirements.txt — зависимости +.github/workflows/ci.yml — пайплайн CI +``` + +## Запуск локально + +```bash +pip install -r requirements.txt +python -m pytest tests/ -v +``` + +## Настройка CI + +Используется **GitHub Actions**. Файл пайплайна: `.github/workflows/ci.yml`. + +Пайплайн запускается автоматически при создании pull request в ветку `main` и выполняет шаги последовательно: + +1. Checkout репозитория. +2. Установка Python и зависимостей проекта (`pip install -r requirements.txt`). +3. Сборка — проверка синтаксиса модуля (`python -m py_compile`). +4. Запуск тестов (`pytest`). + +Если один из шагов завершается с ошибкой (например, тест не проходит), весь пайплайн завершается с ошибкой, и pull request помечается как непрошедший проверку. + +```yaml +name: CI + +on: + pull_request: + branches: [main] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Build (syntax check) + run: python -m py_compile src/temperature.py + + - name: Run tests + run: python -m pytest tests/ -v +``` + +## Демонстрация работы пайплайна + +Для демонстрации подготовлены две ветки в репозитории: + +- `feature/add-fahrenheit-to-kelvin` — добавляет новую функцию конвертации и тест к ней. Все тесты проходят, пайплайн завершается успешно. +- `bugfix/broken-fahrenheit-formula` — намеренно содержит ошибку в формуле конвертации Цельсия в Фаренгейт (пропущено слагаемое `+32`). Тест `test_celsius_to_fahrenheit` не проходит, пайплайн завершается с ошибкой. + +### Успешный запуск + +Pull request из ветки `feature/add-fahrenheit-to-kelvin` в `main`. + +![Успешный запуск CI](./screenshots/ci-success.png) + +### Неуспешный запуск + +Pull request из ветки `bugfix/broken-fahrenheit-formula` в `main`. + +![Неуспешный запуск CI](./screenshots/ci-failure.png) diff --git "a/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/requirements.txt" "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/requirements.txt" new file mode 100644 index 0000000..40543aa --- /dev/null +++ "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/requirements.txt" @@ -0,0 +1 @@ +pytest==8.3.3 diff --git "a/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/screenshots/ci-failure.png" "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/screenshots/ci-failure.png" new file mode 100644 index 0000000..b9918c7 Binary files /dev/null and "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/screenshots/ci-failure.png" differ diff --git "a/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/screenshots/ci-success.png" "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/screenshots/ci-success.png" new file mode 100644 index 0000000..53c92a8 Binary files /dev/null and "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/screenshots/ci-success.png" differ diff --git "a/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/src/__init__.py" "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/src/__init__.py" new file mode 100644 index 0000000..e69de29 diff --git "a/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/src/temperature.py" "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/src/temperature.py" new file mode 100644 index 0000000..ddcb625 --- /dev/null +++ "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/src/temperature.py" @@ -0,0 +1,28 @@ +"""Simple temperature conversion utilities used to demonstrate CI.""" + +ABSOLUTE_ZERO_CELSIUS = -273.15 + + +def celsius_to_fahrenheit(celsius: float) -> float: + if celsius < ABSOLUTE_ZERO_CELSIUS: + raise ValueError("Temperature below absolute zero is not possible") + return celsius * 9 / 5 + 32 + + +def fahrenheit_to_celsius(fahrenheit: float) -> float: + celsius = (fahrenheit - 32) * 5 / 9 + if celsius < ABSOLUTE_ZERO_CELSIUS: + raise ValueError("Temperature below absolute zero is not possible") + return celsius + + +def celsius_to_kelvin(celsius: float) -> float: + if celsius < ABSOLUTE_ZERO_CELSIUS: + raise ValueError("Temperature below absolute zero is not possible") + return celsius + 273.15 + + +def kelvin_to_celsius(kelvin: float) -> float: + if kelvin < 0: + raise ValueError("Temperature in Kelvin cannot be negative") + return kelvin - 273.15 diff --git "a/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/tests/__init__.py" "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/tests/__init__.py" new file mode 100644 index 0000000..e69de29 diff --git "a/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/tests/test_temperature.py" "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/tests/test_temperature.py" new file mode 100644 index 0000000..d0b601e --- /dev/null +++ "b/\320\240\321\203\320\272\320\276\321\201\321\203\320\265\320\262\320\260\320\225\320\224/tests/test_temperature.py" @@ -0,0 +1,36 @@ +import pytest + +from src.temperature import ( + celsius_to_fahrenheit, + celsius_to_kelvin, + fahrenheit_to_celsius, + kelvin_to_celsius, +) + + +def test_celsius_to_fahrenheit(): + assert celsius_to_fahrenheit(0) == 32 + assert celsius_to_fahrenheit(100) == 212 + + +def test_fahrenheit_to_celsius(): + assert fahrenheit_to_celsius(32) == 0 + assert fahrenheit_to_celsius(212) == 100 + + +def test_celsius_to_kelvin(): + assert celsius_to_kelvin(0) == 273.15 + + +def test_kelvin_to_celsius(): + assert kelvin_to_celsius(273.15) == 0 + + +def test_celsius_below_absolute_zero_raises(): + with pytest.raises(ValueError): + celsius_to_fahrenheit(-300) + + +def test_kelvin_negative_raises(): + with pytest.raises(ValueError): + kelvin_to_celsius(-1)