Всем привет, меня зовут Осипов Станислав. Я занимаюсь AppSec/DevOps с 2021 года. В этой статье я хочу рассказать как можно собрать покрытие Python приложения в runtime незавершая работу приложения.

Что было использовано в статье:
https://github.com/pallets/flask - Flask 3.03
https://github.com/nedbat/coveragepy - coverage 7.5.1
Подготовка Flask
Выполним установку Flask согласно инструкции:
https://github.com/pallets/flask/blob/main/docs/installation.rst
В директории приложения создадим app.py, взяв за основу пример из репозитория Flask, и добавим пару роутов:
import coverage, hashlib from flask import Flask #Регистрация rout'ов def main(): @app.route("/") def hello(): return "Main route" @app.route("/1") def hello1(): return "First route" @app.route("/2") def hello2(): return hashlib.sha256(b"Nobody inspects the spammish repetition").hexdigest() #Запуск веб-сервера app.run() #Инициализация app = Flask(__name__) main()
Подготовка coverage
Установим модуль для сборки покрытия:
pip install coverage
Для отображения покрытия third-party модулей в отчёте закомментим следующие строки:
.venv/lib/python3.11/site-packages/coverage/inorout.py-16478: if self.third_match.match(filename) and not self.source_in_third_match.match(filename): .venv/lib/python3.11/site-packages/coverage/inorout.py:16579: return "inside --source, but is third-party"
Инструментация кода
Согласно https://coverage.readthedocs.io/en/7.5.1/api.html сбор покрытия осуществляется следующим образом:
import coverage cov = coverage.Coverage() #Инициализация модуля cov.start() # Запуск сбора покрытия # .. call your code .. cov.stop() # Остановить сбор покрытия - в нашем случае является избыточным cov.save() # Сохранить покрытие cov.html_report() # Сгенерировать отчет в формате .html
Так как стоит задача обновлять покрытие при каждом запросе к приложению Flask, то необходимо внедрить функции по сборке покрытия в исходный код Flask.
Для этого в файле выполним следующие изменения:
.venv/lib/python3.11/site-packages/flask/app.py
Подключим модуль coverage указав флаги:
cover_pylib - отображать покрытие стандартых модулей Python
auto_data - продолжать запись покрытия в один файл
source - список директорий исходного кода, которые будут учитываться в отчете
Подключения модуля coverage
13a14 > import coverage
Встраивание модуля покрытия
Добавим инициализацию модуля coverage в аттрибуте класса Flask и метод запуска сбора покрытия:
253c253,254 #Добавляем инициализацию сбора покрытия в объект класса Flask и запуск сбора покрытия в метод "run" класса Flask < --- > self.cov = coverage.Coverage(cover_pylib=True, auto_data=True, source=["./", ".venv"]) > self.cov.start()
Поиск метода для сохранения покрытия
Найдем метод, который будет вызываться при обработке запроса.
В случае Flask я выбрал метод make_response, который отвечает за отправку ответа на запорс.
В методе этом будем сохранять/обновлять покрытие
1230c1229,1230 < --- > self.cov.save() > self.cov.html_report()
Проверяем сбор покрытия в Runtime
Запустим приложение: python3 app.py
В браузере перейдем по адресу, содержащий main route - 127.0.0.1:5000
Откроем страницу, содержащую покрытие - firefox htmlcov/index.html

Покрытие: Составило 11% вместе исходным кодом сторонних зависимостей.
В браузере перейдем по адресу, содержащий второй route - 127.0.0.1:5000/2
Обновим страницу, содержащую покрытие

Покрытие: Увеличилось на 5 процентов после обращения к 2 rout'у.
Итоги
Благодаря встраиванию модуля сбора покрытия в исходный код Flask, удалось добиться обновления покрытия при каждом запросе к приложеню.
Благодарю за внимание!
Предложения, вопросы, замечания, конструктивная критика приветствуется в комментариях.
