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