Skip to content

Latest commit

 

History

History
274 lines (222 loc) · 21.1 KB

development.md

File metadata and controls

274 lines (222 loc) · 21.1 KB

Журнал разработки (итерация 1)

Здесь фиксируются реально выполненные команды и их фактический вывод. Ничего не додумано: если прогон не выполнялся — так и написано.

Окружение

  • Windows 10 Pro 19045, PowerShell/Git Bash, git 2.55.0.windows.4.
  • uv 0.12.0, изолированный venv .venv на CPython 3.11.3.
  • Переменная PYTHONPATH принудительно очищается в каждой команде (PYTHONPATH= ...): в этой машине она указывает на сторонний агент и «протекает» в venv.
  • Платформа 1С:Предприятие на машине разработки отсутствует, стенда нет → нативные прогоны конфигуратора не выполнялись (см. «Непроверенные швы»).

Подготовка окружения

PYTHONPATH= uv venv --python 3.11 .venv
PYTHONPATH= uv pip install --python .venv -q pytest ruff hatchling
PYTHONPATH= uv pip install --python .venv -q -e .

Ход TDD по слайсам (RED → GREEN)

Слайс Тесты RED (до реализации) GREEN (после)
1. VERSION/AUTHORS/отчёт tests/test_metadata_files.py ModuleNotFoundError: No module named 'gitsync' 9 passed in 0.04s
2. git + нативные argv tests/test_git_repo.py, tests/test_designer.py ModuleNotFoundError: No module named 'gitsync.designer' 18 passed in 2.85s
3. параллельный конвейер tests/test_sync_pipeline.py ModuleNotFoundError: No module named 'gitsync.backends' сначала 2 failed, 14 passed, после правок (safe_join, арифметика дат в тесте) — 16 passed in 8.38s
4. CLI и пакетный режим tests/test_cli.py ModuleNotFoundError: No module named 'gitsync.cli' 9 passed in 2.38s, затем +1 тест на пример из examples/
10. общий репозиторий базы (конфигурация + два расширения) tests/test_monorepo10.py 11 failed, 3 passed: вложенный .git в ещё не созданном подкаталоге, маркер сверялся с числом коммитов всего репозитория, core.quotepath ломал разбор git status 16 passed, полный набор — 173 passed in 153.25s
12. переносимость на Linux и в контейнер tests/test_docker12_portability.py 6 failed: манифест с BOM отвергался как «не JSON»; отказ git по владельцу каталога молча превращался во «вложенный репозиторий»; остановка оператором в sync-all давала код 1 и запускала следующее хранилище; справка и ошибка --v8-path знали только Windows 13 passed, полный набор на Windows — 325 passed, 15 skipped

Промежуточный провал слайса 3 зафиксирован честно: тест на выход выгрузки за пределы каталога не проходил, потому что запись «наружу» вообще не проверялась; это привело к появлению gitsync/safepath.py и его использованию в бэкендах.

Итоговый прогон

PYTHONPATH= ./.venv/Scripts/python.exe -m pytest
.....................................................                    [100%]
53 passed in 13.41s

Распределение (pytest --collect-only -q):

tests/test_cli.py: 10
tests/test_designer.py: 8
tests/test_git_repo.py: 10
tests/test_metadata_files.py: 9
tests/test_sync_pipeline.py: 16

Статический анализ

PYTHONPATH= ./.venv/Scripts/python.exe -m ruff check --output-format=concise src tests
All checks passed!

Сборка и установка пакета

PYTHONPATH= uv build --out-dir dist .
Successfully built dist\gitsync_py-0.1.0.tar.gz
Successfully built dist\gitsync_py-0.1.0-py3-none-any.whl
PYTHONPATH= uv venv --python 3.11 D:/tmp/gsp-smoke
PYTHONPATH= uv pip install --python D:/tmp/gsp-smoke dist/gitsync_py-0.1.0-py3-none-any.whl

Дымовой прогон CLI (установленный wheel, чистый venv)

gitsync-py --help
gitsync-py sync --workdir "D:/tmp/gsp-smoke-run/копия" --backend fixture \
  --fixture-root "D:/tmp/gsp-smoke-run/фикстура" --jobs 2 --email-domain example.org

Фактический вывод:

2026-09-15 12:38:44,210 INFO gitsync.sync: Синхронизированная версия: 0, максимум в хранилище: 2
2026-09-15 12:38:44,363 INFO gitsync.sync: Версия 1 зафиксирована (47d13459...)
2026-09-15 12:38:44,512 INFO gitsync.sync: Версия 2 зафиксирована (f65f15f3...)
Зафиксировано версий: 2 (1..2)

Проверка результата в репозитории:

git log --format='%an|%ae|%ad|%s' --date=format:'%Y-%m-%d %H:%M:%S' --reverse
Иванов|Иванов@example.org|2026-09-15 10:20:30|Первая версия
Петров|Петров@example.org|2026-09-16 08:00:00|Вторая версия

git rev-list --count HEAD  -> 2

Повторный запуск той же команды:

2026-09-15 12:38:44,756 INFO gitsync.sync: Синхронизированная версия: 2, максимум в хранилище: 0
Новых версий нет

Дублирующих коммитов не появилось (rev-list --count HEAD остался равен 2). Временные каталоги дымового прогона после проверки удалены.

Что проверяется тестами на настоящем git

  • коммиты строго по возрастанию версии при экспорте, завершающемся вразнобой (test_sync_commits_in_version_order_despite_out_of_order_export);
  • автор/почта/дата/комментарий и файл VERSION (test_sync_preserves_author_date_and_version_marker);
  • переопределение подписи через AUTHORS;
  • ограниченность конвейера (--jobs, --queue-limit) и изоляция каталогов на версию;
  • сбой версии N не коммитит N+1, VERSION остаётся на последней успешной;
  • повторы при временных сбоях и продолжение после обрыва без дублей коммитов;
  • удаление объектов между версиями отражается в git (git ls-files, git show --stat);
  • служебные файлы (.git, .gitignore, .gitattributes, AUTHORS, VERSION) переживают очистку;
  • отмена укладывается в ограниченное время (тест требует < 6 с при 40 версиях по 0.25 с);
  • грязная рабочая копия не трогается и вызывает отказ;
  • пути с пробелами и кириллицей — во всех тестах рабочая копия называется «рабочая копия»;
  • выход выгрузки за каталог и символьные ссылки отвергаются;
  • блокировка цели: второй держатель получает LockBusyError, потоки сериализуются;
  • argv конфигуратора: порядок -v после ConfigurationRepositoryUpdateCfg, отсутствие shell, маскирование паролей, таймаут.

Слайс 12: Linux и Docker

Стенд: Ubuntu 24.04.4 в WSL2, Docker 29.1.3 (только устаревший сборщик, buildx отсутствует), Compose 2.40.3. Образ ядра собран и запущен; образ с платформой 1С не собирался — дистрибутива для Linux не было.

# образ ядра и цель для тестов
docker build -f docker/Dockerfile.core --target core -t gitsync-py:core .
docker build -f docker/Dockerfile.core --target test -t gitsync-py:test .

# весь набор тестов внутри Linux-контейнера (продукт берётся из установленного колеса)
docker run --rm -e TZ=Europe/Moscow gitsync-py:test \
  sh -c 'git config --global user.email ci@example.org;
         git config --global user.name CI;
         git config --global init.defaultBranch main;
         python -m pytest -q -rs'

# сценарии пользователя на файловой фикстуре, образ ядра, без исходников продукта.
# Скрипт приёмки импортирует ОДНОГО помощника из набора тестов (support.native_report):
# им собираются входные данные — отчёт по версиям. Каталог готовится так же, как в CI.
rm -rf /tmp/acceptance && mkdir -p /tmp/acceptance
cp docker/acceptance/*.py /tmp/acceptance/
cp -r tests/support /tmp/acceptance/support
docker run --rm -e TZ=Europe/Moscow -v /tmp/acceptance:/acceptance:ro \
  --entrypoint python gitsync-py:core /acceptance/core_acceptance.py /tmp/приёмка

Результаты:

Что Результат
Образ ядра 342 МБ на диске (251 МБ — общий базовый образ Python; +9 МБ к слайсу 12 дал клиент ssh); linux/amd64, пользователь app (uid 10001), Python 3.11.16, Git 2.39.5, OpenSSH 9.2p1
Базовый образ python:3.11-slim-bookworm@sha256:528257d4…bdf84, закреплён в Dockerfile
pytest в контейнере 321 passed, 19 skipped — пропуски только про то, чего нет в Linux: NTFS junction (5), описатель каталога Windows без FILE_SHARE_DELETE (1) и обёртка для Windows PowerShell (13)
Сценарии пользователя (core_acceptance.py) 45 проверок, 0 неуспешных
Два контейнера, одно хранилище окна входа не пересеклись; контроль с раздельными томами — пересечение есть (числа последнего прогона — в слайсе 13)
docker compose config принят; без переменных — отказ с названием пропущенной
docker compose run sync-fixture 2 коммита, даты +0300 при TZ=Europe/Moscow, повтор даёт 0 новых коммитов

Содержимое образа выполнения проверено отдельно: продукт импортируется из site-packages (а не из дерева исходников), tempExtension.cfe сходится по sha256 (3d481624…7102, 3720 байт), тестов, .git, документации, примеров и каких-либо дистрибутивов в образе нет.

Две правки коснулись тестов, а не продукта: test_review03_regressions.py и test_safety_recovery.py предполагали установку из дерева исходников и вычисляли каталог импорта как tests/../src. Теперь он берётся у самого импортированного модуля, поэтому инвариант «дочерний процесс берёт тот же код, что и тест» сохраняется и при установке колеса.

Слайс 13: закрытие находок приёмки Docker

Тот же стенд. Проверялось ровно то, что было названо неготовым.

Происхождение образа сверяется, а не предполагается

Контекст сборки берётся из git archive коммита, а не из рабочего дерева: в рабочем дереве на Windows часть файлов лежит с CRLF, и сборка «из папки» даёт образ, который с коммитом побайтово не совпадает. Манифест (sha256 каждого blob'а) считается вне продукта, чтобы репозиторий не содержал хешей самого себя.

Сверка Результат
Контекст сборки против blob'ов коммита 78 файлов из 78 совпали побайтово
Установленный пакет в образе против blob'ов src/gitsync/** 19 из 19 совпали побайтово (без «различие только в переводах строк»)
tempExtension.cfe в образе 3720 байт, sha256 3d481624…7102 — как в дереве

Переводы строк намеренно не нормализуются в продукте: .gitattributes уже задаёт eol=lf, а рабочая копия Windows остаётся такой, какой её делает git.

Что проверено запуском

Проверка Результат
pytest в контейнере 321 passed, 19 skipped, ruff check чист
Сценарии пользователя (core_acceptance.py) 45 проверок, 0 неуспешных
--user <UID>:0 (штатный путь) работает: --version, тома, запись в общий том
--user <UID>:<свой GID> без пересборки код 78 и сообщение с двумя рабочими выходами
root без согласия / с GITSYNC_ALLOW_ROOT=1 78 / работает с предупреждением
Образ, собранный с APP_UID/APP_GID --user 1000:1000 работает, включая ssh
Отправка по ssh из образа ядра реальный одноразовый сервер Git по ssh: push прошёл, ветка и дерево коммита на сервере совпали с локальными
Отрицательный контроль ssh: без ключа Permission denied (publickey), код 128, в зеркале ничего не изменилось
Отрицательный контроль ssh: неизвестный сервер Host key verification failed, код 128
docker compose run --rm -T sync-fixture 2 коммита, даты +0300 при TZ=Europe/Moscow; повтор — 0 новых
Служба push в compose (ssh, ключ как secret) код 0, в зеркале ровно тот же коммит и то же дерево
Разделение секретов sync → только пароль хранилища, push → только ключ ssh
Два контейнера, одно хранилище, общий том ключ блокировки совпал, окна входа не пересеклись (разрыв 11,5 с)
Контроль: раздельные тома блокировок окна пересеклись на 2,8 с — проверка отличает рабочую настройку от сломанной
Контроль: другой UID PermissionError на файле блокировки 0600 — отказ, а не молчаливый обход

Ключ ssh для проверки создавался разово, только в каталоге стенда, и удалён вместе с сервером. Ни одного настоящего доступа к внешним репозиториям в этих проверках не использовалось.

Непроверенные швы (честно)

  1. Нативный бэкенд не исполняется этим набором тестов. NativeStorageBackend и DesignerRunner покрыты здесь только тестами на состав argv: платформы 1С на машине разработки нет. Сами нативные прогоны выполнялись отдельно, на выделенном стенде (1С:Предприятие 8.3.27.2130, синтетические хранилища): выгрузка версий основной конфигурации и расширений, а также раскладка «один репозиторий базы» из слайса 10. Доказательства хранятся вне репозитория и в него намеренно не копируются.
  2. Формат отчёта /ConfigurationRepositoryReport не сверялся с живым отчётом. Парсер написан по структуре русскоязычного отчёта; исходники v8storage, которыми пользуется upstream, недоступны. Фикстуры синтетические.
  3. Создание временной ИБ (CREATEINFOBASE File=...) не проверялось; для реальных прогонов предусмотрен параметр ib_factory, чтобы подставить готовые базы.
  4. Ожидание лицензии 1С (повтор при «Не обнаружено свободной лицензии!») не перенесено — есть только общий механизм повторов.
  5. Многопроцессная блокировка проверена в пределах одного процесса (потоки) и логикой атомарного создания файла; кросс-процессный тест не писался.
  6. Симлинки на Windows: тест допускает обе ветки, так как создание симлинка требует прав; на Linux ветка с реальным симлинком выполняется.
  7. CI не запускался — workflow .github/workflows/ci.yml добавлен, но GitHub-репозиторий ещё не создан (нет авторизации), прогонов не было. Это относится и к добавленной задаче сборки образа: сами команды выполнялись вручную на стенде (см. слайс 12), но именно как задача GitHub Actions они не запускались ни разу.
  8. Работа с настоящим хранилищем 1С из Linux не проверялась — упирается в лицензию. Приватный образ теперь собран из настоящего комплекта 8.3.27.2130: состав компонентов, ключи установщика и перечень системных библиотек сняты с самого дистрибутива (--help установщика и ldd на установленном 1cv8), необходимость Xvfb подтверждена запуском. Но лицензии у стенда нет: на первом обращении к хранилищу платформа отвечает «Не найдена лицензия…» (код 1), поэтому отчёт по версиям, DumpCfg/LoadCfg/DumpConfigToFiles и сверка с XML-oracle на Linux не выполнялись. Что закрыто, а что нет, — в docs/docker-guide.md, раздел 11.
  9. xvfb-run не пропускал SIGTERM — исправлено. Остановка контейнера доставалась посреднику, а не синхронизации: контейнер завершался с кодом 143, обработчик в команде не срабатывал, то есть stop_grace_period в docker/compose.yaml не работал вовсе. Заменено собственной обёрткой docker/xvfb-entrypoint.sh (свой Xvfb + exec), поведение закреплено тестами tests/test_native_xvfb_entrypoint.py.
  10. Разделение доступа к хранилищу между контейнерами проверено на подменённом конфигураторе. Подменён последний шов перед запуском процесса (DesignerRunner.run); ключ блокировки, общий том и порядок захвата — настоящий код. На живой платформе тот же опыт не повторялся.