@githolon/dsl 0.117.0 → 0.119.9
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.
- package/assets/operation-contract.wasm +0 -0
- package/compile_proof.mjs +16 -0
- package/dart/CHANGELOG.md +45 -0
- package/dart/lib/src/application_status.dart +58 -2
- package/dart/lib/src/peer_application_streams.dart +7 -1
- package/dart/lib/src/reception/dart_reception.dart +92 -12
- package/dart/lib/src/reception/reception_session.dart +10 -2
- package/dart/lib/src/replica_readiness.dart +200 -50
- package/dart/lib/src/runner/node-entry.mjs +2480 -1021
- package/dart/lib/src/sync_status_projection.dart +346 -4
- package/dart/lib/src/sync_status_reducer.dart +44 -0
- package/dart/lib/src/test_harness.dart +1 -0
- package/dart/lib/src/typed_watch_registry.dart +67 -7
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/copy_repair_status_test.dart +238 -0
- package/dart/test/reads_never_wait_test.dart +387 -0
- package/dart/test/replica_readiness_test.dart +223 -109
- package/dart/test/sync_status_reducer_test.dart +39 -0
- package/dart/test/typed_watch_registry_test.dart +69 -0
- package/dart/test/unshared_work_status_test.dart +269 -0
- package/dart_types/CHANGELOG.md +18 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +320 -85
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +57 -1
- package/dart_types/lib/src/proto/nomos/runtime/v2/maintenance_desk.pb.dart +14 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pbenum.dart +25 -1
- package/dart_types/lib/src/runtime_outcome.dart +17 -0
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +6 -2
- package/src/codegen_ts.ts +7 -8
- package/src/compile_package_main.ts +11 -4
- package/src/reception_template.ts +4 -3
- package/src/reception_unit_artifact.ts +105 -0
- package/src/reception_unit_artifact_contract.ts +220 -0
- package/src/reception_unit_context.ts +47 -0
- package/src/reception_unit_source.ts +205 -0
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
//
|
|
2
|
-
import 'dart:async';
|
|
1
|
+
// Reads answer at once from what is on this device; nothing waits for the network.
|
|
3
2
|
import 'dart:io';
|
|
3
|
+
import 'dart:typed_data';
|
|
4
4
|
|
|
5
5
|
import 'package:nomos_client/src/replica_readiness.dart';
|
|
6
|
+
import 'package:nomos_client/src/result_usda.dart' show NmsUsdaResult;
|
|
7
|
+
import 'package:nomos_client/src/sync_status_projection.dart'
|
|
8
|
+
show LocalCopyReadiness;
|
|
6
9
|
import 'package:nomos_types/nomos_types.dart'
|
|
7
10
|
show NomosUnavailable, NomosUnavailableReason;
|
|
11
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
|
|
12
|
+
import 'package:nomos_types/src/proto/google/protobuf/any.pb.dart'
|
|
13
|
+
as google_protobuf;
|
|
8
14
|
import 'package:test/test.dart';
|
|
9
15
|
|
|
10
16
|
NomosUnavailable _unavailable(
|
|
@@ -15,135 +21,245 @@ NomosUnavailable _unavailable(
|
|
|
15
21
|
reason: reason,
|
|
16
22
|
workspace: 'catalogue-cars',
|
|
17
23
|
retryable: retryable,
|
|
24
|
+
// A long hint: if anything still slept on it, the test would time out.
|
|
25
|
+
retryAfter: const Duration(minutes: 1),
|
|
18
26
|
);
|
|
19
27
|
|
|
20
28
|
void main() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
group('a one-shot read answers at once', () {
|
|
30
|
+
for (final (reason, readiness) in [
|
|
31
|
+
(
|
|
32
|
+
NomosUnavailableReason.localReplicaNotReady,
|
|
33
|
+
LocalCopyReadiness.arriving,
|
|
34
|
+
),
|
|
35
|
+
(NomosUnavailableReason.workspaceOpening, LocalCopyReadiness.arriving),
|
|
36
|
+
(NomosUnavailableReason.readIndexRefreshing, LocalCopyReadiness.updating),
|
|
37
|
+
]) {
|
|
38
|
+
test(
|
|
39
|
+
'${reason.name}: the empty answer and ${readiness.name}, asked once',
|
|
40
|
+
() async {
|
|
41
|
+
var reads = 0;
|
|
42
|
+
final reported = <LocalCopyReadiness>[];
|
|
43
|
+
final clock = Stopwatch()..start();
|
|
44
|
+
final rows = await nmsReadNow<List<String>>(
|
|
45
|
+
() async {
|
|
46
|
+
reads++;
|
|
47
|
+
throw _unavailable(reason);
|
|
48
|
+
},
|
|
49
|
+
pending: (_) => const [],
|
|
50
|
+
report: reported.add,
|
|
51
|
+
);
|
|
52
|
+
clock.stop();
|
|
53
|
+
expect(rows, isEmpty);
|
|
54
|
+
expect(reads, 1, reason: 'no retry');
|
|
55
|
+
expect(reported, [readiness]);
|
|
56
|
+
expect(clock.elapsedMilliseconds, lessThan(50));
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
}
|
|
41
60
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
(
|
|
49
|
-
'a remote-side unavailable answer',
|
|
50
|
-
_unavailable(NomosUnavailableReason.remoteUnreachable),
|
|
51
|
-
),
|
|
52
|
-
('any other error', StateError('NMS-KERNEL-CALL: query failed')),
|
|
53
|
-
]) {
|
|
54
|
-
test('$label is reported at once', () async {
|
|
55
|
-
var reads = 0;
|
|
56
|
-
await expectLater(
|
|
57
|
-
nmsUntilReplicaReady<void>(() async {
|
|
58
|
-
reads++;
|
|
59
|
-
throw error;
|
|
60
|
-
}),
|
|
61
|
-
throwsA(same(error)),
|
|
61
|
+
test('a read that answers reports the copy ready', () async {
|
|
62
|
+
final reported = <LocalCopyReadiness>[];
|
|
63
|
+
final rows = await nmsReadNow<List<String>>(
|
|
64
|
+
() async => ['Hatchback'],
|
|
65
|
+
pending: (_) => const [],
|
|
66
|
+
report: reported.add,
|
|
62
67
|
);
|
|
63
|
-
expect(
|
|
68
|
+
expect(rows, ['Hatchback']);
|
|
69
|
+
expect(reported, [LocalCopyReadiness.ready]);
|
|
64
70
|
});
|
|
65
|
-
}
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
test(
|
|
73
|
+
'a read with no empty form reports the typed answer at once',
|
|
74
|
+
() async {
|
|
75
|
+
var reads = 0;
|
|
76
|
+
final reported = <LocalCopyReadiness>[];
|
|
77
|
+
final notReady = _unavailable(
|
|
78
|
+
NomosUnavailableReason.localReplicaNotReady,
|
|
79
|
+
);
|
|
80
|
+
await expectLater(
|
|
81
|
+
nmsReadNow<List<String>>(
|
|
82
|
+
() async {
|
|
83
|
+
reads++;
|
|
84
|
+
throw notReady;
|
|
85
|
+
},
|
|
86
|
+
pending: (_) => null,
|
|
87
|
+
report: reported.add,
|
|
88
|
+
),
|
|
89
|
+
throwsA(same(notReady)),
|
|
90
|
+
);
|
|
91
|
+
expect(reads, 1);
|
|
92
|
+
expect(reported, [LocalCopyReadiness.arriving]);
|
|
93
|
+
},
|
|
75
94
|
);
|
|
95
|
+
|
|
96
|
+
for (final (label, error) in [
|
|
97
|
+
(
|
|
98
|
+
'a non-retryable unavailable answer',
|
|
99
|
+
_unavailable(
|
|
100
|
+
NomosUnavailableReason.localReplicaNotReady,
|
|
101
|
+
retryable: false,
|
|
102
|
+
),
|
|
103
|
+
),
|
|
104
|
+
(
|
|
105
|
+
'a remote-side unavailable answer',
|
|
106
|
+
_unavailable(NomosUnavailableReason.remoteUnreachable),
|
|
107
|
+
),
|
|
108
|
+
('any other error', StateError('NMS-KERNEL-CALL: query failed')),
|
|
109
|
+
]) {
|
|
110
|
+
test('$label is reported at once and is not "pending"', () async {
|
|
111
|
+
var reads = 0;
|
|
112
|
+
final reported = <LocalCopyReadiness>[];
|
|
113
|
+
await expectLater(
|
|
114
|
+
nmsReadNow<void>(
|
|
115
|
+
() async {
|
|
116
|
+
reads++;
|
|
117
|
+
throw error;
|
|
118
|
+
},
|
|
119
|
+
pending: (_) => fail('not a pending copy'),
|
|
120
|
+
report: reported.add,
|
|
121
|
+
),
|
|
122
|
+
throwsA(same(error)),
|
|
123
|
+
);
|
|
124
|
+
expect(reads, 1);
|
|
125
|
+
expect(reported, isEmpty);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
76
128
|
});
|
|
77
129
|
|
|
78
|
-
group('a
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
130
|
+
group('the empty answer of a copy not readable here yet', () {
|
|
131
|
+
test(
|
|
132
|
+
'a declared rows query answers its own generated result type with no rows',
|
|
133
|
+
() {
|
|
134
|
+
final empty = nmsPendingQueryResponse(
|
|
135
|
+
protocol.QueryRequest(
|
|
136
|
+
resultFormat: protocol
|
|
137
|
+
.QueryResultFormat.QUERY_RESULT_FORMAT_APPLICATION_PROTOBUF,
|
|
138
|
+
rows: protocol.RowsQuery(
|
|
139
|
+
id: 'listingsByCatalogueId',
|
|
140
|
+
applicationArguments: google_protobuf.Any(
|
|
141
|
+
typeUrl:
|
|
142
|
+
'type.nomos.dev/nomos.operation.v1.Query_3407d8ac4c444f26',
|
|
143
|
+
value: [10, 1, 120],
|
|
144
|
+
),
|
|
145
|
+
),
|
|
146
|
+
),
|
|
147
|
+
)!;
|
|
148
|
+
expect(
|
|
149
|
+
empty.applicationResult.typeUrl,
|
|
150
|
+
'type.nomos.dev/nomos.operation.v1.QueryResult_3407d8ac4c444f26',
|
|
85
151
|
);
|
|
152
|
+
expect(empty.applicationResult.value, isEmpty);
|
|
153
|
+
},
|
|
154
|
+
);
|
|
86
155
|
|
|
87
|
-
test('
|
|
88
|
-
final
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
expect(reads, 1, reason: 'another workspace syncing is not this copy');
|
|
102
|
-
synced.add('estate-a');
|
|
156
|
+
test('a page of the same query is empty too', () {
|
|
157
|
+
final empty = nmsPendingQueryResponse(
|
|
158
|
+
protocol.QueryRequest(
|
|
159
|
+
resultFormat: protocol
|
|
160
|
+
.QueryResultFormat.QUERY_RESULT_FORMAT_APPLICATION_PROTOBUF,
|
|
161
|
+
page: protocol.PageQuery(
|
|
162
|
+
id: 'listingsByCatalogueId',
|
|
163
|
+
limit: 100,
|
|
164
|
+
applicationArguments: google_protobuf.Any(
|
|
165
|
+
typeUrl: 'type.nomos.dev/nomos.operation.v1.Query_abc',
|
|
166
|
+
),
|
|
167
|
+
),
|
|
168
|
+
),
|
|
169
|
+
)!;
|
|
103
170
|
expect(
|
|
104
|
-
|
|
105
|
-
|
|
171
|
+
empty.applicationResult.typeUrl,
|
|
172
|
+
'type.nomos.dev/nomos.operation.v1.QueryResult_abc',
|
|
106
173
|
);
|
|
107
|
-
expect(reads, 2);
|
|
108
|
-
await synced.close();
|
|
109
174
|
});
|
|
110
175
|
|
|
111
|
-
test(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
176
|
+
test(
|
|
177
|
+
'nominal by-id, by-type and spatial reads answer a result with no rows',
|
|
178
|
+
() {
|
|
179
|
+
for (final query in [
|
|
180
|
+
protocol.QueryRequest(
|
|
181
|
+
byId: protocol.ByIdQuery(aggregateType: 'a', aggregateId: 'b'),
|
|
182
|
+
),
|
|
183
|
+
protocol.QueryRequest(
|
|
184
|
+
byType: protocol.ByTypeQuery(aggregateType: 'a'),
|
|
185
|
+
),
|
|
186
|
+
protocol.QueryRequest(spatial: protocol.SpatialQuery()),
|
|
187
|
+
]) {
|
|
188
|
+
query.resultFormat =
|
|
189
|
+
protocol.QueryResultFormat.QUERY_RESULT_FORMAT_NOMINAL_USDA;
|
|
190
|
+
final empty = nmsPendingQueryResponse(query)!;
|
|
191
|
+
final parsed = NmsUsdaResult.parse(
|
|
192
|
+
Uint8List.fromList(empty.resultUsda),
|
|
193
|
+
);
|
|
194
|
+
expect(parsed.rows, isEmpty, reason: query.whichQuery().name);
|
|
195
|
+
expect(parsed.done, isTrue);
|
|
120
196
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
test('a count and a sum answer an exact zero', () {
|
|
201
|
+
for (final query in [
|
|
202
|
+
protocol.QueryRequest(count: protocol.TallyQuery(id: 'assets')),
|
|
203
|
+
protocol.QueryRequest(sum: protocol.TallyQuery(id: 'wattage')),
|
|
204
|
+
]) {
|
|
205
|
+
final empty = nmsPendingQueryResponse(query)!;
|
|
206
|
+
expect(empty.hasIntegerValue(), isTrue);
|
|
207
|
+
expect(empty.integerValue.toInt(), 0);
|
|
208
|
+
final changed = nmsPendingWatchChanged(query, 'estate:watch:1')!;
|
|
209
|
+
expect(changed.hasIntegerValue(), isTrue);
|
|
210
|
+
expect(changed.subscriptionId, 'estate:watch:1');
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('a framework read has no empty form', () {
|
|
215
|
+
expect(
|
|
216
|
+
nmsPendingQueryResponse(
|
|
217
|
+
protocol.QueryRequest(
|
|
218
|
+
authenticationPosture: protocol.AuthenticationPostureQuery(),
|
|
219
|
+
),
|
|
220
|
+
),
|
|
221
|
+
isNull,
|
|
222
|
+
);
|
|
223
|
+
expect(
|
|
224
|
+
nmsPendingQueryResponse(
|
|
225
|
+
protocol.QueryRequest(
|
|
226
|
+
resultFormat: protocol
|
|
227
|
+
.QueryResultFormat.QUERY_RESULT_FORMAT_APPLICATION_PROTOBUF,
|
|
228
|
+
rows: protocol.RowsQuery(
|
|
229
|
+
applicationArguments: google_protobuf.Any(
|
|
230
|
+
typeUrl: 'type.nomos.dev/untyped',
|
|
231
|
+
),
|
|
232
|
+
),
|
|
233
|
+
),
|
|
234
|
+
),
|
|
235
|
+
isNull,
|
|
236
|
+
reason: 'only a generated Query_<sid> names its result type',
|
|
237
|
+
);
|
|
126
238
|
});
|
|
127
239
|
});
|
|
128
240
|
|
|
129
|
-
// The guard: every one-shot read reaches the kernel through one of these two
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
241
|
+
// The guard: every one-shot read reaches the kernel through one of these two request
|
|
242
|
+
// shapes. Each must sit inside nmsReadNow, so a new read path can neither wait for a copy
|
|
243
|
+
// nor hand callers a "not ready yet" as an error. A site is where the request is BUILT (its
|
|
244
|
+
// requestId, or the historic request), never a trace line that merely names the same logical
|
|
245
|
+
// id (CAP-741's `call.send`), and "inside" means the read opens between the site's enclosing
|
|
246
|
+
// member and the site, however much tracing the closure carries.
|
|
247
|
+
test('every kernel query request is built inside nmsReadNow', () {
|
|
248
|
+
final source =
|
|
249
|
+
File('lib/src/reception/dart_reception.dart').readAsLinesSync();
|
|
250
|
+
expect(
|
|
251
|
+
source.any((line) => line.contains('nmsUntilReplicaReady')),
|
|
252
|
+
isFalse,
|
|
253
|
+
reason: 'reads never wait for a copy to arrive',
|
|
254
|
+
);
|
|
140
255
|
final member = RegExp(r'^ [A-Za-z_]');
|
|
141
256
|
final requestSites = <int>[];
|
|
142
257
|
for (var i = 0; i < source.length; i++) {
|
|
143
258
|
final line = source[i];
|
|
144
259
|
if ((line.contains('requestId:') && line.contains(r":query:$")) ||
|
|
145
260
|
line.contains(
|
|
146
|
-
|
|
261
|
+
'historicQuery: nomos_protocol.HistoricQueryRequest(',
|
|
262
|
+
)) {
|
|
147
263
|
requestSites.add(i);
|
|
148
264
|
}
|
|
149
265
|
}
|
|
@@ -154,12 +270,10 @@ void main() {
|
|
|
154
270
|
start--;
|
|
155
271
|
}
|
|
156
272
|
expect(
|
|
157
|
-
source
|
|
158
|
-
.sublist(start, site)
|
|
159
|
-
.any((line) => line.contains('nmsUntilReplicaReady(')),
|
|
273
|
+
source.sublist(start, site).any((line) => line.contains('nmsReadNow')),
|
|
160
274
|
isTrue,
|
|
161
275
|
reason: 'dart_reception.dart:${site + 1} sends a kernel query outside '
|
|
162
|
-
'
|
|
276
|
+
'nmsReadNow',
|
|
163
277
|
);
|
|
164
278
|
}
|
|
165
279
|
});
|
|
@@ -358,6 +358,45 @@ void main() {
|
|
|
358
358
|
reason: 'a completed round never forgets the link state');
|
|
359
359
|
});
|
|
360
360
|
|
|
361
|
+
test(
|
|
362
|
+
'while every workspace stays live, re-folding emits no new application status (CAP-930)',
|
|
363
|
+
() async {
|
|
364
|
+
final projection = NmsSyncStatusProjection();
|
|
365
|
+
addTearDown(projection.close);
|
|
366
|
+
final settled = reduce(protocol.SyncWorkspaceResponse(
|
|
367
|
+
previousHead: 'head', currentHead: 'head'));
|
|
368
|
+
final emitted = <SyncStatus>[];
|
|
369
|
+
final subscription = projection.applicationStream.listen(emitted.add);
|
|
370
|
+
addTearDown(subscription.cancel);
|
|
371
|
+
projection.pushSession('estate', settled.copyWith(live: true));
|
|
372
|
+
projection.pushSession('home', settled.copyWith(live: true));
|
|
373
|
+
projection.foldApplication(['estate', 'home']);
|
|
374
|
+
await Future<void>.delayed(Duration.zero);
|
|
375
|
+
expect(projection.applicationStatus.live, isTrue);
|
|
376
|
+
final becameLive = projection.applicationStatus.lastSyncedAt;
|
|
377
|
+
final before = emitted.length;
|
|
378
|
+
// Every incoming change re-reports the workspaces and re-folds the summary.
|
|
379
|
+
for (var i = 0; i < 20; i++) {
|
|
380
|
+
await Future<void>.delayed(const Duration(microseconds: 50));
|
|
381
|
+
projection.pushSession('estate', settled.copyWith(live: true));
|
|
382
|
+
projection.foldApplication(['estate', 'home']);
|
|
383
|
+
}
|
|
384
|
+
await Future<void>.delayed(Duration.zero);
|
|
385
|
+
expect(emitted.length, before,
|
|
386
|
+
reason: 'an unchanged live status is not a new status');
|
|
387
|
+
expect(projection.applicationStatus.lastSyncedAt, becameLive);
|
|
388
|
+
// Losing the link and regaining it is a new "current now".
|
|
389
|
+
projection.pushSession('estate', settled);
|
|
390
|
+
projection.foldApplication(['estate', 'home']);
|
|
391
|
+
expect(projection.applicationStatus.live, isFalse);
|
|
392
|
+
await Future<void>.delayed(const Duration(milliseconds: 2));
|
|
393
|
+
projection.pushSession('estate', settled.copyWith(live: true));
|
|
394
|
+
projection.foldApplication(['estate', 'home']);
|
|
395
|
+
expect(projection.applicationStatus.live, isTrue);
|
|
396
|
+
expect(projection.applicationStatus.lastSyncedAt!.isAfter(becameLive!),
|
|
397
|
+
isTrue);
|
|
398
|
+
});
|
|
399
|
+
|
|
361
400
|
test(
|
|
362
401
|
'the runtime follow inventory survives every re-fold of the application status',
|
|
363
402
|
() async {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import 'dart:async';
|
|
2
|
+
import 'package:nomos_client/src/sync_status_projection.dart'
|
|
3
|
+
show LocalCopyReadiness;
|
|
2
4
|
import 'package:nomos_client/src/typed_watch_registry.dart';
|
|
3
5
|
import 'package:nomos_types/nomos_kernel_protocol.dart' as protocol;
|
|
4
6
|
import 'package:nomos_types/nomos_types.dart'
|
|
@@ -246,4 +248,71 @@ void main() {
|
|
|
246
248
|
);
|
|
247
249
|
await expectLater(watch.stream.first, throwsA(isA<NomosUnavailable>()));
|
|
248
250
|
});
|
|
251
|
+
|
|
252
|
+
test(
|
|
253
|
+
'a copy not here yet: the empty answer at once, the copy reported arriving, never a wait',
|
|
254
|
+
() async {
|
|
255
|
+
final registry = NmsTypedWatchRegistry();
|
|
256
|
+
final reported = <LocalCopyReadiness>[];
|
|
257
|
+
final watch = registry.open(
|
|
258
|
+
session: 'catalogue',
|
|
259
|
+
decode: (event) => event.resultUsda,
|
|
260
|
+
pending: (id) => protocol.WatchChanged(subscriptionId: id),
|
|
261
|
+
report: reported.add,
|
|
262
|
+
start: (_) async => throw const NomosUnavailable(
|
|
263
|
+
reason: NomosUnavailableReason.localReplicaNotReady,
|
|
264
|
+
workspace: 'catalogue',
|
|
265
|
+
retryable: true,
|
|
266
|
+
retryAfter: Duration(minutes: 1),
|
|
267
|
+
),
|
|
268
|
+
stop: (_) async {},
|
|
269
|
+
);
|
|
270
|
+
final clock = Stopwatch()..start();
|
|
271
|
+
expect(await watch.stream.first, isEmpty);
|
|
272
|
+
expect(clock.elapsedMilliseconds, lessThan(50));
|
|
273
|
+
expect(reported, [LocalCopyReadiness.arriving]);
|
|
274
|
+
await registry.close();
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
test(
|
|
279
|
+
'a registration the peer finished after answering "not ready" ends the re-asks',
|
|
280
|
+
() async {
|
|
281
|
+
final registry = NmsTypedWatchRegistry();
|
|
282
|
+
var starts = 0;
|
|
283
|
+
final reported = <LocalCopyReadiness>[];
|
|
284
|
+
late final String subscription;
|
|
285
|
+
final watch = registry.open(
|
|
286
|
+
session: 'catalogue',
|
|
287
|
+
decode: (event) => event.resultUsda,
|
|
288
|
+
pending: (id) => protocol.WatchChanged(subscriptionId: id),
|
|
289
|
+
report: reported.add,
|
|
290
|
+
start: (id) async {
|
|
291
|
+
subscription = id;
|
|
292
|
+
starts++;
|
|
293
|
+
throw const NomosUnavailable(
|
|
294
|
+
reason: NomosUnavailableReason.localReplicaNotReady,
|
|
295
|
+
workspace: 'catalogue',
|
|
296
|
+
retryable: true,
|
|
297
|
+
retryAfter: Duration(milliseconds: 20),
|
|
298
|
+
);
|
|
299
|
+
},
|
|
300
|
+
stop: (_) async {},
|
|
301
|
+
);
|
|
302
|
+
final values = StreamIterator(watch.stream);
|
|
303
|
+
expect(await values.moveNext(), isTrue);
|
|
304
|
+
expect(values.current, isEmpty);
|
|
305
|
+
// The peer completed the registration in the background and delivers its first result.
|
|
306
|
+
registry.deliver(
|
|
307
|
+
protocol.WatchChanged(subscriptionId: subscription, resultUsda: [7]),
|
|
308
|
+
);
|
|
309
|
+
expect(await values.moveNext(), isTrue);
|
|
310
|
+
expect(values.current, [7]);
|
|
311
|
+
await Future<void>.delayed(const Duration(milliseconds: 100));
|
|
312
|
+
expect(starts, 1, reason: 'a delivering watch is registered');
|
|
313
|
+
expect(reported, [LocalCopyReadiness.arriving, LocalCopyReadiness.ready]);
|
|
314
|
+
await values.cancel();
|
|
315
|
+
await registry.close();
|
|
316
|
+
},
|
|
317
|
+
);
|
|
249
318
|
}
|