Skip to content

Commit

Permalink
Minimal integration test for search completition. (#8034)
Browse files Browse the repository at this point in the history
  • Loading branch information
isoos authored Sep 12, 2024
1 parent 8d438cb commit ae925a8
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 46 deletions.
57 changes: 54 additions & 3 deletions app/lib/fake/backend/fake_pub_worker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import 'dart:math';
import 'package:_pub_shared/dartdoc/dartdoc_page.dart';
import 'package:_pub_shared/data/package_api.dart';
import 'package:_pub_shared/data/task_payload.dart';
import 'package:_pub_shared/worker/docker_utils.dart';
import 'package:clock/clock.dart';
import 'package:gcloud/service_scope.dart';
import 'package:http/http.dart';
import 'package:http_parser/http_parser.dart';
import 'package:indexed_blob/indexed_blob.dart';
Expand All @@ -18,6 +20,7 @@ import 'package:pana/pana.dart';
import 'package:path/path.dart' as p;
import 'package:pub_dev/fake/backend/fake_pana_runner.dart';
import 'package:pub_dev/frontend/handlers/pubapi.client.dart';
import 'package:pub_dev/frontend/static_files.dart';
import 'package:pub_dev/scorecard/backend.dart';
import 'package:pub_dev/service/async_queue/async_queue.dart';
import 'package:pub_dev/shared/utils.dart';
Expand Down Expand Up @@ -65,13 +68,31 @@ Future<void> processTasksLocallyWithPubWorker() async {
]);
}

/// Process analysis tasks locally, using either:
/// - `fake`: the semi-randomized fake analysis that is fast
/// - `local`: running pkg/pub_worker using the same context as the app
/// - `worker`: running pkg/pub_worker using the dockerized container
Future<void> processTaskFakeLocalOrWorker(String analysis) async {
if (analysis == 'none') {
return;
} else if (analysis == 'local') {
await _analyzeLocal();
} else if (analysis == 'worker') {
await _analyzeWorker();
} else if (analysis == 'fake') {
await processTasksWithFakePanaAndDartdoc();
} else {
throw ArgumentError('Unknown analysis: `$analysis`');
}
}

/// Updates the task status for all packages and imitates
/// pub_worker using fake pana and dartdoc results.
Future<void> processTasksWithFakePanaAndDartdoc() async {
await taskBackend.backfillAndProcessAllPackages(_processPayload);
await taskBackend.backfillAndProcessAllPackages(_fakeAnalysis);
}

Future<void> _processPayload(Payload payload) async {
Future<void> _fakeAnalysis(Payload payload) async {
for (final v in payload.versions) {
final client = httpClientWithAuthorization(
tokenProvider: () async => v.token,
Expand Down Expand Up @@ -154,10 +175,40 @@ Future<void> _processPayload(Payload payload) async {
}
}

Future<void> _analyzeLocal() async {
await fork(() async {
await taskBackend.backfillAndProcessAllPackages((Payload payload) async {
final arguments = [json.encode(payload.toJson())];
final pr = await Process.run(
Platform.resolvedExecutable,
['run', 'pub_worker', ...arguments],
workingDirectory: p.join(resolveAppDir(), '..', 'pkg', 'pub_worker'),
);
if (pr.exitCode != 0) {
throw Exception('Unexpected status code: ${pr.exitCode} ${pr.stdout}');
}
});
});
}

Future<void> _analyzeWorker() async {
await buildDockerImage();
await fork(() async {
await taskBackend.backfillAndProcessAllPackages((Payload payload) async {
final p = await startDockerAnalysis(payload);
final exitCode = await p.exitCode;
if (exitCode != 0) {
throw Exception(
'Failed to analyze ${payload.package} with exitCode $exitCode');
}
});
});
}

Future<void> fakeCloudComputeInstanceRunner(FakeCloudInstance instance) async {
final payload = Payload.fromJson(
json.decode(instance.arguments.first) as Map<String, dynamic>);
await _processPayload(payload);
await _fakeAnalysis(payload);
}

Map<String, String> _fakeDartdocFiles(
Expand Down
3 changes: 2 additions & 1 deletion app/lib/fake/server/fake_server_entrypoint.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ Future<shelf.Response> _testProfile(shelf.Request rq) async {
profile: profile,
source: ImportSource.autoGenerated(),
);
await processTasksWithFakePanaAndDartdoc();
final analysis = (map['analysis'] as String?) ?? 'fake';
await processTaskFakeLocalOrWorker(analysis);
return shelf.Response.ok('{}');
}

Expand Down
43 changes: 1 addition & 42 deletions app/lib/fake/tool/init_data_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,15 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';
import 'dart:io';

import 'package:_pub_shared/data/task_payload.dart';
import 'package:_pub_shared/worker/docker_utils.dart';
import 'package:args/command_runner.dart';
import 'package:gcloud/service_scope.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:pub_dev/fake/backend/fake_pub_worker.dart';

import 'package:pub_dev/frontend/static_files.dart';
import 'package:pub_dev/service/services.dart';
import 'package:pub_dev/task/backend.dart';
import 'package:pub_dev/tool/test_profile/import_source.dart';
import 'package:pub_dev/tool/test_profile/importer.dart';
import 'package:pub_dev/tool/test_profile/models.dart';
Expand Down Expand Up @@ -90,44 +85,8 @@ class FakeInitDataFileCommand extends Command {
: ImportSource.autoGenerated(),
);

if (analysis == 'local') {
await _analyzeLocal();
} else if (analysis == 'worker') {
await _analyzeWorker();
} else if (analysis == 'fake') {
await processTasksWithFakePanaAndDartdoc();
}
await processTaskFakeLocalOrWorker(analysis);
});
await state.save(dataFile);
}
}

Future<void> _analyzeLocal() async {
await fork(() async {
await taskBackend.backfillAndProcessAllPackages((Payload payload) async {
final arguments = [json.encode(payload.toJson())];
final pr = await Process.run(
Platform.resolvedExecutable,
['run', 'pub_worker', ...arguments],
workingDirectory: p.join(resolveAppDir(), '..', 'pkg', 'pub_worker'),
);
if (pr.exitCode != 0) {
throw Exception('Unexpected status code: ${pr.exitCode} ${pr.stdout}');
}
});
});
}

Future<void> _analyzeWorker() async {
await buildDockerImage();
await fork(() async {
await taskBackend.backfillAndProcessAllPackages((Payload payload) async {
final p = await startDockerAnalysis(payload);
final exitCode = await p.exitCode;
if (exitCode != 0) {
throw Exception(
'Failed to analyze ${payload.package} with exitCode $exitCode');
}
});
});
}
89 changes: 89 additions & 0 deletions pkg/pub_integration/test/search_completition_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:pub_integration/src/fake_test_context_provider.dart';
import 'package:pub_integration/src/test_browser.dart';
import 'package:puppeteer/puppeteer.dart';
import 'package:test/test.dart';

void main() {
group('search completition', () {
late final TestContextProvider fakeTestScenario;
final httpClient = http.Client();

setUpAll(() async {
fakeTestScenario = await TestContextProvider.start();
});

tearDownAll(() async {
await fakeTestScenario.close();
httpClient.close();
});

test('bulk tests', () async {
final origin = fakeTestScenario.pubHostedUrl;
// init server data
//
// The test profile import uses a fake analysis by default, which
// assigns tags with a pseudorandom process (based on the hash of the
// package's name and sometimes the version), with a few hardcoded
// patterns, e.g. `flutter_*` packages will get `sdk:flutter` tag assigned.
//
// This imports 100 packages with these semi-random tags, and adding and
// removing filters works because of the number of packages and their
// tags are kind of random.
await httpClient.post(Uri.parse('$origin/fake-test-profile'),
body: json.encode({
'testProfile': {
'defaultUser': 'admin@pub.dev',
'packages': [
{
'name': 'oxygen',
'publisher': 'example.com',
},
],
},
'analysis': 'local',
}));

final user = await fakeTestScenario.createAnonymousTestUser();

await user.withBrowserPage((page) async {
await page.gotoOrigin('/experimental?search-completion=1');

await page.gotoOrigin('/');
await page.keyboard.type('is:un');
await Future.delayed(Duration(milliseconds: 200));
await page.keyboard.press(Key.enter);
await Future.delayed(Duration(milliseconds: 200));
await page.keyboard.press(Key.enter);
await page.waitForNavigation();

// TODO: try to fix form submission to trim whitespaces
expect(page.url, '$origin/packages?q=is%3Aunlisted+');
});

await user.withBrowserPage((page) async {
await page.gotoOrigin('/experimental?search-completion=1');

await page.gotoOrigin('/packages?q=abc');
await page.focus('input[name="q"]');
// go to the end of the input field and start typing
await page.keyboard.press(Key.arrowDown);
await page.keyboard.type(' -sdk:fl');
await Future.delayed(Duration(milliseconds: 200));
await page.keyboard.press(Key.enter);
await Future.delayed(Duration(milliseconds: 200));
await page.keyboard.press(Key.enter);
await page.waitForNavigation();

// TODO: try to fix form submission to trim whitespaces
expect(page.url, '$origin/packages?q=abc+-sdk%3Aflutter+');
});
});
}, timeout: Timeout.factor(testTimeoutFactor));
}

0 comments on commit ae925a8

Please sign in to comment.