blob: f1c8916c009ba88d1e221a513646eb4019ebdd21 [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))
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? _tempDir;
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 {
_tempDir = 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 = _tempDir!;
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');
});
}
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;
}