diff --git a/.github/workflows/incremental-dump.yml b/.github/workflows/incremental-dump.yml new file mode 100644 index 0000000..63d1582 --- /dev/null +++ b/.github/workflows/incremental-dump.yml @@ -0,0 +1,23 @@ +name: Incremental dump regression + +on: [push, pull_request, workflow_dispatch] + +permissions: + contents: read + +jobs: + regression: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install OneScript 2.2.0 + run: | + curl --fail --location --retry 3 https://github.com/EvilBeaver/OneScript/releases/download/v2.2.0/OneScript-2.2.0-linux-x64.zip -o "$RUNNER_TEMP/onescript.zip" + echo "3b3b0a96e141d6758b74cbf492d5e5c11823bfd92318caff1aaa19bd500f1df7 $RUNNER_TEMP/onescript.zip" | sha256sum --check + unzip -q "$RUNNER_TEMP/onescript.zip" -d "$RUNNER_TEMP/onescript" + chmod +x "$RUNNER_TEMP/onescript/bin/oscript" + echo "$RUNNER_TEMP/onescript/bin" >> "$GITHUB_PATH" + - name: Test without a 1C installation + run: | + oscript -version + python3 -B -m unittest discover -s tests -p test_incremental_dump.py -v diff --git a/src/v8runner.os b/src/v8runner.os index 862cd2b..f06cf19 100644 --- a/src/v8runner.os +++ b/src/v8runner.os @@ -728,7 +728,13 @@ Если ТолькоИзмененные Тогда - Если ПроверитьВозможностьОбновленияФайловВыгрузки(КаталогВыгрузки, ПутьКФайлуВерсийДляСравнения, ФорматВыгрузки) Тогда + ФайлВерсий = Новый Файл(ПутьКФайлуВерсийДляСравнения); + ФайлВерсийКаталога = Новый Файл(ОбъединитьПути(КаталогВыгрузки, "ConfigDumpInfo.xml")); + Если ФайлВерсий.ПолноеИмя = ФайлВерсийКаталога.ПолноеИмя Тогда + ПутьКФайлуВерсийДляСравнения = ""; + КонецЕсли; + + Если ПроверитьВозможностьОбновленияФайловВыгрузки(КаталогВыгрузки, ПутьКФайлуВерсийДляСравнения, ФорматВыгрузки, Истина) Тогда Параметры.Добавить("-update"); @@ -2867,7 +2873,8 @@ // Функция проверяет возможность обновления файлов выгрузки, для каталога или конкретного файла версий // -Функция ПроверитьВозможностьОбновленияФайловВыгрузки(Знач КаталогВыгрузки, Знач ПутьКФайлуВерсийДляСравнения = "", Знач ФорматВыгрузки = "") +Функция ПроверитьВозможностьОбновленияФайловВыгрузки(Знач КаталогВыгрузки, Знач ПутьКФайлуВерсийДляСравнения = "", Знач ФорматВыгрузки = "", + Знач ПустойОтветРазрешаетОбновление = Ложь) ПутьКФайлуИзменений = ВременныеФайлы.НовоеИмяФайла(); ОбновлениеВозможно = Ложь; @@ -2893,6 +2900,8 @@ Лог.Отладка("Строка проверки на возможность выгрузки конфигурации: <%1> = <%2> ", СтрокаПолныйДамп, СтрокаВыгрузки); ОбновлениеВозможно = НЕ (СтрокаВыгрузки = СтрокаПолныйДамп); + Иначе + ОбновлениеВозможно = ПустойОтветРазрешаетОбновление И мИсключениеПриОшибкеВыполненияКоманды; КонецЕсли; чтениеФайла.Закрыть(); diff --git a/tests/test_incremental_dump.py b/tests/test_incremental_dump.py new file mode 100644 index 0000000..2e59cd6 --- /dev/null +++ b/tests/test_incremental_dump.py @@ -0,0 +1,141 @@ +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + + +SOURCE = Path(__file__).resolve().parents[1] / 'src' / 'v8runner.os' + + +@unittest.skipUnless(shutil.which('oscript'), 'OneScript is required') +class IncrementalDumpTest(unittest.TestCase): + def check_response(self, response, expected, versions=True, explicit=False, + raise_errors=True, allow_empty=True): + source = SOURCE.read_text(encoding='utf-8-sig') + start = source.index('Функция ПроверитьВозможностьОбновленияФайловВыгрузки(') + end = source.index('КонецФункции', start) + len('КонецФункции') + with tempfile.TemporaryDirectory(prefix='v8runner-incremental-') as directory: + root = Path(directory) + if versions: + (root / 'ConfigDumpInfo.xml').touch() + reference = root / ('ConfigDumpInfo.xml' if versions else 'reference.xml') + if explicit: + reference.touch() + if response is not None: + (root / 'response.txt').write_bytes(response) + harness = '''#Использовать tempfiles +#Использовать logos +Перем Лог; +Перем мИсключениеПриОшибкеВыполненияКоманды; + +Процедура ВыгрузитьИзмененияКонфигурацииВФайл(Каталог, Путь, Версии, Формат) + Если Новый Файл("response.txt").Существует() Тогда + КопироватьФайл("response.txt", Путь); + КонецЕсли; +КонецПроцедуры + +''' + harness += source[start:end] + harness += ''' +Лог = Логирование.ПолучитьЛог("test.incremental"); +мИсключениеПриОшибкеВыполненияКоманды = АргументыКоманднойСтроки[1] = "raise"; +Результат = ПроверитьВозможностьОбновленияФайловВыгрузки(ТекущийКаталог(), АргументыКоманднойСтроки[0], "", АргументыКоманднойСтроки[2] = "allow"); +Сообщить(?(Результат, "INCREMENTAL", "FULL")); +''' + script = root / 'check.os' + script.write_text(harness, encoding='utf-8-sig') + env = os.environ.copy() + env.pop('OSLIB_LOADER_DUPLICATES', None) + result = subprocess.run( + ['oscript', str(script), str(reference) if explicit else '', + 'raise' if raise_errors else 'ignore', 'allow' if allow_empty else 'deny'], + cwd=root, env=env, capture_output=True, text=True, timeout=30) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertEqual(result.stdout.strip(), expected) + + def test_empty_response(self): + self.check_response(b'', 'INCREMENTAL') + + def test_bom_only_response(self): + self.check_response(b'\xef\xbb\xbf', 'INCREMENTAL', explicit=True) + + def test_modified(self): + self.check_response(b'Modified:Configuration.Test\r\n', 'INCREMENTAL') + + def test_new(self): + self.check_response(b'New:Catalog.Test\r\n', 'INCREMENTAL') + + def test_full_dump(self): + self.check_response(b'FullDump\r\n', 'FULL') + + def test_full_dump_case_insensitive(self): + self.check_response(b'fulldump\r\n', 'FULL') + + def test_missing_response(self): + self.check_response(None, 'FULL') + + def test_missing_versions(self): + self.check_response(b'', 'FULL', versions=False) + + def test_external_reference_without_local_versions(self): + self.check_response(b'', 'INCREMENTAL', versions=False, explicit=True) + + def test_empty_response_with_suppressed_errors_stays_full(self): + self.check_response(b'', 'FULL', raise_errors=False) + + def test_empty_response_for_extension_stays_full(self): + self.check_response(b'', 'FULL', allow_empty=False) + + def check_command(self, relative_versions, expected_external): + source = SOURCE.read_text(encoding='utf-8-sig') + start = source.index('Процедура ВыгрузитьКонфигурациюВФайлы(') + end = source.index('КонецПроцедуры', start) + len('КонецПроцедуры') + harness = '''Перем РежимВыгрузкиКонфигурации; +Функция СтандартныеПараметрыЗапускаКонфигуратора() + Возврат Новый Массив; +КонецФункции +Функция ОбернутьВКавычки(Значение) + Возврат Значение; +КонецФункции +Функция ИспользуемаяВерсияПлатформыСтаршеИлиРавна(Версия) + Возврат Истина; +КонецФункции +Функция ПроверитьВозможностьОбновленияФайловВыгрузки(Каталог, Версии, Формат, ПустойОтвет) + Возврат Истина; +КонецФункции +Процедура ВыполнитьКоманду(Параметры) + Для Каждого Параметр Из Параметры Цикл + Сообщить(Параметр); + КонецЦикла; +КонецПроцедуры +''' + harness += source[start:end] + harness += '\nВыгрузитьКонфигурациюВФайлы(ТекущийКаталог(), "Hierarchical", Истина, АргументыКоманднойСтроки[0]);\n' + with tempfile.TemporaryDirectory(prefix='v8runner-command-') as directory: + root = Path(directory) + script = root / 'command.os' + script.write_text(harness, encoding='utf-8-sig') + versions = str(root / 'ConfigDumpInfo.xml') if relative_versions is None else relative_versions + result = subprocess.run(['oscript', str(script), versions], cwd=root, + capture_output=True, text=True, timeout=30) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn('-update', result.stdout.splitlines()) + self.assertEqual('-configDumpInfoForChanges' in result.stdout, expected_external) + + def test_same_absolute_versions_update_in_place(self): + self.check_command(None, False) + + def test_same_relative_versions_update_in_place(self): + self.check_command('ConfigDumpInfo.xml', False) + + def test_external_versions_retained(self): + self.check_command('../reference/ConfigDumpInfo.xml', True) + + def test_implicit_versions_update_in_place(self): + self.check_command('', False) + + +if __name__ == '__main__': + unittest.main()