Skip to content

Commit

Permalink
Add offline mode
Browse files Browse the repository at this point in the history
  • Loading branch information
RossComputerGuy committed Oct 8, 2024
1 parent a5ac4ac commit 6fbec3a
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 6 deletions.
16 changes: 14 additions & 2 deletions webdev/lib/src/command/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const nullSafetyAuto = 'auto';
const disableDdsFlag = 'disable-dds';
const enableExperimentOption = 'enable-experiment';
const canaryFeaturesFlag = 'canary';
const offlineFlag = 'offline';

ReloadConfiguration _parseReloadConfiguration(ArgResults argResults) {
var auto = argResults.options.contains(autoOption)
Expand Down Expand Up @@ -107,6 +108,7 @@ class Configuration {
final String? _nullSafety;
final List<String>? _experiments;
final bool? _canaryFeatures;
final bool? _offline;

Configuration({
bool? autoRun,
Expand All @@ -133,6 +135,7 @@ class Configuration {
String? nullSafety,
List<String>? experiments,
bool? canaryFeatures,
bool? offline,
}) : _autoRun = autoRun,
_chromeDebugPort = chromeDebugPort,
_debugExtension = debugExtension,
Expand All @@ -154,7 +157,8 @@ class Configuration {
_verbose = verbose,
_nullSafety = nullSafety,
_experiments = experiments,
_canaryFeatures = canaryFeatures {
_canaryFeatures = canaryFeatures,
_offline = offline {
_validateConfiguration();
}

Expand Down Expand Up @@ -229,7 +233,8 @@ class Configuration {
verbose: other._verbose ?? _verbose,
nullSafety: other._nullSafety ?? _nullSafety,
experiments: other._experiments ?? _experiments,
canaryFeatures: other._canaryFeatures ?? _canaryFeatures);
canaryFeatures: other._canaryFeatures ?? _canaryFeatures,
offline: other._offline ?? _offline);

factory Configuration.noInjectedClientDefaults() =>
Configuration(autoRun: false, debug: false, debugExtension: false);
Expand Down Expand Up @@ -284,6 +289,8 @@ class Configuration {

bool get canaryFeatures => _canaryFeatures ?? false;

bool get offline => _offline ?? false;

/// Returns a new configuration with values updated from the parsed args.
static Configuration fromArgs(ArgResults? argResults,
{Configuration? defaultConfiguration}) {
Expand Down Expand Up @@ -408,6 +415,10 @@ class Configuration {
? argResults[canaryFeaturesFlag] as bool?
: defaultConfiguration.canaryFeatures;

final offline = argResults.options.contains(offlineFlag)
? argResults[offlineFlag] as bool?
: defaultConfiguration.verbose;

return Configuration(
autoRun: defaultConfiguration.autoRun,
chromeDebugPort: chromeDebugPort,
Expand All @@ -433,6 +444,7 @@ class Configuration {
nullSafety: nullSafety,
experiments: experiments,
canaryFeatures: canaryFeatures,
offline: offline,
);
}
}
Expand Down
10 changes: 8 additions & 2 deletions webdev/lib/src/command/shared.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ void addSharedArgs(ArgParser argParser,
abbr: 'v',
defaultsTo: false,
negatable: false,
help: 'Enables verbose logging.');
help: 'Enables verbose logging.')
..addFlag(offlineFlag,
defaultsTo: false,
negatable: false,
help: 'Disable feching from pub.dev.');
}

/// Parses the provided [Configuration] to return a list of
Expand Down Expand Up @@ -103,7 +107,9 @@ List<String> buildRunnerArgs(Configuration configuration) {
}

Future<void> validatePubspecLock(Configuration configuration) async {
final pubspecLock = await PubspecLock.read();
final pubspecLock = await PubspecLock.read(
offline: configuration.offline
);
await checkPubspecLock(pubspecLock,
requireBuildWebCompilers: configuration.requireBuildWebCompilers);
}
Expand Down
8 changes: 6 additions & 2 deletions webdev/lib/src/pubspec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,12 @@ class PubspecLock {

PubspecLock(this._packages);

static Future<PubspecLock> read() async {
await _runPubDeps();
static Future<PubspecLock> read({
bool offline = false
}) async {
if (!offline) {
await _runPubDeps();
}
var dir = p.absolute(p.current);
while (true) {
final candidate = p.join(
Expand Down
43 changes: 43 additions & 0 deletions webdev/test/installation_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ void main() {
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(
Expand Down Expand Up @@ -141,6 +149,41 @@ void main() {
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) {
Expand Down
97 changes: 97 additions & 0 deletions webdev/test/integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,62 @@
// 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.

@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';
import 'package:test_descriptor/test_descriptor.dart' as d;

import 'test_utils.dart';

enum StreamType {
stdout,
stderr,
}

const processTimeout = Duration(minutes: 1);

void main() {
final testRunner = TestRunner();
setUpAll(testRunner.setUpAll);
tearDownAll(testRunner.tearDownAll);

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),
);
}

test('non-existent commands create errors', () async {
final process = await testRunner.runWebDev(['monkey']);

Expand Down Expand Up @@ -214,6 +258,26 @@ dependencies:
await checkProcessStdout(process, ['webdev could not run']);
await process.shouldExit(78);
});

if (command != 'daemon') {
test('failure with offline and unresolved dependencies', () async {
final createProcess = await Process.start(
'dart',
['create', '--no-pub', '--template', 'web', 'temp_app'],
workingDirectory: d.sandbox,
);
await expectStdoutAndCleanExit(createProcess,
expectedStdout: 'Created project temp_app');

final appPath = p.join(d.sandbox, 'temp_app');

final process = await testRunner
.runWebDev([command, '--offline'], workingDirectory: appPath);

await checkProcessStdout(process, ['webdev could not run']);
await process.shouldExit(78);
});
}
});
}
}
Expand Down Expand Up @@ -286,3 +350,36 @@ packages:

return buffer.toString();
}

Future<int> _waitForExitOrTimeout(Process process) {
Timer(processTimeout, () {
process.kill(ProcessSignal.sigint);
});
return process.exitCode;
}

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;
}

0 comments on commit 6fbec3a

Please sign in to comment.