Skip to content

Commit de827c6

Browse files
authored
Improve search isolate testing by loading an actual index. (#8821)
1 parent 2aadb0a commit de827c6

File tree

5 files changed

+104
-38
lines changed

5 files changed

+104
-38
lines changed

app/bin/tools/search_benchmark.dart

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,14 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:convert';
6-
import 'dart:io';
7-
85
import 'package:_pub_shared/search/search_form.dart';
9-
import 'package:pub_dev/package/overrides.dart';
10-
import 'package:pub_dev/search/mem_index.dart';
11-
import 'package:pub_dev/search/models.dart';
126
import 'package:pub_dev/search/search_service.dart';
7+
import 'package:pub_dev/search/updater.dart';
138

149
/// Loads a search snapshot and executes queries on it, benchmarking their total time to complete.
1510
Future<void> main(List<String> args) async {
1611
// Assumes that the first argument is a search snapshot file.
17-
final file = File(args.first);
18-
final content =
19-
json.decode(utf8.decode(gzip.decode(await file.readAsBytes())))
20-
as Map<String, Object?>;
21-
final snapshot = SearchSnapshot.fromJson(content);
22-
final index = InMemoryPackageIndex(
23-
documents:
24-
snapshot.documents!.values.where((d) => !isSdkPackage(d.package)));
12+
final index = await loadInMemoryPackageIndexFromFile(args.first);
2513

2614
// NOTE: please add more queries to this list, especially if there is a performance bottleneck.
2715
final queries = [

app/lib/search/updater.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:convert';
7+
import 'dart:io';
68

79
import 'package:gcloud/service_scope.dart' as ss;
810
import 'package:logging/logging.dart';
911
import 'package:meta/meta.dart';
12+
import 'package:pub_dev/package/overrides.dart';
13+
import 'package:pub_dev/search/models.dart';
1014
import 'package:pub_dev/search/search_service.dart';
1115

1216
import '../package/models.dart' show Package;
@@ -24,6 +28,31 @@ void registerIndexUpdater(IndexUpdater updater) =>
2428
/// The active index updater.
2529
IndexUpdater get indexUpdater => ss.lookup(#_indexUpdater) as IndexUpdater;
2630

31+
/// Loads a local search snapshot file and builds an in-memory package index from it.
32+
Future<InMemoryPackageIndex> loadInMemoryPackageIndexFromFile(
33+
String path) async {
34+
final file = File(path);
35+
final content =
36+
json.decode(utf8.decode(gzip.decode(await file.readAsBytes())))
37+
as Map<String, Object?>;
38+
final snapshot = SearchSnapshot.fromJson(content);
39+
return InMemoryPackageIndex(
40+
documents:
41+
snapshot.documents!.values.where((d) => !isSdkPackage(d.package)));
42+
}
43+
44+
/// Saves the provided [documents] into a local search snapshot file.
45+
Future<void> saveInMemoryPackageIndexToFile(
46+
Iterable<PackageDocument> documents, String path) async {
47+
final file = File(path);
48+
final snapshot = SearchSnapshot();
49+
for (final doc in documents) {
50+
snapshot.add(doc);
51+
}
52+
await file
53+
.writeAsBytes(gzip.encode(utf8.encode(json.encode(snapshot.toJson()))));
54+
}
55+
2756
class IndexUpdater {
2857
final DatastoreDB _db;
2958

app/lib/service/entrypoint/_isolate.dart

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class IsolateRunner {
3333
final ServicesWrapperFn? servicesWrapperFn;
3434
final EntryPointFn? entryPoint;
3535
final Uri? spawnUri;
36+
final List<String>? spawnArgs;
3637

3738
int started = 0;
3839
final _isolates = <_Isolate>[];
@@ -43,14 +44,17 @@ class IsolateRunner {
4344
required this.kind,
4445
required ServicesWrapperFn this.servicesWrapperFn,
4546
required EntryPointFn this.entryPoint,
46-
}) : spawnUri = null;
47+
}) : spawnUri = null,
48+
spawnArgs = null;
4749

4850
IsolateRunner.uri({
4951
required this.logger,
5052
required this.kind,
5153
required Uri this.spawnUri,
54+
List<String>? spawnArgs,
5255
}) : entryPoint = null,
53-
servicesWrapperFn = null;
56+
servicesWrapperFn = null,
57+
spawnArgs = spawnArgs ?? <String>[];
5458

5559
/// Starts [count] new isolates.
5660
Future<void> start(int count) async {
@@ -138,7 +142,7 @@ class IsolateRunner {
138142
entryPoint: entryPoint!,
139143
);
140144
} else {
141-
await isolate.spawnUri(spawnUri: spawnUri!);
145+
await isolate.spawnUri(spawnUri: spawnUri!, args: spawnArgs);
142146
}
143147
if (_closing) {
144148
await isolate.close();
@@ -176,12 +180,14 @@ Future<IsolateRunner> startWorkerIsolate({
176180
Future<IsolateRunner> startQueryIsolate({
177181
required Logger logger,
178182
required Uri spawnUri,
183+
List<String>? spawnArgs,
179184
required String kind,
180185
}) async {
181186
final worker = IsolateRunner.uri(
182187
logger: logger,
183188
kind: kind,
184189
spawnUri: spawnUri,
190+
spawnArgs: spawnArgs ?? [],
185191
);
186192
await worker.start(1);
187193
return worker;
@@ -242,10 +248,11 @@ class _Isolate {
242248

243249
Future<void> spawnUri({
244250
required Uri spawnUri,
251+
required List<String>? args,
245252
}) async {
246253
_isolate = await Isolate.spawnUri(
247254
spawnUri,
248-
[],
255+
args ?? [],
249256
EntryMessage(
250257
protocolSendPort: _protocolReceivePort.sendPort,
251258
).encodeAsJson(),

app/lib/service/entrypoint/search_index.dart

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:async';
66
import 'dart:convert';
77

8+
import 'package:args/args.dart';
89
import 'package:gcloud/service_scope.dart';
910
import 'package:logging/logging.dart';
1011
import 'package:pub_dev/search/backend.dart';
@@ -20,10 +21,20 @@ import 'package:pub_dev/shared/utils.dart';
2021

2122
final _logger = Logger('search_index');
2223

24+
final _argParser = ArgParser()
25+
..addOption(
26+
'snapshot',
27+
help:
28+
'If specified, the snapshot will be loaded from the local path instead of cloud storage.',
29+
);
30+
2331
/// Entry point for the search index isolate.
2432
Future<void> main(List<String> args, var message) async {
2533
final timer = Timer.periodic(Duration(milliseconds: 250), (_) {});
2634

35+
final argv = _argParser.parse(args);
36+
final snapshot = argv['snapshot'] as String?;
37+
2738
final ServicesWrapperFn servicesWrapperFn;
2839
if (envConfig.isRunningInAppengine) {
2940
servicesWrapperFn = withServices;
@@ -34,7 +45,11 @@ Future<void> main(List<String> args, var message) async {
3445
}
3546
await fork(() async {
3647
await servicesWrapperFn(() async {
37-
await indexUpdater.init();
48+
if (snapshot == null) {
49+
await indexUpdater.init();
50+
} else {
51+
updatePackageIndex(await loadInMemoryPackageIndexFromFile(snapshot));
52+
}
3853

3954
await runIsolateFunctions(
4055
message: message,

app/test/service/entrypoint/search_index_test.dart

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,66 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:logging/logging.dart';
6+
import 'package:path/path.dart' as p;
67
import 'package:pub_dev/search/search_service.dart';
8+
import 'package:pub_dev/search/updater.dart';
79
import 'package:pub_dev/service/entrypoint/_isolate.dart';
810
import 'package:pub_dev/service/entrypoint/search_index.dart';
11+
import 'package:pub_dev/shared/utils.dart';
912
import 'package:test/test.dart';
1013

1114
final _logger = Logger('search_index_test');
1215

1316
void main() {
1417
group('Search index inside an isolate', () {
15-
final indexRunner = IsolateRunner.uri(
16-
kind: 'index',
17-
logger: _logger,
18-
spawnUri:
19-
Uri.parse('package:pub_dev/service/entrypoint/search_index.dart'),
20-
);
21-
22-
tearDownAll(() async {
18+
late IsolateRunner indexRunner;
19+
20+
tearDown(() async {
2321
await indexRunner.close();
2422
});
2523

26-
test('start and work with index', () async {
27-
await indexRunner.start(1);
24+
test('start and work with local index', () async {
25+
await withTempDirectory((tempDir) async {
26+
final snapshotPath = p.join(tempDir.path, 'index.json.gz');
27+
await saveInMemoryPackageIndexToFile(
28+
[
29+
PackageDocument(
30+
package: 'json_annotation',
31+
description: 'Annotation metadata for JSON serialization.',
32+
),
33+
],
34+
snapshotPath,
35+
);
36+
37+
indexRunner = IsolateRunner.uri(
38+
kind: 'index',
39+
logger: _logger,
40+
spawnUri:
41+
Uri.parse('package:pub_dev/service/entrypoint/search_index.dart'),
42+
spawnArgs: ['--snapshot', snapshotPath],
43+
);
44+
45+
await indexRunner.start(1);
2846

29-
// index calling the sendport
30-
final searchIndex = IsolateSearchIndex(indexRunner);
31-
expect(await searchIndex.isReady(), true);
47+
// index calling the sendport
48+
final searchIndex = IsolateSearchIndex(indexRunner);
49+
expect(await searchIndex.isReady(), true);
3250

33-
// working search only with SDK results (no packages in the isolate)
34-
final rs =
35-
await searchIndex.search(ServiceSearchQuery.parse(query: 'json'));
36-
expect(rs.errorMessage, isNull);
37-
expect(rs.sdkLibraryHits, isEmpty);
38-
expect(rs.packageHits, isEmpty);
51+
// returns package hit
52+
final rs =
53+
await searchIndex.search(ServiceSearchQuery.parse(query: 'json'));
54+
expect(rs.toJson(), {
55+
'timestamp': isNotEmpty,
56+
'totalCount': 1,
57+
'sdkLibraryHits': [],
58+
'packageHits': [
59+
{
60+
'package': 'json_annotation',
61+
'score': greaterThan(0.5),
62+
},
63+
],
64+
});
65+
});
3966
}, timeout: Timeout(Duration(minutes: 5)));
4067
});
4168
}

0 commit comments

Comments
 (0)