answer42 0.5.7__tar.gz → 0.5.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {answer42-0.5.7 → answer42-0.5.8}/PKG-INFO +1 -1
- {answer42-0.5.7 → answer42-0.5.8}/pyproject.toml +1 -1
- {answer42-0.5.7 → answer42-0.5.8}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl +95 -3
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
- answer42-0.5.8/src/mcp_1c/assets/MCPTestManager.cf +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/runtime.py +118 -15
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/server.py +183 -92
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/win_desktop.py +72 -11
- answer42-0.5.7/src/mcp_1c/assets/MCPTestManager.cf +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/.gitignore +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/LICENSE +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/README.md +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/credentials.example.json +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/docs/agent-installation.md +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/docs/architecture.md +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/docs/assets/answer42-logo.png +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/docs/assets/platform42-logo.svg +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/docs/installation.md +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/scripts/build_cf.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/scripts/build_pages.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/scripts/e2e_http_smoke.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/scripts/e2e_stable.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/scripts/openclaw_mcp_autoreload.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/scripts/rag_cli.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/scripts/read_webclient_start_info.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/cf/Configuration.xml +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Catalogs//320/237/320/241_/320/222/320/273/320/260/320/264/320/265/320/273/320/265/321/206.xml" +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Catalogs//320/237/320/241_/320/241/320/277/321/200/320/260/320/262/320/276/321/207/320/275/320/270/320/2721.xml" +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/ConfigDumpInfo.xml +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Configuration.xml +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Ext/ManagerModule.bsl" +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Ext/ObjectModule.bsl" +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Templates//320/236/321/201/320/275/320/276/320/262/320/275/320/260/321/217/320/241/321/205/320/265/320/274/320/260/320/232/320/276/320/274/320/277/320/276/320/275/320/276/320/262/320/272/320/270/320/224/320/260/320/275/320/275/321/213/321/205/Ext/Template.xml" +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Templates//320/236/321/201/320/275/320/276/320/262/320/275/320/260/321/217/320/241/321/205/320/265/320/274/320/260/320/232/320/276/320/274/320/277/320/276/320/275/320/276/320/262/320/272/320/270/320/224/320/260/320/275/320/275/321/213/321/205.xml" +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262.xml" +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/__init__.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/assets/__init__.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/auth.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/bridge.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/credentials.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/os_support.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/platform.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/protocol.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/__init__.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/detect.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/dump.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/model.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/parsers.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/service.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/store.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/summary_backend.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/summary_vector.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/recorder.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/release_helper.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/skill_installer.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/webclient_probe.py +0 -0
- {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/window_control.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: answer42
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.8
|
|
4
4
|
Summary: Answer42 — The Answer to Life, Universe, and 1C — UI Driver. MCP-powered 1C:Enterprise UI automation: click, fill, navigate, test, and introspect managed forms through the test-client API
|
|
5
5
|
Author: Marvin (AI Assistant), 42Clouds, and contributors
|
|
6
6
|
Author-email: "Kosolapov Stanislav (proDOOMman)" <prodoomman@gmail.com>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "answer42"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.8"
|
|
4
4
|
description = "Answer42 — The Answer to Life, Universe, and 1C — UI Driver. MCP-powered 1C:Enterprise UI automation: click, fill, navigate, test, and introspect managed forms through the test-client API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
#Область ОписаниеПеременных
|
|
2
2
|
|
|
3
|
+
// Имена локальных переменных, совпадающие со свойствами управляемой формы
|
|
4
|
+
// (Заголовок, ТолькоПросмотр, ТекущийЭлемент и т.п.), без явного "Перем"
|
|
5
|
+
// ссылаются на свойства этой формы менеджера: присваивание меняет форму,
|
|
6
|
+
// а чтение возвращает её значение, а не параметр команды. Поэтому такие
|
|
7
|
+
// переменные объявляются в функциях через "Перем".
|
|
3
8
|
&НаКлиенте
|
|
4
9
|
Перем СоединениеWebSocket;
|
|
5
10
|
|
|
@@ -921,6 +926,7 @@
|
|
|
921
926
|
// BSLLS:DeprecatedCurrentDate-off
|
|
922
927
|
&НаКлиенте
|
|
923
928
|
Процедура ПодключитьсяКТестовомуКлиенту(Пользователь = "", ИмяФайлаЛога = "")
|
|
929
|
+
Перем Подсказка;
|
|
924
930
|
Лог("ПодключитьсяКТестовомуКлиенту: ждём 3с перед попыткой соединения");
|
|
925
931
|
Пауза = ТекущаяДата() + 3;
|
|
926
932
|
Пока ТекущаяДата() < Пауза Цикл
|
|
@@ -1075,6 +1081,7 @@
|
|
|
1075
1081
|
|
|
1076
1082
|
&НаКлиенте
|
|
1077
1083
|
Функция АктивироватьОкноMCP(Параметры)
|
|
1084
|
+
Перем Заголовок;
|
|
1078
1085
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
1079
1086
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
1080
1087
|
КонецЕсли;
|
|
@@ -1103,11 +1110,35 @@
|
|
|
1103
1110
|
ТекущийИндекс = ТекущийИндекс + 1;
|
|
1104
1111
|
КонецЦикла;
|
|
1105
1112
|
|
|
1113
|
+
// Заголовок окна клиента часто не совпадает с заголовком открытой в нём
|
|
1114
|
+
// формы (главное окно, окно с несколькими формами), поэтому при промахе
|
|
1115
|
+
// по заголовку окна ищем окно, содержащее форму с таким заголовком.
|
|
1116
|
+
Если НайденноеОкно = Неопределено И Индекс < 0 И Заголовок <> "" Тогда
|
|
1117
|
+
ТекущийИндекс = 0;
|
|
1118
|
+
Для Каждого ТекущееОкно Из ВсеОкна Цикл
|
|
1119
|
+
Если НайтиОбъектРекурсивно(ТекущееОкно, "", Заголовок, "ТестируемаяФорма", 0) <> Неопределено Тогда
|
|
1120
|
+
НайденноеОкно = ТекущееОкно;
|
|
1121
|
+
НайденныйИндекс = ТекущийИндекс;
|
|
1122
|
+
Прервать;
|
|
1123
|
+
КонецЕсли;
|
|
1124
|
+
ТекущийИндекс = ТекущийИндекс + 1;
|
|
1125
|
+
КонецЦикла;
|
|
1126
|
+
КонецЕсли;
|
|
1127
|
+
|
|
1106
1128
|
Если НайденноеОкно = Неопределено Тогда
|
|
1107
1129
|
Возврат Новый Структура("activated,index,count,reason", Ложь, Индекс, ВсеОкна.Количество(), "window_not_found");
|
|
1108
1130
|
КонецЕсли;
|
|
1109
1131
|
|
|
1110
|
-
|
|
1132
|
+
Попытка
|
|
1133
|
+
НайденноеОкно.Активизировать();
|
|
1134
|
+
Исключение
|
|
1135
|
+
// Окно найдено, но недоступно: его блокирует модальная или
|
|
1136
|
+
// вспомогательная форма. Это состояние интерфейса, а не сбой моста.
|
|
1137
|
+
ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
|
|
1138
|
+
АктивноеОкно = ТестируемоеПриложение.ПолучитьАктивноеОкно();
|
|
1139
|
+
Результат = Новый Структура("activated,index,count,reason,error,window,active_window", Ложь, НайденныйИндекс, ВсеОкна.Количество(), "window_unavailable", ТекстОшибки, ОбъектВСтруктуру(НайденноеОкно, 0), КраткоеОписаниеОбъектаMCP(АктивноеОкно));
|
|
1140
|
+
Возврат Результат;
|
|
1141
|
+
КонецПопытки;
|
|
1111
1142
|
Возврат Новый Структура("activated,index,count,window", Истина, НайденныйИндекс, ВсеОкна.Количество(), ОбъектВСтруктуру(НайденноеОкно, 0));
|
|
1112
1143
|
КонецФункции
|
|
1113
1144
|
|
|
@@ -1133,6 +1164,7 @@
|
|
|
1133
1164
|
|
|
1134
1165
|
&НаКлиенте
|
|
1135
1166
|
Функция ОжидатьОбъектMCP(Параметры)
|
|
1167
|
+
Перем Заголовок;
|
|
1136
1168
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
1137
1169
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
1138
1170
|
КонецЕсли;
|
|
@@ -1168,6 +1200,7 @@
|
|
|
1168
1200
|
|
|
1169
1201
|
&НаКлиенте
|
|
1170
1202
|
Функция ОжидатьФормуMCP(Параметры)
|
|
1203
|
+
Перем Заголовок;
|
|
1171
1204
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
1172
1205
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
1173
1206
|
КонецЕсли;
|
|
@@ -1180,11 +1213,13 @@
|
|
|
1180
1213
|
Если Заголовок <> "" Тогда ПараметрыПоиска.Вставить("Заголовок", Заголовок); КонецЕсли;
|
|
1181
1214
|
ПараметрыПоиска.Вставить("Тип", "ТестируемаяФорма");
|
|
1182
1215
|
|
|
1183
|
-
АктивноеОкно = ТестируемоеПриложение.ПолучитьАктивноеОкно();
|
|
1184
1216
|
Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
|
|
1185
1217
|
КонецОжидания = Начало + Таймаут * 1000;
|
|
1186
1218
|
НайденнаяФорма = Неопределено;
|
|
1187
1219
|
Пока ТекущаяУниверсальнаяДатаВМиллисекундах() <= КонецОжидания Цикл
|
|
1220
|
+
// Ожидаемая форма может открыться в новом окне: активное окно
|
|
1221
|
+
// перечитывается на каждой итерации.
|
|
1222
|
+
АктивноеОкно = ТестируемоеПриложение.ПолучитьАктивноеОкно();
|
|
1188
1223
|
НайденнаяФорма = НайтиОбъектРекурсивно(АктивноеОкно, ЛокИмяФормы, Заголовок, "ТестируемаяФорма", 0);
|
|
1189
1224
|
Если НайденнаяФорма <> Неопределено Тогда
|
|
1190
1225
|
Прервать;
|
|
@@ -1202,6 +1237,7 @@
|
|
|
1202
1237
|
|
|
1203
1238
|
&НаКлиенте
|
|
1204
1239
|
Функция ОжидатьОкноMCP(Параметры)
|
|
1240
|
+
Перем Заголовок;
|
|
1205
1241
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
1206
1242
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
1207
1243
|
КонецЕсли;
|
|
@@ -1438,6 +1474,7 @@
|
|
|
1438
1474
|
|
|
1439
1475
|
&НаКлиенте
|
|
1440
1476
|
Функция ПодсказкаПоляMCP(Параметры)
|
|
1477
|
+
Перем Подсказка;
|
|
1441
1478
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
1442
1479
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
1443
1480
|
КонецЕсли;
|
|
@@ -1825,6 +1862,7 @@
|
|
|
1825
1862
|
|
|
1826
1863
|
&НаКлиенте
|
|
1827
1864
|
Функция КоманднаяПанельОбъектаMCP(Параметры)
|
|
1865
|
+
Перем Заголовок;
|
|
1828
1866
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
1829
1867
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
1830
1868
|
КонецЕсли;
|
|
@@ -1858,6 +1896,7 @@
|
|
|
1858
1896
|
|
|
1859
1897
|
&НаКлиенте
|
|
1860
1898
|
Функция КонтекстноеМенюОбъектаMCP(Параметры)
|
|
1899
|
+
Перем Заголовок;
|
|
1861
1900
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
1862
1901
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
1863
1902
|
КонецЕсли;
|
|
@@ -2281,6 +2320,7 @@
|
|
|
2281
2320
|
|
|
2282
2321
|
&НаКлиенте
|
|
2283
2322
|
Функция КомандныйИнтерфейсСлужебныйРазделMCP(ЗаголовокРаздела)
|
|
2323
|
+
Перем Заголовок;
|
|
2284
2324
|
Заголовок = НРег(СокрЛП(ЗаголовокРаздела));
|
|
2285
2325
|
Возврат Заголовок = "помощь" Или Заголовок = "help";
|
|
2286
2326
|
КонецФункции
|
|
@@ -2330,6 +2370,7 @@
|
|
|
2330
2370
|
|
|
2331
2371
|
&НаКлиенте
|
|
2332
2372
|
Процедура КомандыКомандногоИнтерфейсаРекурсивно(Объект, Результат, Путь, ИсключатьСлужебныеПанели)
|
|
2373
|
+
Перем Заголовок;
|
|
2333
2374
|
Если Объект = Неопределено Тогда
|
|
2334
2375
|
Возврат;
|
|
2335
2376
|
КонецЕсли;
|
|
@@ -2372,6 +2413,7 @@
|
|
|
2372
2413
|
|
|
2373
2414
|
&НаКлиенте
|
|
2374
2415
|
Функция ПоискВКомандахКомандногоИнтерфейса(Команды, СтрокаПоиска, Максимум)
|
|
2416
|
+
Перем Заголовок;
|
|
2375
2417
|
Результат = Новый Массив;
|
|
2376
2418
|
КлючиСовпадений = Новый Соответствие;
|
|
2377
2419
|
НормПоиск = НРег(СтрокаПоиска);
|
|
@@ -2463,6 +2505,7 @@
|
|
|
2463
2505
|
|
|
2464
2506
|
&НаКлиенте
|
|
2465
2507
|
Функция РазвернутьГруппуMCP(Параметры)
|
|
2508
|
+
Перем Заголовок;
|
|
2466
2509
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2467
2510
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2468
2511
|
КонецЕсли;
|
|
@@ -2489,6 +2532,7 @@
|
|
|
2489
2532
|
|
|
2490
2533
|
&НаКлиенте
|
|
2491
2534
|
Функция СвернутьГруппуMCP(Параметры)
|
|
2535
|
+
Перем Заголовок;
|
|
2492
2536
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2493
2537
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2494
2538
|
КонецЕсли;
|
|
@@ -2515,6 +2559,7 @@
|
|
|
2515
2559
|
|
|
2516
2560
|
&НаКлиенте
|
|
2517
2561
|
Функция ТекущаяСтраницаГруппыMCP(Параметры)
|
|
2562
|
+
Перем Заголовок;
|
|
2518
2563
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2519
2564
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2520
2565
|
КонецЕсли;
|
|
@@ -2545,6 +2590,7 @@
|
|
|
2545
2590
|
|
|
2546
2591
|
&НаКлиенте
|
|
2547
2592
|
Функция АктивироватьСтраницуГруппыMCP(Параметры)
|
|
2593
|
+
Перем Заголовок;
|
|
2548
2594
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2549
2595
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2550
2596
|
КонецЕсли;
|
|
@@ -2574,6 +2620,7 @@
|
|
|
2574
2620
|
|
|
2575
2621
|
&НаКлиенте
|
|
2576
2622
|
Функция СостояниеГруппыMCP(Параметры)
|
|
2623
|
+
Перем Заголовок;
|
|
2577
2624
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2578
2625
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2579
2626
|
КонецЕсли;
|
|
@@ -2605,6 +2652,7 @@
|
|
|
2605
2652
|
|
|
2606
2653
|
&НаКлиенте
|
|
2607
2654
|
Функция ГиперссылкиДекорацииMCP(Параметры)
|
|
2655
|
+
Перем Заголовок;
|
|
2608
2656
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2609
2657
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2610
2658
|
КонецЕсли;
|
|
@@ -2635,6 +2683,7 @@
|
|
|
2635
2683
|
|
|
2636
2684
|
&НаКлиенте
|
|
2637
2685
|
Функция НажатьГиперссылкуДекорацииMCP(Параметры)
|
|
2686
|
+
Перем Заголовок;
|
|
2638
2687
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2639
2688
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2640
2689
|
КонецЕсли;
|
|
@@ -2662,6 +2711,7 @@
|
|
|
2662
2711
|
|
|
2663
2712
|
&НаКлиенте
|
|
2664
2713
|
Функция НажатьДекорациюMCP(Параметры)
|
|
2714
|
+
Перем Заголовок;
|
|
2665
2715
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2666
2716
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2667
2717
|
КонецЕсли;
|
|
@@ -2688,6 +2738,7 @@
|
|
|
2688
2738
|
|
|
2689
2739
|
&НаКлиенте
|
|
2690
2740
|
Функция ВыделитьВсеСтрокиТаблицыMCP(Параметры)
|
|
2741
|
+
Перем Заголовок;
|
|
2691
2742
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2692
2743
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2693
2744
|
КонецЕсли;
|
|
@@ -2714,6 +2765,7 @@
|
|
|
2714
2765
|
|
|
2715
2766
|
&НаКлиенте
|
|
2716
2767
|
Функция СнятьВыделениеТаблицыMCP(Параметры)
|
|
2768
|
+
Перем Заголовок;
|
|
2717
2769
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2718
2770
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2719
2771
|
КонецЕсли;
|
|
@@ -2740,6 +2792,7 @@
|
|
|
2740
2792
|
|
|
2741
2793
|
&НаКлиенте
|
|
2742
2794
|
Функция УдалитьСтрокуТаблицыMCP(Параметры)
|
|
2795
|
+
Перем Заголовок;
|
|
2743
2796
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2744
2797
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2745
2798
|
КонецЕсли;
|
|
@@ -2766,6 +2819,7 @@
|
|
|
2766
2819
|
|
|
2767
2820
|
&НаКлиенте
|
|
2768
2821
|
Функция СкопироватьСтрокуТаблицыMCP(Параметры)
|
|
2822
|
+
Перем Заголовок;
|
|
2769
2823
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2770
2824
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2771
2825
|
КонецЕсли;
|
|
@@ -2792,6 +2846,7 @@
|
|
|
2792
2846
|
|
|
2793
2847
|
&НаКлиенте
|
|
2794
2848
|
Функция ПереключитьПометкуУдаленияТаблицыMCP(Параметры)
|
|
2849
|
+
Перем Заголовок;
|
|
2795
2850
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2796
2851
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2797
2852
|
КонецЕсли;
|
|
@@ -2818,6 +2873,7 @@
|
|
|
2818
2873
|
|
|
2819
2874
|
&НаКлиенте
|
|
2820
2875
|
Функция НачатьРедактированиеТаблицыMCP(Параметры)
|
|
2876
|
+
Перем Заголовок;
|
|
2821
2877
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2822
2878
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2823
2879
|
КонецЕсли;
|
|
@@ -2852,6 +2908,7 @@
|
|
|
2852
2908
|
|
|
2853
2909
|
&НаКлиенте
|
|
2854
2910
|
Функция ЗакончитьРедактированиеТаблицыMCP(Параметры)
|
|
2911
|
+
Перем Заголовок;
|
|
2855
2912
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2856
2913
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2857
2914
|
КонецЕсли;
|
|
@@ -2878,6 +2935,7 @@
|
|
|
2878
2935
|
|
|
2879
2936
|
&НаКлиенте
|
|
2880
2937
|
Функция РежимРедактированияТаблицыMCP(Параметры)
|
|
2938
|
+
Перем Заголовок;
|
|
2881
2939
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
2882
2940
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
2883
2941
|
КонецЕсли;
|
|
@@ -2909,10 +2967,23 @@
|
|
|
2909
2967
|
КонецЕсли;
|
|
2910
2968
|
АктивноеОкно = ТестируемоеПриложение.ПолучитьАктивноеОкно();
|
|
2911
2969
|
Ошибки = Новый Массив;
|
|
2970
|
+
// Окно уже активно; Активизировать() нужно только чтобы поднять его.
|
|
2971
|
+
// Пока открыта модальная форма (стартовые формы БСП, предупреждения),
|
|
2972
|
+
// главное окно недоступно и платформа отвечает ошибкой
|
|
2973
|
+
// "Недоступный пользователю элемент управления не может выполнять
|
|
2974
|
+
// интерактивные действия" - это штатное состояние, а не сбой старта.
|
|
2975
|
+
Доступно = БезопасноеЗначениеМетода(АктивноеОкно, "ТекущаяДоступность", БезопасноеСвойство(АктивноеОкно, "Доступность", Истина));
|
|
2976
|
+
Если Доступно = Ложь Тогда
|
|
2977
|
+
Возврат Новый Структура("maximized,method,errors,window,skipped", Истина, "activate_only", Ошибки, КраткоеОписаниеОбъектаMCP(АктивноеОкно), "window_unavailable");
|
|
2978
|
+
КонецЕсли;
|
|
2912
2979
|
Попытка
|
|
2913
2980
|
АктивноеОкно.Активизировать();
|
|
2914
2981
|
Исключение
|
|
2915
|
-
|
|
2982
|
+
ТекстОшибки = ОписаниеОшибки();
|
|
2983
|
+
Если СтрНайти(НРег(ТекстОшибки), "недоступный пользователю элемент") > 0 Тогда
|
|
2984
|
+
Возврат Новый Структура("maximized,method,errors,window,skipped", Истина, "activate_only", Ошибки, КраткоеОписаниеОбъектаMCP(АктивноеОкно), "window_unavailable");
|
|
2985
|
+
КонецЕсли;
|
|
2986
|
+
Ошибки.Добавить("Активизировать: " + ТекстОшибки);
|
|
2916
2987
|
КонецПопытки;
|
|
2917
2988
|
Возврат Новый Структура("maximized,method,errors,window", Истина, "activate_only", Ошибки, КраткоеОписаниеОбъектаMCP(АктивноеОкно));
|
|
2918
2989
|
КонецФункции
|
|
@@ -3033,6 +3104,7 @@
|
|
|
3033
3104
|
|
|
3034
3105
|
&НаКлиенте
|
|
3035
3106
|
Процедура НайтиУзлыДереваИнтерфейсаПлоскоMCP(Объект, Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние, КешСериализации, СчетчикУзлов, Найденные, ТекущаяГлубина, ВнутриКоманднойПанели)
|
|
3107
|
+
Перем Видимость;
|
|
3036
3108
|
Если Объект = Неопределено Тогда
|
|
3037
3109
|
Возврат;
|
|
3038
3110
|
КонецЕсли;
|
|
@@ -3074,6 +3146,7 @@
|
|
|
3074
3146
|
|
|
3075
3147
|
&НаКлиенте
|
|
3076
3148
|
Функция НайтиУзлыДереваИнтерфейсаБыстроMCP(КорневойОбъект, Имя, ТипОбъектаСтрокой, ПоказыватьСкрытые, ВключатьДанные, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние, КешСериализации)
|
|
3149
|
+
Перем Видимость;
|
|
3077
3150
|
ТипОбъекта = ТипТестируемогоОбъектаMCP(ТипОбъектаСтрокой);
|
|
3078
3151
|
Если Не ПустаяСтрока(ТипОбъектаСтрокой) И ТипОбъекта = Неопределено Тогда
|
|
3079
3152
|
Возврат Неопределено;
|
|
@@ -3153,6 +3226,7 @@
|
|
|
3153
3226
|
|
|
3154
3227
|
&НаКлиенте
|
|
3155
3228
|
Функция НайтиОбъектИнтерфейса(Параметры)
|
|
3229
|
+
Перем Заголовок;
|
|
3156
3230
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
3157
3231
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
3158
3232
|
КонецЕсли;
|
|
@@ -3327,6 +3401,7 @@
|
|
|
3327
3401
|
|
|
3328
3402
|
&НаКлиенте
|
|
3329
3403
|
Функция ОбъектИнтерактивноДоступенMCP(Объект)
|
|
3404
|
+
Перем ТолькоПросмотр, Доступность, Видимость;
|
|
3330
3405
|
Видимость = БезопасноеЗначениеМетода(Объект, "ТекущаяВидимость", БезопасноеСвойство(Объект, "Видимость", Истина));
|
|
3331
3406
|
Доступность = БезопасноеЗначениеМетода(Объект, "ТекущаяДоступность", БезопасноеСвойство(Объект, "Доступность", Истина));
|
|
3332
3407
|
Если Не Видимость Или Не Доступность Тогда
|
|
@@ -3342,6 +3417,7 @@
|
|
|
3342
3417
|
|
|
3343
3418
|
&НаКлиенте
|
|
3344
3419
|
Функция ОбъектВидимИДоступенMCP(Объект)
|
|
3420
|
+
Перем Доступность, Видимость;
|
|
3345
3421
|
Видимость = БезопасноеЗначениеМетода(Объект, "ТекущаяВидимость", БезопасноеСвойство(Объект, "Видимость", Истина));
|
|
3346
3422
|
Доступность = БезопасноеЗначениеМетода(Объект, "ТекущаяДоступность", БезопасноеСвойство(Объект, "Доступность", Истина));
|
|
3347
3423
|
Возврат Видимость И Доступность;
|
|
@@ -3349,6 +3425,7 @@
|
|
|
3349
3425
|
|
|
3350
3426
|
&НаКлиенте
|
|
3351
3427
|
Функция НайтиВсеОбъектыИнтерфейса(Параметры)
|
|
3428
|
+
Перем Заголовок;
|
|
3352
3429
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
3353
3430
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
3354
3431
|
КонецЕсли;
|
|
@@ -3612,6 +3689,7 @@
|
|
|
3612
3689
|
|
|
3613
3690
|
&НаКлиенте
|
|
3614
3691
|
Функция БезопасныйЗаголовок(Объект)
|
|
3692
|
+
Перем Заголовок;
|
|
3615
3693
|
Заголовок = БезопасноеСвойство(Объект, "Заголовок", "");
|
|
3616
3694
|
Если ПустаяСтрока(Заголовок) Тогда
|
|
3617
3695
|
Заголовок = БезопасноеСвойство(Объект, "ТекстЗаголовка", "");
|
|
@@ -3735,6 +3813,7 @@
|
|
|
3735
3813
|
|
|
3736
3814
|
&НаКлиенте
|
|
3737
3815
|
Функция БезопасныйЗаголовокСКешемMCP(Объект, КешПоиска)
|
|
3816
|
+
Перем Заголовок;
|
|
3738
3817
|
Заголовок = БезопасноеСвойствоСКешемMCP(Объект, "Заголовок", "", КешПоиска);
|
|
3739
3818
|
Если ПустаяСтрока(Заголовок) Тогда
|
|
3740
3819
|
Заголовок = БезопасноеСвойствоСКешемMCP(Объект, "ТекстЗаголовка", "", КешПоиска);
|
|
@@ -3778,6 +3857,7 @@
|
|
|
3778
3857
|
|
|
3779
3858
|
&НаКлиенте
|
|
3780
3859
|
Функция ИмяТекущейСтраницыСКешемMCP(Объект, КешПоиска)
|
|
3860
|
+
Перем ТекущаяСтраница;
|
|
3781
3861
|
Если КешПоиска <> Неопределено Тогда
|
|
3782
3862
|
Запись = КешПоиска.ТекущиеСтраницы.Получить(Объект);
|
|
3783
3863
|
Если Запись <> Неопределено Тогда
|
|
@@ -3959,6 +4039,7 @@
|
|
|
3959
4039
|
|
|
3960
4040
|
&НаКлиенте
|
|
3961
4041
|
Функция ПолучитьОбъектПриложенияMCP(Параметры)
|
|
4042
|
+
Перем Заголовок;
|
|
3962
4043
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
3963
4044
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
3964
4045
|
КонецЕсли;
|
|
@@ -3984,6 +4065,7 @@
|
|
|
3984
4065
|
|
|
3985
4066
|
&НаКлиенте
|
|
3986
4067
|
Функция ФокусироватьОбъектИнтерфейса(Параметры)
|
|
4068
|
+
Перем Заголовок;
|
|
3987
4069
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
3988
4070
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
3989
4071
|
КонецЕсли;
|
|
@@ -4310,6 +4392,7 @@
|
|
|
4310
4392
|
|
|
4311
4393
|
&НаКлиенте
|
|
4312
4394
|
Функция ТекстТабличногоДокумента(Параметры)
|
|
4395
|
+
Перем Заголовок;
|
|
4313
4396
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
4314
4397
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
4315
4398
|
КонецЕсли;
|
|
@@ -4394,6 +4477,7 @@
|
|
|
4394
4477
|
|
|
4395
4478
|
&НаКлиенте
|
|
4396
4479
|
Функция ОтображениеСостоянияТабличногоДокументаMCP(Параметры)
|
|
4480
|
+
Перем Заголовок;
|
|
4397
4481
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
4398
4482
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
4399
4483
|
КонецЕсли;
|
|
@@ -4443,6 +4527,7 @@
|
|
|
4443
4527
|
|
|
4444
4528
|
&НаКлиенте
|
|
4445
4529
|
Функция СохранитьТабличныйДокументMCP(Параметры)
|
|
4530
|
+
Перем Заголовок;
|
|
4446
4531
|
Если ТестируемоеПриложение = Неопределено Тогда
|
|
4447
4532
|
ВызватьИсключение "Тестируемое приложение не подключено.";
|
|
4448
4533
|
КонецЕсли;
|
|
@@ -5710,6 +5795,7 @@
|
|
|
5710
5795
|
|
|
5711
5796
|
&НаКлиенте
|
|
5712
5797
|
Функция ПроверитьАктивностьСтраницыРодителя(Объект)
|
|
5798
|
+
Перем ТекущаяСтраница;
|
|
5713
5799
|
Страница = НайтиРодительскуюСтраницу(Объект);
|
|
5714
5800
|
Если Страница = Неопределено Тогда
|
|
5715
5801
|
Возврат Неопределено;
|
|
@@ -6295,6 +6381,7 @@
|
|
|
6295
6381
|
|
|
6296
6382
|
&НаКлиенте
|
|
6297
6383
|
Функция НайтиПолеФлажкаТекущейСтрокиТаблицыMCP(ИмяТаблицы, ИмяФлажка, ИскатьПоЗаголовку, Таблица = Неопределено)
|
|
6384
|
+
Перем ТекущийЭлемент;
|
|
6298
6385
|
Если Таблица <> Неопределено Тогда
|
|
6299
6386
|
Попытка
|
|
6300
6387
|
ТекущийЭлемент = Таблица.ПолучитьТекущийЭлемент();
|
|
@@ -6889,6 +6976,7 @@
|
|
|
6889
6976
|
|
|
6890
6977
|
&НаКлиенте
|
|
6891
6978
|
Функция НайтиВидимыйРедакторПоляНастройкиСпискаMCP(РодительскийОбъект, ИмяПоля, Глубина = 0)
|
|
6979
|
+
Перем ТолькоПросмотр;
|
|
6892
6980
|
Если РодительскийОбъект = Неопределено Или Глубина > 30 Тогда
|
|
6893
6981
|
Возврат Неопределено;
|
|
6894
6982
|
КонецЕсли;
|
|
@@ -7104,6 +7192,7 @@
|
|
|
7104
7192
|
|
|
7105
7193
|
&НаКлиенте
|
|
7106
7194
|
Функция ВидимыйРедакторТаблицыДоступенMCP(Поле)
|
|
7195
|
+
Перем ТолькоПросмотр;
|
|
7107
7196
|
Если Поле = Неопределено Тогда
|
|
7108
7197
|
Возврат Ложь;
|
|
7109
7198
|
КонецЕсли;
|
|
@@ -10060,6 +10149,7 @@
|
|
|
10060
10149
|
|
|
10061
10150
|
&НаКлиенте
|
|
10062
10151
|
Процедура ДобавитьСообщениеВалидацииЕслиЕстьMCP(Сообщения, Объект, Текст)
|
|
10152
|
+
Перем Заголовок;
|
|
10063
10153
|
Текст = СокрЛП(Строка(Текст));
|
|
10064
10154
|
Если Не ПохожеНаСообщениеВалидацииMCP(Текст) Тогда
|
|
10065
10155
|
Возврат;
|
|
@@ -10078,6 +10168,7 @@
|
|
|
10078
10168
|
|
|
10079
10169
|
&НаКлиенте
|
|
10080
10170
|
Процедура ДобавитьСообщенияВалидацииТекущегоЭлементаMCP(АктивноеОкно, Сообщения)
|
|
10171
|
+
Перем ТекущийЭлемент;
|
|
10081
10172
|
// Не сканируем все окно: ПолучитьТекстПодсказки() на большом дереве форм может быть дорогим.
|
|
10082
10173
|
// После ошибки ввода/проведения платформа обычно переводит фокус на проблемный элемент.
|
|
10083
10174
|
Попытка
|
|
@@ -10168,6 +10259,7 @@
|
|
|
10168
10259
|
|
|
10169
10260
|
&НаКлиенте
|
|
10170
10261
|
Функция ОбъектВСтруктуру(Объект, ТекущаяГлубина, ПоказыватьСкрытые = Истина, РодительскаяВидимость = Истина, РодительскаяДоступность = Истина, РодительТолькоПросмотр = Ложь, КешСериализации = Неопределено, ВключатьДанные = Истина, СериализоватьДетей = Истина, СчетчикУзлов = Неопределено, ВключатьКомандныеПанели = Истина, ВключатьСостояниеВидимости = Истина, ВключатьСостояниеДоступности = Истина, ВключатьСостояниеТолькоПросмотр = Истина, ВключатьТекущееСостояние = Истина)
|
|
10262
|
+
Перем ТолькоПросмотр, Доступность, Видимость;
|
|
10171
10263
|
Если Объект = Неопределено Тогда
|
|
10172
10264
|
Возврат Неопределено;
|
|
10173
10265
|
КонецЕсли;
|
|
Binary file
|
|
Binary file
|
|
@@ -105,16 +105,30 @@ def find_free_display(start: int = DEFAULT_DISPLAY_START) -> str:
|
|
|
105
105
|
raise RuntimeError("No free X display found")
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
def _port_bindable(port: int) -> bool:
|
|
109
|
+
"""Return True when *port* can be bound both on all interfaces and on loopback.
|
|
110
|
+
|
|
111
|
+
On Windows a wildcard bind succeeds even while another socket listens on
|
|
112
|
+
``127.0.0.1:port`` (no SO_EXCLUSIVEADDRUSE), and the later loopback bind of
|
|
113
|
+
the bridge, ibsrv or test client then fails with WinError 10048.
|
|
114
|
+
"""
|
|
115
|
+
for host in ("", "127.0.0.1"):
|
|
116
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
117
|
+
if os_support.IS_WINDOWS and hasattr(socket, "SO_EXCLUSIVEADDRUSE"):
|
|
118
|
+
s.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
|
|
119
|
+
try:
|
|
120
|
+
s.bind((host, port))
|
|
121
|
+
except OSError:
|
|
122
|
+
return False
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
|
|
108
126
|
def find_free_port(start: int = DEFAULT_WS_PORT, end: int | None = None) -> int:
|
|
109
127
|
"""Find a free TCP port in the range start..start+500 (or start..end)."""
|
|
110
128
|
end = end or start + 500
|
|
111
129
|
for port in range(start, end):
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
s.bind(("", port))
|
|
115
|
-
return port
|
|
116
|
-
except OSError:
|
|
117
|
-
continue
|
|
130
|
+
if _port_bindable(port):
|
|
131
|
+
return port
|
|
118
132
|
raise RuntimeError(f"No free port found in range {start}..{end}")
|
|
119
133
|
|
|
120
134
|
|
|
@@ -238,14 +252,33 @@ def kill_process(proc: subprocess.Popen | None, label: str) -> None:
|
|
|
238
252
|
os_support.kill_process_tree(proc, label, LOGGER)
|
|
239
253
|
|
|
240
254
|
|
|
255
|
+
def _cyrillic_score(text: str) -> int:
|
|
256
|
+
score = 0
|
|
257
|
+
for char in text:
|
|
258
|
+
if ord(char) < 128:
|
|
259
|
+
continue
|
|
260
|
+
score += 1 if ("А" <= char <= "я" or char in "Ёё") else -1
|
|
261
|
+
return score
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def decode_8bit_cyrillic(data: bytes) -> str:
|
|
265
|
+
"""Decode single-byte Russian text written in either cp866 (OEM) or cp1251 (ANSI).
|
|
266
|
+
|
|
267
|
+
Windows ibsrv/ibcmd console output is OEM (cp866), while 1C /Out logs are
|
|
268
|
+
usually ANSI. Both decodings always succeed, so pick the one that yields
|
|
269
|
+
Cyrillic letters instead of pseudographics/punctuation.
|
|
270
|
+
"""
|
|
271
|
+
candidates = [data.decode(encoding, errors="replace") for encoding in ("cp1251", "cp866")]
|
|
272
|
+
return max(candidates, key=_cyrillic_score)
|
|
273
|
+
|
|
274
|
+
|
|
241
275
|
def _decode_process_output(data: bytes) -> str:
|
|
242
|
-
|
|
243
|
-
for encoding in encodings:
|
|
276
|
+
for encoding in ("utf-8-sig", "utf-8"):
|
|
244
277
|
try:
|
|
245
278
|
return data.decode(encoding)
|
|
246
279
|
except UnicodeDecodeError:
|
|
247
280
|
continue
|
|
248
|
-
return data
|
|
281
|
+
return decode_8bit_cyrillic(data)
|
|
249
282
|
|
|
250
283
|
|
|
251
284
|
def _run_capture(argv: list[str], *, timeout: float, input_text: str | None = None) -> subprocess.CompletedProcess[str]:
|
|
@@ -397,6 +430,7 @@ def start_standalone_server(
|
|
|
397
430
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
398
431
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
399
432
|
log_path = log_dir / "ibsrv.log"
|
|
433
|
+
log_offset = log_path.stat().st_size if log_path.exists() else 0
|
|
400
434
|
with log_path.open("ab", buffering=0) as log_handle:
|
|
401
435
|
LOGGER.info("Starting ibsrv (data=%s, name=%s, address=%s, port=%s, direct_regport=%s)", data_dir, name, address, port, direct_regport or "default")
|
|
402
436
|
config_path = ensure_standalone_config(
|
|
@@ -423,16 +457,85 @@ def start_standalone_server(
|
|
|
423
457
|
stdin=subprocess.DEVNULL,
|
|
424
458
|
**os_support.subprocess_creation_kwargs(),
|
|
425
459
|
)
|
|
426
|
-
|
|
427
|
-
kill_process(proc, "ibsrv")
|
|
428
|
-
raise RuntimeError(
|
|
429
|
-
f"ibsrv did not start listening on {address}:{port} within {timeout}s. "
|
|
430
|
-
f"See log: {log_path}"
|
|
431
|
-
)
|
|
460
|
+
_wait_standalone_server_ready(proc, address, port, timeout, log_path, log_offset, db_path=db_path)
|
|
432
461
|
LOGGER.info("ibsrv is listening on %s:%s (pid=%s, name=%s)", address, port, proc.pid, name)
|
|
433
462
|
return proc
|
|
434
463
|
|
|
435
464
|
|
|
465
|
+
_IBSRV_FATAL_MARKERS = ("[fatal]", "[error]")
|
|
466
|
+
_IBSRV_READY_MARKER = "server ready"
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def read_log_since(path: Path, offset: int = 0, *, max_chars: int = 8000) -> str:
|
|
470
|
+
"""Return text appended to *path* after byte *offset* (ibsrv.log is shared across starts)."""
|
|
471
|
+
try:
|
|
472
|
+
with path.open("rb") as handle:
|
|
473
|
+
handle.seek(max(0, offset))
|
|
474
|
+
data = handle.read()
|
|
475
|
+
except OSError:
|
|
476
|
+
return ""
|
|
477
|
+
text = _decode_process_output(data).strip()
|
|
478
|
+
return text[-max_chars:]
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def _standalone_server_failure(proc: subprocess.Popen, output: str) -> str:
|
|
482
|
+
if proc.poll() is not None:
|
|
483
|
+
return f"ibsrv exited with code {proc.returncode}"
|
|
484
|
+
if any(marker in line.lower() for line in output.splitlines() for marker in _IBSRV_FATAL_MARKERS):
|
|
485
|
+
return "ibsrv reported a fatal error"
|
|
486
|
+
return ""
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _wait_standalone_server_ready(
|
|
490
|
+
proc: subprocess.Popen,
|
|
491
|
+
address: str,
|
|
492
|
+
port: int,
|
|
493
|
+
timeout: float,
|
|
494
|
+
log_path: Path,
|
|
495
|
+
log_offset: int,
|
|
496
|
+
db_path: Path | None = None,
|
|
497
|
+
ready_grace: float = 10.0,
|
|
498
|
+
poll: float = 0.25,
|
|
499
|
+
) -> None:
|
|
500
|
+
"""Wait until ibsrv serves HTTP and has opened the infobase.
|
|
501
|
+
|
|
502
|
+
ibsrv opens its HTTP port *before* it locks the infobase, so a listening
|
|
503
|
+
port alone is not enough: with a file infobase already opened elsewhere
|
|
504
|
+
(Designer, a web publication in Apache) it prints ``[FATAL] Ошибка
|
|
505
|
+
исключительной блокировки информационной базы`` and exits a moment later.
|
|
506
|
+
"""
|
|
507
|
+
def fail(reason: str) -> None:
|
|
508
|
+
kill_process(proc, "ibsrv")
|
|
509
|
+
output = read_log_since(log_path, log_offset)
|
|
510
|
+
message = f"ibsrv failed to start on {address}:{port}: {reason}. Log: {log_path}"
|
|
511
|
+
if output:
|
|
512
|
+
message += f"\n{output}"
|
|
513
|
+
if "исключительной блокировки" in output.lower() or "exclusive lock" in output.lower():
|
|
514
|
+
message += (
|
|
515
|
+
f"\nИнформационная база {db_path} уже открыта другим процессом (Конфигуратор, "
|
|
516
|
+
"клиент 1С, веб-публикация в Apache/IIS, другой ibsrv). Закройте его и повторите."
|
|
517
|
+
)
|
|
518
|
+
raise RuntimeError(message)
|
|
519
|
+
|
|
520
|
+
deadline = time.monotonic() + timeout
|
|
521
|
+
while not is_port_listening(address, port):
|
|
522
|
+
reason = _standalone_server_failure(proc, read_log_since(log_path, log_offset))
|
|
523
|
+
if reason:
|
|
524
|
+
fail(reason)
|
|
525
|
+
if time.monotonic() >= deadline:
|
|
526
|
+
fail(f"did not start listening within {timeout}s")
|
|
527
|
+
time.sleep(poll)
|
|
528
|
+
ready_deadline = time.monotonic() + ready_grace
|
|
529
|
+
while time.monotonic() < ready_deadline:
|
|
530
|
+
output = read_log_since(log_path, log_offset)
|
|
531
|
+
reason = _standalone_server_failure(proc, output)
|
|
532
|
+
if reason:
|
|
533
|
+
fail(reason)
|
|
534
|
+
if _IBSRV_READY_MARKER in output.lower():
|
|
535
|
+
return
|
|
536
|
+
time.sleep(poll)
|
|
537
|
+
|
|
538
|
+
|
|
436
539
|
def export_eventlog(
|
|
437
540
|
ibcmd: str,
|
|
438
541
|
eventlog_dir: Path,
|