Skip to content

Commit 2a5636c

Browse files
Copilotnixel2007
andcommitted
Add comprehensive multi-threading tests demonstrating context-based transaction isolation
Co-authored-by: nixel2007 <[email protected]>
1 parent bfe9266 commit 2a5636c

File tree

1 file changed

+327
-0
lines changed

1 file changed

+327
-0
lines changed
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
// BSLLS:MagicNumber-off
2+
// BSLLS:LatinAndCyrillicSymbolInWord-off
3+
// BSLLS:DuplicateStringLiteral-off
4+
// BSLLS:LineLength-off
5+
6+
#Использовать ".."
7+
#Использовать "utils"
8+
9+
Перем МенеджерСущностей;
10+
11+
Процедура ПередЗапускомТеста() Экспорт
12+
13+
ЗапускатьТестыPostgres = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_POSTGRES_TESTS", "true");
14+
ЗапускатьТестыSQLite = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_SQLITE_TESTS", "true");
15+
16+
ВыполнятьСбросТаблиц = Ложь;
17+
Если ЗапускатьТестыSQLite = "true" Тогда
18+
СтрокаСоединения = "FullUri=file::memory:?cache=shared";
19+
//СтрокаСоединения = "Data Source=test.db";
20+
ТипКоннектора = Тип("КоннекторSQLite");
21+
ИначеЕсли ЗапускатьТестыPostgres = "true" Тогда
22+
Хост = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_HOST", "localhost");
23+
Порт = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PORT", "5432");
24+
Пользователь = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_USERNAME", "postgres");
25+
Пароль = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PASSWORD", "postgres");
26+
ИмяБД = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_DATABASE", "postgres");
27+
СтрокаСоединения = СтрШаблон(
28+
"Host=%1;Username=%2;Password=%3;Database=%4;port=%5;",
29+
Хост,
30+
Пользователь,
31+
Пароль,
32+
ИмяБД,
33+
Порт
34+
);
35+
ТипКоннектора = Тип("КоннекторPostgreSQL");
36+
ВыполнятьСбросТаблиц = Истина;
37+
Иначе
38+
ВызватьИсключение "Нет доступного коннектора для тестирования менеджера сущностей";
39+
КонецЕсли;
40+
41+
МенеджерСущностей = Новый МенеджерСущностей(ТипКоннектора, СтрокаСоединения);
42+
43+
Если ВыполнятьСбросТаблиц Тогда
44+
Коннектор = МенеджерСущностей.ПолучитьКоннектор();
45+
Коннектор.Открыть(СтрокаСоединения, Новый Массив);
46+
ТестовыеУтилиты.УдалитьТаблицыВБазеДанных(Коннектор);
47+
Коннектор.Закрыть();
48+
КонецЕсли;
49+
50+
ПодключитьСценарий(
51+
ОбъединитьПути(
52+
ТекущийКаталог(),
53+
"tests",
54+
"fixtures",
55+
"Автор.os"
56+
),
57+
"Автор"
58+
);
59+
ПодключитьСценарий(
60+
ОбъединитьПути(
61+
ТекущийКаталог(),
62+
"tests",
63+
"fixtures",
64+
"СущностьБезГенерируемогоИдентификатора.os"
65+
),
66+
"СущностьБезГенерируемогоИдентификатора"
67+
);
68+
ПодключитьСценарий(
69+
ОбъединитьПути(
70+
ТекущийКаталог(),
71+
"tests",
72+
"fixtures",
73+
"СущностьСоВсемиТипамиКолонок.os"
74+
),
75+
"СущностьСоВсемиТипамиКолонок"
76+
);
77+
78+
МенеджерСущностей.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора"));
79+
МенеджерСущностей.ДобавитьКлассВМодель(Тип("Автор"));
80+
МенеджерСущностей.ДобавитьКлассВМодель(Тип("СущностьСоВсемиТипамиКолонок"));
81+
82+
МенеджерСущностей.Инициализировать();
83+
84+
КонецПроцедуры
85+
86+
Процедура ПослеЗапускаТеста() Экспорт
87+
МенеджерСущностей.Закрыть();
88+
МенеджерСущностей = Неопределено;
89+
КонецПроцедуры
90+
91+
&Тест
92+
Процедура НезависимыеТранзакцииВРазныхКонтекстах() Экспорт
93+
// Проверяем, что транзакции в разных контекстах не влияют друг на друга
94+
95+
// Контекст 1 - имитирует основной поток
96+
КонтекстID1 = МенеджерСущностей.НачатьТранзакцию();
97+
Ожидаем.Что(КонтекстID1, "Контекст 1 должен быть создан").Не_().Равно(Неопределено);
98+
99+
// Сохраняем сущность в контексте 1
100+
Автор1 = Новый Автор;
101+
Автор1.Имя = "Первый";
102+
Автор1.ВтороеИмя = "Автор";
103+
МенеджерСущностей.Сохранить(Автор1, КонтекстID1);
104+
105+
// Контекст 2 - имитирует фоновое задание
106+
КонтекстID2 = МенеджерСущностей.НачатьТранзакцию();
107+
Ожидаем.Что(КонтекстID2, "Контекст 2 должен быть создан").Не_().Равно(Неопределено);
108+
Ожидаем.Что(КонтекстID2, "Контексты должны быть разными").Не_().Равно(КонтекстID1);
109+
110+
// Сохраняем другую сущность в контексте 2
111+
Автор2 = Новый Автор;
112+
Автор2.Имя = "Второй";
113+
Автор2.ВтороеИмя = "Автор";
114+
МенеджерСущностей.Сохранить(Автор2, КонтекстID2);
115+
116+
// Откатываем только контекст 2 (имитируем ошибку в фоновом задании)
117+
МенеджерСущностей.ОтменитьТранзакцию(КонтекстID2);
118+
119+
// Фиксируем контекст 1 (основной поток завершается успешно)
120+
МенеджерСущностей.ЗафиксироватьТранзакцию(КонтекстID1);
121+
122+
// Проверяем результат - должен остаться только автор из контекста 1
123+
Результат = МенеджерСущностей.ПолучитьКоннектор().ВыполнитьЗапрос("SELECT * FROM Авторы");
124+
Ожидаем.Что(Результат, "Должен остаться только один автор").ИмеетДлину(1);
125+
Ожидаем.Что(Результат[0].Имя, "Должен остаться автор из первого контекста").Равно("Первый");
126+
КонецПроцедуры
127+
128+
&Тест
129+
Процедура МногократныеНезависимыеТранзакции() Экспорт
130+
// Проверяем работу нескольких независимых транзакций подряд
131+
132+
МассивКонтекстов = Новый Массив;
133+
134+
// Создаем несколько контекстов (имитируем множественные фоновые задания)
135+
Для Индекс = 1 По 3 Цикл
136+
КонтекстID = МенеджерСущностей.НачатьТранзакцию();
137+
МассивКонтекстов.Добавить(КонтекстID);
138+
139+
// В каждом контексте сохраняем автора
140+
Автор = Новый Автор;
141+
Автор.Имя = "Автор" + Индекс;
142+
Автор.ВтороеИмя = "Контекст" + Индекс;
143+
МенеджерСущностей.Сохранить(Автор, КонтекстID);
144+
КонецЦикла;
145+
146+
// Фиксируем все транзакции
147+
Для Каждого КонтекстID Из МассивКонтекстов Цикл
148+
МенеджерСущностей.ЗафиксироватьТранзакцию(КонтекстID);
149+
КонецЦикла;
150+
151+
// Проверяем, что все авторы сохранились
152+
Результат = МенеджерСущностей.ПолучитьКоннектор().ВыполнитьЗапрос("SELECT * FROM Авторы ORDER BY Имя");
153+
Ожидаем.Что(Результат, "Должны сохраниться все три автора").ИмеетДлину(3);
154+
155+
Для Индекс = 0 По 2 Цикл
156+
ОжидаемоеИмя = "Автор" + (Индекс + 1);
157+
Ожидаем.Что(Результат[Индекс].Имя, "Автор должен быть сохранен корректно").Равно(ОжидаемоеИмя);
158+
КонецЦикла;
159+
КонецПроцедуры
160+
161+
&Тест
162+
Процедура ОбратнаяСовместимостьБезКонтекстов() Экспорт
163+
// Проверяем, что старый API без указания контекстов продолжает работать
164+
165+
МенеджерСущностей.НачатьТранзакцию(); // Без возврата КонтекстID
166+
167+
Автор = Новый Автор;
168+
Автор.Имя = "Старый";
169+
Автор.ВтороеИмя = "API";
170+
МенеджерСущностей.Сохранить(Автор); // Без указания контекста
171+
172+
МенеджерСущностей.ЗафиксироватьТранзакцию(); // Без указания контекста
173+
174+
// Проверяем результат
175+
Результат = МенеджерСущностей.ПолучитьКоннектор().ВыполнитьЗапрос("SELECT * FROM Авторы");
176+
Ожидаем.Что(Результат, "Автор должен быть сохранен").ИмеетДлину(1);
177+
Ожидаем.Что(Результат[0].Имя, "Имя автора должно быть корректным").Равно("Старый");
178+
КонецПроцедуры
179+
180+
&Тест
181+
Процедура СмешанноеИспользованиеКонтекстовИБезКонтекстов() Экспорт
182+
// Проверяем совместимость нового и старого API
183+
184+
// Новый API с контекстом
185+
КонтекстID = МенеджерСущностей.НачатьТранзакцию();
186+
187+
Автор1 = Новый Автор;
188+
Автор1.Имя = "Новый";
189+
Автор1.ВтороеИмя = "API";
190+
МенеджерСущностей.Сохранить(Автор1, КонтекстID);
191+
192+
// Старый API без контекста (параллельно)
193+
МенеджерСущностей.НачатьТранзакцию();
194+
195+
Автор2 = Новый Автор;
196+
Автор2.Имя = "Старый";
197+
Автор2.ВтороеИмя = "API";
198+
МенеджерСущностей.Сохранить(Автор2);
199+
200+
// Фиксируем обе транзакции
201+
МенеджерСущностей.ЗафиксироватьТранзакцию(КонтекстID);
202+
МенеджерСущностей.ЗафиксироватьТранзакцию();
203+
204+
// Проверяем результат
205+
Результат = МенеджерСущностей.ПолучитьКоннектор().ВыполнитьЗапрос("SELECT * FROM Авторы ORDER BY Имя");
206+
Ожидаем.Что(Результат, "Должны быть сохранены оба автора").ИмеетДлину(2);
207+
Ожидаем.Что(Результат[0].Имя, "Первый автор").Равно("Новый");
208+
Ожидаем.Что(Результат[1].Имя, "Второй автор").Равно("Старый");
209+
КонецПроцедуры
210+
211+
&Тест
212+
Процедура СимуляцияФоновогоЗаданияСОшибкой() Экспорт
213+
// Имитируем ситуацию, когда фоновое задание завершается с ошибкой
214+
215+
// Основной поток начинает транзакцию
216+
ОсновнойКонтекстID = МенеджерСущностей.НачатьТранзакцию();
217+
218+
Автор1 = Новый Автор;
219+
Автор1.Имя = "Основной";
220+
Автор1.ВтороеИмя = "Поток";
221+
МенеджерСущностей.Сохранить(Автор1, ОсновнойКонтекстID);
222+
223+
// Фоновое задание начинает свою транзакцию
224+
ФоновыйКонтекстID = МенеджерСущностей.НачатьТранзакцию();
225+
226+
Автор2 = Новый Автор;
227+
Автор2.Имя = "Фоновое";
228+
Автор2.ВтороеИмя = "Задание";
229+
МенеджерСущностей.Сохранить(Автор2, ФоновыйКонтекстID);
230+
231+
// Основной поток завершается успешно
232+
МенеджерСущностей.ЗафиксироватьТранзакцию(ОсновнойКонтекстID);
233+
234+
// Фоновое задание завершается с ошибкой (откат)
235+
МенеджерСущностей.ОтменитьТранзакцию(ФоновыйКонтекстID);
236+
237+
// Проверяем, что сохранился только автор из основного потока
238+
Результат = МенеджерСущностей.ПолучитьКоннектор().ВыполнитьЗапрос("SELECT * FROM Авторы");
239+
Ожидаем.Что(Результат, "Должен остаться только автор из основного потока").ИмеетДлину(1);
240+
Ожидаем.Что(Результат[0].Имя, "Должен остаться правильный автор").Равно("Основной");
241+
КонецПроцедуры
242+
243+
// Тест для демонстрации реальной работы с ФоновыеЗадания API
244+
// (будет работать только при наличии поддержки фоновых заданий)
245+
&Тест
246+
Процедура ДемонстрацияФоновыхЗаданий() Экспорт
247+
// Проверяем доступность API фоновых заданий
248+
249+
ДоступныФоновыеЗадания = Ложь;
250+
Попытка
251+
Если ТипЗнч(ФоновыеЗадания) = Тип("МенеджерФоновыхЗаданий") Тогда
252+
ДоступныФоновыеЗадания = Истина;
253+
КонецЕсли;
254+
Исключение
255+
// API фоновых заданий недоступен
256+
КонецПопытки;
257+
258+
Если НЕ ДоступныФоновыеЗадания Тогда
259+
Сообщить("API фоновых заданий недоступен. Пропускаем тест реальных фоновых заданий.");
260+
Возврат;
261+
КонецЕсли;
262+
263+
// Начинаем транзакцию в основном потоке
264+
ОсновнойКонтекстID = МенеджерСущностей.НачатьТранзакцию();
265+
266+
Автор1 = Новый Автор;
267+
Автор1.Имя = "Основной";
268+
Автор1.ВтороеИмя = "Поток";
269+
МенеджерСущностей.Сохранить(Автор1, ОсновнойКонтекстID);
270+
271+
// Запускаем реальное фоновое задание
272+
ПараметрыЗадания = Новый Структура;
273+
ПараметрыЗадания.Вставить("МенеджерСущностей", МенеджерСущностей);
274+
275+
Попытка
276+
ФоновоеЗадание = ФоновыеЗадания.Выполнить("ФоновоеЗаданиеСозданияАвтора", ПараметрыЗадания);
277+
278+
// Ждем завершения фонового задания (максимум 5 секунд)
279+
СчетчикОжидания = 0;
280+
Пока ФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.Активно И СчетчикОжидания < 50 Цикл
281+
Приостановить(100); // 100 мс
282+
СчетчикОжидания = СчетчикОжидания + 1;
283+
КонецЦикла;
284+
285+
// Фиксируем транзакцию основного потока
286+
МенеджерСущностей.ЗафиксироватьТранзакцию(ОсновнойКонтекстID);
287+
288+
// Проверяем результат
289+
Результат = МенеджерСущностей.ПолучитьКоннектор().ВыполнитьЗапрос("SELECT * FROM Авторы ORDER BY Имя");
290+
291+
Если ФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
292+
Ожидаем.Что(Результат, "Должны быть сохранены оба автора").ИмеетДлину(2);
293+
Ожидаем.Что(Результат[0].Имя, "Первый автор").Равно("Основной");
294+
Ожидаем.Что(Результат[1].Имя, "Второй автор").Равно("Фоновое");
295+
Иначе
296+
Сообщить("Фоновое задание не завершилось в ожидаемое время. Состояние: " + ФоновоеЗадание.Состояние);
297+
Ожидаем.Что(Результат, "Должен остаться только автор из основного потока").ИмеетДлину(1);
298+
КонецЕсли;
299+
300+
Исключение
301+
Сообщить("Ошибка при запуске фонового задания: " + ОписаниеОшибки());
302+
// Все равно фиксируем основной поток
303+
МенеджерСущностей.ЗафиксироватьТранзакцию(ОсновнойКонтекстID);
304+
КонецПопытки;
305+
КонецПроцедуры
306+
307+
// Процедура для выполнения в фоновом задании
308+
Процедура ФоновоеЗаданиеСозданияАвтора(ПараметрыЗадания) Экспорт
309+
МенеджерСущностей = ПараметрыЗадания.МенеджерСущностей;
310+
311+
// Начинаем транзакцию в фоновом задании
312+
КонтекстID = МенеджерСущностей.НачатьТранзакцию();
313+
314+
Попытка
315+
Автор = Новый Автор;
316+
Автор.Имя = "Фоновое";
317+
Автор.ВтороеИмя = "Задание";
318+
МенеджерСущностей.Сохранить(Автор, КонтекстID);
319+
320+
// Фиксируем транзакцию
321+
МенеджерСущностей.ЗафиксироватьТранзакцию(КонтекстID);
322+
Исключение
323+
// В случае ошибки откатываем транзакцию
324+
МенеджерСущностей.ОтменитьТранзакцию(КонтекстID);
325+
ВызватьИсключение;
326+
КонецПопытки;
327+
КонецПроцедуры

0 commit comments

Comments
 (0)