1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
|
#pragma once
#include "datashard.h"
#include "datashard_impl.h"
#include <ydb/core/engine/mkql_engine_flat.h>
#include <ydb/core/kqp/kqp_compute.h>
#include <ydb/core/protos/ydb_result_set_old.pb.h>
#include <ydb/core/testlib/minikql_compile.h>
#include <ydb/core/testlib/tablet_helpers.h>
#include <ydb/core/testlib/test_client.h>
#include <library/cpp/testing/unittest/registar.h>
namespace NKikimr {
class TBalanceCoverageBuilder;
class TEngineHolder;
class TFakeMiniKQLProxy;
class TFakeProxyTx;
constexpr ui64 FAKE_SCHEMESHARD_TABLET_ID = 4200;
constexpr ui64 FAKE_TX_ALLOCATOR_TABLET_ID = 4201;
///
class TTester : public TNonCopyable {
public:
friend class TEngineHolder;
friend class TFakeMiniKQLProxy;
friend class TFakeProxyTx;
friend class TFakeScanTx;
using TKeyResolver = std::function<void(TKeyDesc&)>;
enum ESchema {
ESchema_KV,
ESchema_DoubleKV,
ESchema_MultiShardKV,
ESchema_SpecialKV,
ESchema_DoubleKVExternal,
};
///
struct TOptions {
static constexpr const ui64 DEFAULT_FIRST_STEP = 1000000;
ui64 FirstStep;
ui32 NumActiveTx;
bool SoftUpdates;
bool DelayReadSet;
bool DelayData;
bool RebootOnDelay;
std::optional<bool> Mvcc;
ui64 ExecutorCacheSize;
TOptions(ui64 firstStep = 0)
: FirstStep(firstStep ? firstStep : DEFAULT_FIRST_STEP)
, NumActiveTx(0)
, SoftUpdates(false)
, DelayReadSet(false)
, DelayData(false)
, RebootOnDelay(false)
, ExecutorCacheSize(Max<ui64>())
{}
void EnableOutOfOrder(ui32 num = 8) { NumActiveTx = num; }
void EnableSoftUpdates() { SoftUpdates = true; }
void EnableMvcc(std::optional<bool> enabled = {true}) { Mvcc = enabled; }
TString PartConfig() const {
TString pipelineConfig = Sprintf(R"(PipelineConfig {
NumActiveTx: %u
EnableOutOfOrder: %u
EnableSoftUpdates: %u
})", NumActiveTx, (NumActiveTx != 0), SoftUpdates);
TString cacheSize;
if (ExecutorCacheSize != Max<ui64>())
cacheSize = Sprintf("ExecutorCacheSize: %lu", ExecutorCacheSize);
return Sprintf(R"(PartitionConfig {
%s
%s
})", cacheSize.data(), pipelineConfig.data());
}
};
struct TActiveZone {
TTester& Tester;
TActiveZone(TTester& t): Tester(t) { if (Tester.ActiveZone) *Tester.ActiveZone = true; }
~TActiveZone() { if (Tester.ActiveZone) *Tester.ActiveZone = false; }
};
TTestBasicRuntime Runtime;
TTester(ESchema schema, const TOptions& opts = TOptions());
TTester(ESchema schema, const TString& dispatchName, std::function<void (TTestActorRuntime&)> setup,
bool& activeZone, const TOptions& opts = TOptions());
static void Setup(TTestActorRuntime& runtime, const TOptions& opts = TOptions());
private:
ESchema Schema;
TActorId Sender;
ui64 LastTxId;
ui64 LastStep;
TMockDbSchemeResolver DbSchemeResolver;
TString DispatchName = "NONE";
bool AllowIncompleteResult = false;
bool* ActiveZone = nullptr;
TDuration Timeout = TDuration::Minutes(10);
TKeyResolver GetKeyResolver() const;
void CreateSchema(ESchema schema, const TOptions& opts);
void CreateDataShard(TFakeMiniKQLProxy& proxy, ui64 tabletId, const TString& schemeText, bool withRegister = false);
void RegisterTableInResolver(const TString& schemeText);
static void EmptyShardKeyResolver(TKeyDesc& key);
static void SingleShardKeyResolver(TKeyDesc& key); // uses TTestTxConfig::TxTablet0
static void ThreeShardPointKeyResolver(TKeyDesc& key); // uses TTestTxConfig::TxTablet0,1,2
};
///
struct TExpectedReadSet {
struct TWaitFor {
ui64 Shard;
ui64 TxId;
};
ui64 SrcTablet;
ui64 DstTablet;
ui64 TxId;
TWaitFor Freedom;
TExpectedReadSet(ui64 txId, TWaitFor freedom)
: SrcTablet(TTestTxConfig::TxTablet1)
, DstTablet(TTestTxConfig::TxTablet0)
, TxId(txId)
, Freedom(freedom)
{}
};
///
class TEngineHolder {
public:
TEngineHolder()
: Env(Alloc)
{
Alloc.Release();
}
virtual ~TEngineHolder() {
Engine.Reset();
Alloc.Acquire();
}
NMiniKQL::TRuntimeNode ProgramText2Bin(TTester& tester, const TString& programText);
protected:
NMiniKQL::TScopedAlloc Alloc;
NMiniKQL::TTypeEnvironment Env;
THolder<NMiniKQL::IEngineFlat> Engine;
};
///
class TFakeProxyTx : public TEngineHolder {
public:
using IEngineFlat = NMiniKQL::IEngineFlat;
using TPtr = std::shared_ptr<TFakeProxyTx>;
TVector<ui64> Shards;
TMap<ui64, TVector<NKikimrTxDataShard::TError>> Errors;
ui64 MinStep = 0;
ui64 MaxStep = Max<ui64>();
TFakeProxyTx(ui64 txId, const TString& txBody, ui32 flags = NDataShard::TTxFlags::Default)
: TxId_(txId)
, TxKind_(NKikimrTxDataShard::TX_KIND_DATA)
, TxBody_(txBody)
, TxFlags_(flags)
, ShardsCount_(0)
{}
/// @return shards count
ui32 SetProgram(TTester& tester, const TString& programText);
virtual ui32 SetProgram(TTester& tester);
/// @return shardId
virtual ui32 GetShardProgram(ui32 idx, TString& outTxBody);
void AddProposeShardResult(ui32 shardId, const TEvDataShard::TEvProposeTransactionResult * event);
virtual void AddPlanStepShardResult(ui32 shardId, const TEvDataShard::TEvProposeTransactionResult * event, bool complete);
virtual IEngineFlat::EStatus GetStatus(bool atPropose);
virtual NKikimrMiniKQL::TResult GetResult() const;
ui64 TxId() const { return TxId_; }
ui32 TxFlags() const { return TxFlags_; }
TString TxBody() const { return TxBody_; }
NKikimrTxDataShard::ETransactionKind TxKind() { return TxKind_; }
bool IsDataTx() const { return TxKind_ == NKikimrTxDataShard::TX_KIND_DATA; }
bool IsReadTable() const { return TxKind_ == NKikimrTxDataShard::TX_KIND_SCAN; }
bool HasErrors() const { return !Errors.empty(); }
bool Immediate() const { return IsDataTx() && (ShardsCount_ < 2) && !(TxFlags_ & NDataShard::TTxFlags::ForceOnline); }
ui32 ShardsCount() const { return ShardsCount_; }
void SetKindSchema() { TxKind_ = NKikimrTxDataShard::TX_KIND_SCHEME; }
void SetKindScan() { TxKind_ = NKikimrTxDataShard::TX_KIND_SCAN; }
TBalanceCoverageBuilder * GetCoverageBuilder(ui64 shard);
void SetCheck(std::function<bool(TFakeProxyTx&)> check) {
Check = check;
}
bool CheckResult() {
if (Check)
return Check(*this);
return true;
}
protected:
ui64 TxId_;
NKikimrTxDataShard::ETransactionKind TxKind_;
TString TxBody_;
ui32 TxFlags_;
ui32 ShardsCount_;
THashMap<ui64, std::shared_ptr<TBalanceCoverageBuilder>> CoverageBuilders; // key - shard
std::function<bool(TFakeProxyTx&)> Check;
};
///
class TFakeScanTx : public TFakeProxyTx {
public:
TFakeScanTx(ui64 txId, const TString& txBody, ui32 flags = NDataShard::TTxFlags::Default)
: TFakeProxyTx(txId, txBody, flags)
, Status(IEngineFlat::EStatus::Unknown)
{
SetKindScan();
}
ui32 SetProgram(TTester& tester) override;
ui32 GetShardProgram(ui32 idx, TString& outTxBody) override;
void AddPlanStepShardResult(ui32 shardId, const TEvDataShard::TEvProposeTransactionResult * event, bool complete) override;
YdbOld::ResultSet GetScanResult() const;
IEngineFlat::EStatus GetStatus(bool atPropose) override;
private:
YdbOld::ResultSet Result;
IEngineFlat::EStatus Status;
};
///
class TFakeMiniKQLProxy {
public:
using IEngineFlat = NMiniKQL::IEngineFlat;
//using TEvProgressTransaction = NDataShard::TDataShard::TEvPrivate::TEvProgressTransaction;
TFakeMiniKQLProxy(TTester& tester)
: Tester(tester)
, LastTxId_(tester.LastTxId)
, LastStep_(tester.LastStep)
, RebootOnDelay(false)
{}
// Propose + Plan (if needed) in own step
IEngineFlat::EStatus ExecSchemeCreateTable(const TString& schemaText, const TVector<ui64>& shards);
IEngineFlat::EStatus Execute(const TString& programText, NKikimrMiniKQL::TResult& out,
bool waitForResult = true);
IEngineFlat::EStatus Execute(const TString& programText, bool waitForResult = true) {
NKikimrMiniKQL::TResult result;
return Execute(programText, result, waitForResult);
}
void CheckedExecute(const TString& programText) {
try {
UNIT_ASSERT_EQUAL(Execute(programText), IEngineFlat::EStatus::Complete);
} catch (TEmptyEventQueueException&) {
Cout << "Event queue is empty at dispatch " << Tester.DispatchName << "\n";
if (!Tester.AllowIncompleteResult)
throw;
}
}
void Enqueue(const TString& programText, std::function<bool(TFakeProxyTx&)> check = DoNothing,
ui32 flags = NDataShard::TTxFlags::ForceOnline);
void EnqueueScan(const TString& programText, std::function<bool(TFakeProxyTx&)> check = DoNothing,
ui32 flags = NDataShard::TTxFlags::ForceOnline);
void ExecQueue();
static bool DoNothing(TFakeProxyTx&) {
return true;
}
ui64 LastTxId() const { return LastTxId_; }
void DelayReadSet(const TExpectedReadSet& rs, bool withReboot = false) {
DelayedReadSets.emplace_back(rs);
RebootOnDelay = withReboot;
}
void DelayData(const TExpectedReadSet::TWaitFor& shardTx) {
DelayedData.emplace_back(shardTx);
}
private:
TTester& Tester;
ui64& LastTxId_;
ui64& LastStep_;
TVector<TFakeProxyTx::TPtr> TxQueue;
TMap<ui64, TActorId> ShardActors;
//
TVector<TExpectedReadSet> DelayedReadSets;
TVector<TExpectedReadSet::TWaitFor> DelayedData;
bool RebootOnDelay;
void Propose(TFakeProxyTx& tx, bool holdImmediate = false);
void ProposeSchemeCreateTable(TFakeProxyTx& tx, const TVector<ui64>& shards);
void ProposeScheme(TFakeProxyTx& tx, const TVector<ui64>& shards,
const std::function<NKikimrTxDataShard::TFlatSchemeTransaction(ui64)>& txBodyForShard);
ui64 Plan(ui64 stepId, const TMap<ui64, TFakeProxyTx::TPtr>& txs, bool waitForResult = true);
void ResolveShards(const TSet<ui64>& shards);
};
///
class TDatashardInitialEventsFilter {
public:
TDatashardInitialEventsFilter(const TVector<ui64>& tabletIds);
TTestActorRuntime::TEventFilter Prepare();
bool operator()(TTestActorRuntimeBase& runtime, TAutoPtr<IEventHandle>& event);
TDatashardInitialEventsFilter(const TDatashardInitialEventsFilter&) = delete;
TDatashardInitialEventsFilter(TDatashardInitialEventsFilter&&) = default;
static TDatashardInitialEventsFilter CreateDefault() {
TVector<ui64> tabletIds;
tabletIds.push_back((ui64)TTestTxConfig::TxTablet0);
tabletIds.push_back((ui64)TTestTxConfig::TxTablet1);
tabletIds.push_back((ui64)TTestTxConfig::TxTablet2);
return TDatashardInitialEventsFilter(tabletIds);
}
const TVector<ui64> Tablets() const { return TabletIds; }
private:
const TVector<ui64> TabletIds;
TVector<ui64> RemainTablets;
};
///
class TKeyExtractor : public TEngineHolder {
public:
using TPKey = THolder<TKeyDesc>;
TKeyExtractor(TTester& tester, TString programText);
const TVector<TPKey>& GetKeys() const { return Engine->GetDbKeys(); }
};
THolder<NKqp::TEvKqp::TEvQueryRequest> MakeSQLRequest(const TString &sql,
bool dml = true);
void InitRoot(Tests::TServer::TPtr server,
TActorId sender);
enum class EShadowDataMode {
Default,
Enabled,
};
struct TShardedTableOptions {
using TSelf = TShardedTableOptions;
struct TColumn {
TColumn(const TString& name, const TString& type, bool isKey, bool notNull)
: Name(name)
, Type(type)
, IsKey(isKey)
, NotNull(notNull) {}
TString Name;
TString Type;
bool IsKey;
bool NotNull;
};
struct TIndex {
using EType = NKikimrSchemeOp::EIndexType;
TString Name;
TVector<TString> IndexColumns;
TVector<TString> DataColumns = {};
EType Type = EType::EIndexTypeGlobal;
};
struct TCdcStream {
using EMode = NKikimrSchemeOp::ECdcStreamMode;
TString Name;
EMode Mode;
};
#define TABLE_OPTION_IMPL(type, name, defaultValue) \
TSelf& name(type value) {\
name##_ = std::move(value); \
return *this; \
} \
type name##_ = defaultValue
#define TABLE_OPTION(type, name, defaultValue) TABLE_OPTION_IMPL(type, name, defaultValue)
TABLE_OPTION(ui64, Shards, 1);
TABLE_OPTION(bool, EnableOutOfOrder, true);
TABLE_OPTION(const NLocalDb::TCompactionPolicy*, Policy, nullptr);
TABLE_OPTION(EShadowDataMode, ShadowData, EShadowDataMode::Default);
TABLE_OPTION(TVector<TColumn>, Columns, (TVector<TColumn>{{"key", "Uint32", true, false}, {"value", "Uint32", false, false}}));
TABLE_OPTION(TVector<TIndex>, Indexes, {});
TABLE_OPTION(ui64, Followers, 0);
TABLE_OPTION(bool, FollowerPromotion, false);
TABLE_OPTION(bool, ExternalStorage, false);
#undef TABLE_OPTION
#undef TABLE_OPTION_IMPL
};
#define Y_UNIT_TEST_WITH_MVCC_IMPL(N, OPT) \
template<bool OPT> void N(NUnitTest::TTestContext&); \
struct TTestRegistration##N { \
TTestRegistration##N() { \
TCurrentTest::AddTest(#N, static_cast<void (*)(NUnitTest::TTestContext&)>(&N<false>), false); \
TCurrentTest::AddTest("Mvcc" #N, static_cast<void (*)(NUnitTest::TTestContext&)>(&N<true>), false); \
} \
}; \
static TTestRegistration##N testRegistration##N; \
template<bool OPT> \
void N(NUnitTest::TTestContext&)
#define Y_UNIT_TEST_WITH_MVCC(N) Y_UNIT_TEST_WITH_MVCC_IMPL(N, UseMvcc)
#define WithMvcc UseMvcc
#define Y_UNIT_TEST_QUAD(N, OPT1, OPT2) \
template<bool OPT1, bool OPT2> void N(NUnitTest::TTestContext&); \
struct TTestRegistration##N { \
TTestRegistration##N() { \
TCurrentTest::AddTest(#N "-" #OPT1 "-" #OPT2, static_cast<void (*)(NUnitTest::TTestContext&)>(&N<false, false>), false); \
TCurrentTest::AddTest(#N "+" #OPT1 "-" #OPT2, static_cast<void (*)(NUnitTest::TTestContext&)>(&N<true, false>), false); \
TCurrentTest::AddTest(#N "-" #OPT1 "+" #OPT2, static_cast<void (*)(NUnitTest::TTestContext&)>(&N<false, true>), false); \
TCurrentTest::AddTest(#N "+" #OPT1 "+" #OPT2, static_cast<void (*)(NUnitTest::TTestContext&)>(&N<true, true>), false); \
} \
}; \
static TTestRegistration##N testRegistration##N; \
template<bool OPT1, bool OPT2> \
void N(NUnitTest::TTestContext&)
void CreateShardedTable(Tests::TServer::TPtr server,
TActorId sender,
const TString &root,
const TString &name,
const TShardedTableOptions &opts = TShardedTableOptions());
void CreateShardedTable(Tests::TServer::TPtr server,
TActorId sender,
const TString &root,
const TString &name,
ui64 shards,
bool enableOutOfOrder = true,
const NLocalDb::TCompactionPolicy* policy = nullptr,
EShadowDataMode shadowData = EShadowDataMode::Default);
TVector<ui64> GetTableShards(Tests::TServer::TPtr server,
TActorId sender,
const TString &path);
using TTableInfoMap = THashMap<TString, NKikimrTxDataShard::TEvGetInfoResponse::TUserTable>;
std::pair<TTableInfoMap, ui64> GetTables(Tests::TServer::TPtr server,
ui64 tabletId);
TTableId ResolveTableId(
Tests::TServer::TPtr server,
TActorId sender,
const TString& path);
NTable::TRowVersionRanges GetRemovedRowVersions(
Tests::TServer::TPtr server,
ui64 shardId);
TRowVersion CreateVolatileSnapshot(
Tests::TServer::TPtr server,
const TVector<TString>& tables,
TDuration timeout = TDuration::Seconds(5));
bool RefreshVolatileSnapshot(
Tests::TServer::TPtr server,
const TVector<TString>& tables,
TRowVersion snapshot);
bool DiscardVolatileSnapshot(
Tests::TServer::TPtr server,
const TVector<TString>& tables,
TRowVersion snapshot);
struct TChange {
i64 Offset;
ui64 WriteTxId;
ui32 Key;
ui32 Value;
};
void ApplyChanges(
const Tests::TServer::TPtr& server,
ui64 shardId,
const TTableId& tableId,
const TString& sourceId,
const TVector<TChange>& changes,
NKikimrTxDataShard::TEvApplyReplicationChangesResult::EStatus expected =
NKikimrTxDataShard::TEvApplyReplicationChangesResult::STATUS_OK);
TRowVersion CommitWrites(
Tests::TServer::TPtr server,
const TVector<TString>& tables,
ui64 writeTxId);
ui64 AsyncSplitTable(
Tests::TServer::TPtr server,
TActorId sender,
const TString& path,
ui64 sourceTablet,
ui32 splitKey);
ui64 AsyncMergeTable(
Tests::TServer::TPtr server,
TActorId sender,
const TString& path,
const TVector<ui64>& sourceTabletIds);
ui64 AsyncAlterAddExtraColumn(
Tests::TServer::TPtr server,
const TString& workingDir,
const TString& name);
ui64 AsyncAlterDropColumn(
Tests::TServer::TPtr server,
const TString& workingDir,
const TString& name,
const TString& colName);
ui64 AsyncAlterAndDisableShadow(
Tests::TServer::TPtr server,
const TString& workingDir,
const TString& name,
const NLocalDb::TCompactionPolicy* policy = nullptr);
ui64 AsyncAlterAddIndex(
Tests::TServer::TPtr server,
const TString& dbName,
const TString& tablePath,
const TShardedTableOptions::TIndex& indexDesc);
ui64 AsyncAlterDropIndex(
Tests::TServer::TPtr server,
const TString& workingDir,
const TString& tableName,
const TString& indexName);
ui64 AsyncAlterAddStream(
Tests::TServer::TPtr server,
const TString& workingDir,
const TString& tableName,
const TShardedTableOptions::TCdcStream& streamDesc);
ui64 AsyncAlterDropStream(
Tests::TServer::TPtr server,
const TString& workingDir,
const TString& tableName,
const TString& streamName);
struct TReadShardedTableState {
TActorId Sender;
TActorId Worker;
TString Result;
};
TReadShardedTableState StartReadShardedTable(
Tests::TServer::TPtr server,
const TString& path,
TRowVersion snapshot = TRowVersion::Max(),
bool pause = true,
bool ordered = true);
void ResumeReadShardedTable(
Tests::TServer::TPtr server,
TReadShardedTableState& state);
TString ReadShardedTable(
Tests::TServer::TPtr server,
const TString& path,
TRowVersion snapshot = TRowVersion::Max());
void WaitTxNotification(Tests::TServer::TPtr server, TActorId sender, ui64 txId);
void WaitTxNotification(Tests::TServer::TPtr server, ui64 txId);
void SimulateSleep(Tests::TServer::TPtr server, TDuration duration);
void SendSQL(Tests::TServer::TPtr server,
TActorId sender,
const TString &sql,
bool dml = true);
void ExecSQL(Tests::TServer::TPtr server,
TActorId sender,
const TString &sql,
bool dml = true,
Ydb::StatusIds::StatusCode code = Ydb::StatusIds::SUCCESS);
struct IsTxResultComplete {
bool operator()(IEventHandle& ev)
{
if (ev.GetTypeRewrite() == TEvDataShard::EvProposeTransactionResult) {
auto status = ev.Get<TEvDataShard::TEvProposeTransactionResult>()->GetStatus();
if (status == NKikimrTxDataShard::TEvProposeTransactionResult::COMPLETE)
return true;
}
return false;
}
};
void WaitTabletBecomesOffline(Tests::TServer::TPtr server, ui64 tabletId);
}
|