blob: e06d1ffde74119b4dbf064bfd2e45f79d85d32fc [file] [log] [blame]
// Copyright (c) 2023, 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.
@Tags(['daily'])
@Timeout(Duration(minutes: 3))
library;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
// TODO(elliette): Move to a cron job. This tests that the currently published
// Webdev can be activated and serve a web app. It is intended to catch any
// regressions due to changes in the Dart SDK.
enum StreamType {
stdout,
stderr,
}
const processTimeout = Duration(minutes: 1);
void main() {
Process? createProcess;
Process? activateProcess;
Process? serveProcess;
Directory? tempDir0;
final testScript =
File(p.join(p.dirname(Platform.script.toFilePath()), 'test.dart'))
.readAsStringSync();
final thisScript = File.fromUri(Uri.parse(testScript.substring(
testScript.lastIndexOf('import', testScript.indexOf('as test;')) + 8,
testScript.indexOf('as test;') - 2)));
final packageDir = p.dirname(p.dirname(thisScript.path));
Future<void> expectStdoutAndCleanExit(Process process,
{required String expectedStdout}) async {
final stdoutCompleter = _captureOutput(
process,
streamType: StreamType.stdout,
stopCaptureFuture: process.exitCode,
);
final stderrCompleter = _captureOutput(
process,
streamType: StreamType.stderr,
stopCaptureFuture: process.exitCode,
);
final exitCode = await _waitForExitOrTimeout(process);
final stderrLogs = await stderrCompleter.future;
final stdoutLogs = await stdoutCompleter.future;
expect(
exitCode,
equals(0),
// Include the stderr and stdout logs if the process does not terminate
// cleanly:
reason: 'stderr: $stderrLogs, stdout: $stdoutLogs',
);
expect(
stderrLogs,
isEmpty,
);
expect(
stdoutLogs,
contains(expectedStdout),
);
}
Future<void> expectStdoutThenExit(Process process,
{required String expectedStdout}) async {
final expectedStdoutCompleter = _waitForStdoutOrTimeout(
process,
expectedStdout: expectedStdout,
);
final stderrCompleter = _captureOutput(
process,
streamType: StreamType.stderr,
stopCaptureFuture: expectedStdoutCompleter.future,
);
final stdoutLogs = await expectedStdoutCompleter.future;
final stderrLogs = await stderrCompleter.future;
expect(
stdoutLogs, contains(expectedStdout),
// Also include the stderr if the stdout is not expected.
reason: 'stderr: $stderrLogs',
);
}
setUp(() async {
tempDir0 = Directory.systemTemp.createTempSync('installation_test');
await Process.run(
'dart',
['pub', 'global', 'deactivate', 'webdev'],
);
});
tearDown(() async {
// Kill any stale processes:
if (createProcess != null) {
Process.killPid(createProcess!.pid, ProcessSignal.sigint);
createProcess = null;
}
if (activateProcess != null) {
Process.killPid(activateProcess!.pid, ProcessSignal.sigint);
activateProcess = null;
}
if (serveProcess != null) {
Process.killPid(serveProcess!.pid, ProcessSignal.sigint);
serveProcess = null;
}
});
test('can activate and serve webdev', () async {
final tempDir = tempDir0!;
final tempPath = tempDir.path;
// Verify that we can create a new Dart app:
createProcess = await Process.start(
'dart',
['create', '--template', 'web', 'temp_app'],
workingDirectory: tempPath,
);
await expectStdoutAndCleanExit(
createProcess!,
expectedStdout: 'Created project temp_app in temp_app!',
);
final appPath = p.join(tempPath, 'temp_app');
expect(await Directory(appPath).exists(), isTrue);
// Verify that `dart pub global activate` works:
activateProcess = await Process.start(
'dart',
['pub', 'global', 'activate', 'webdev'],
);
await expectStdoutAndCleanExit(
activateProcess!,
expectedStdout: 'Activated webdev',
);
// Verify that `webdev serve` works for our new app:
serveProcess = await Process.start(
'dart', ['pub', 'global', 'run', 'webdev', 'serve'],
workingDirectory: appPath);
await expectStdoutThenExit(serveProcess!,
expectedStdout: 'Serving `web` on');
});
test('activate and serve webdev fails with offline', () async {
final tempDir = tempDir0!;
final tempPath = tempDir.path;
// Verify that we can create a new Dart app:
createProcess = await Process.start(
'dart',
['create', '--no-pub', '--template', 'web', 'temp_app'],
workingDirectory: tempPath,
);
await expectStdoutAndCleanExit(
createProcess!,
expectedStdout: 'Created project temp_app in temp_app!',
);
final appPath = p.join(tempPath, 'temp_app');
expect(await Directory(appPath).exists(), isTrue);
// Verify that `dart pub global activate` works:
activateProcess = await Process.start(
'dart',
['pub', 'global', 'activate', '--source', 'path', packageDir],
);
await expectStdoutAndCleanExit(
activateProcess!,
expectedStdout: 'Activated webdev',
);
// Verify that `webdev serve` works for our new app:
serveProcess = await Process.start('dart',
['pub', 'global', 'run', 'webdev', 'serve', '--offline', 'web:8081'],
workingDirectory: appPath);
await expectStdoutThenExit(serveProcess!,
expectedStdout: 'Cannot open file\n pubspec.lock\n');
});
}
Future<int> _waitForExitOrTimeout(Process process) {
Timer(processTimeout, () {
process.kill(ProcessSignal.sigint);
});
return process.exitCode;
}
/// Returns the stdout for the [process] once the [expectedStdout] is found.
///
/// Otherwise returns all the stdout up to the [processTimeout].
Completer<String> _waitForStdoutOrTimeout(Process process,
{required String expectedStdout}) {
var output = '';
final completer = Completer<String>();
Timer(processTimeout, () {
process.kill(ProcessSignal.sigint);
if (!completer.isCompleted) {
completer.complete(output);
}
});
process.stdout.transform(utf8.decoder).listen((line) {
output += line;
if (line.contains(expectedStdout)) {
process.kill(ProcessSignal.sigint);
if (!completer.isCompleted) {
completer.complete(output);
}
}
});
return completer;
}
Completer<String> _captureOutput(
Process process, {
required StreamType streamType,
required Future stopCaptureFuture,
}) {
final stream =
streamType == StreamType.stdout ? process.stdout : process.stderr;
final completer = Completer<String>();
var output = '';
stream.transform(utf8.decoder).listen((line) {
output += line;
if (line.contains('[SEVERE]')) {
process.kill(ProcessSignal.sigint);
if (!completer.isCompleted) {
completer.complete(output);
}
}
});
unawaited(stopCaptureFuture.then((_) {
if (!completer.isCompleted) {
completer.complete(output);
}
}));
return completer;
}