blob: 3bdd224bbeae7bd1706bb4768d340efdf8217f96 [file] [log] [blame]
// Copyright (c) 2020, 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 'dart:io';
import 'package:path/path.dart' as p;
import 'package:pub/src/dart.dart';
import 'package:pub/src/io.dart';
import 'package:pub_semver/pub_semver.dart';
import '../descriptor.dart' as d;
import '../golden_file.dart';
import '../test_pub.dart';
void manifestAndLockfile(GoldenTestContext context) {
String catFile(String filename) {
final contents = filterUnstableLines(
File(p.join(d.sandbox, appPath, filename)).readAsLinesSync());
return '''
\$ cat $filename
${contents.join('\n')}''';
}
context.expectNextSection('''
${catFile('pubspec.yaml')}
${catFile('pubspec.lock')}
''');
}
final snapshot = () async {
final snapshotFilename = p.absolute(
p.join('.dart_tool', '_pub', 'dependency_services.dart.snapshot.dart2'));
final snapshotIncrementalFilename = '$snapshotFilename.incremental';
await precompile(
executablePath: p.join('bin', 'dependency_services.dart'),
outputPath: snapshotFilename,
incrementalDillOutputPath: snapshotIncrementalFilename,
name: 'bin/pub.dart',
packageConfigPath: p.join('.dart_tool', 'package_config.json'));
return snapshotFilename;
}();
extension on GoldenTestContext {
Future<void> runDependencyServices(List<String> args, {String? stdin}) async {
final buffer = StringBuffer();
buffer.writeln('## Section ${args.join(' ')}');
final process = await Process.start(
Platform.resolvedExecutable,
[
await snapshot,
...args,
],
environment: getPubTestEnvironment(),
workingDirectory: p.join(d.sandbox, appPath),
);
if (stdin != null) {
process.stdin.write(stdin);
await process.stdin.flush();
await process.stdin.close();
}
final exitCode = await process.exitCode;
final pipe = stdin == null ? '' : ' echo ${protectArgument(stdin)} |';
buffer.writeln([
'\$$pipe dependency_services ${args.map(protectArgument).join(' ')}',
...await outputLines(process.stdout),
...(await outputLines(process.stderr)).map((e) => '[STDERR] $e'),
if (exitCode != 0) '[EXIT CODE] $exitCode',
].join('\n'));
expectNextSection(buffer.toString());
}
}
Future<Iterable<String>> outputLines(Stream<List<int>> stream) async {
final s = await utf8.decodeStream(stream);
if (s.isEmpty) return [];
return filterUnstableLines(s.split('\n'));
}
Future<void> listReportApply(
GoldenTestContext context,
List<_PackageVersion> upgrades,
) async {
manifestAndLockfile(context);
await context.runDependencyServices(['list']);
await context.runDependencyServices(['report']);
final input = json.encode({
'dependencyChanges': upgrades,
});
await context.runDependencyServices(['apply'], stdin: input);
manifestAndLockfile(context);
}
Future<void> main() async {
testWithGolden('Removing transitive', (context) async {
await servePackages((builder) => builder
..serve('foo', '1.2.3', deps: {'transitive': '^1.0.0'})
..serve('foo', '2.2.3')
..serve('transitive', '1.0.0'));
await d.dir(appPath, [
d.pubspec({
'name': 'app',
'dependencies': {
'foo': '^1.0.0',
},
})
]).create();
await pubGet();
await listReportApply(context, [
_PackageVersion('foo', Version.parse('2.2.3')),
_PackageVersion('transitive', null)
]);
});
testWithGolden('Compatible', (context) async {
await servePackages((builder) => builder
..serve('foo', '1.2.3')
..serve('foo', '2.2.3')
..serve('bar', '1.2.3')
..serve('bar', '2.2.3'));
await d.dir(appPath, [
d.pubspec({
'name': 'app',
'dependencies': {
'foo': '^1.0.0',
'bar': '^1.0.0',
},
})
]).create();
await pubGet();
globalPackageServer!.add((b) => b.serve('foo', '1.2.4'));
await listReportApply(context, [
_PackageVersion('foo', Version.parse('1.2.3')),
_PackageVersion('transitive', null)
]);
});
testWithGolden('Adding transitive', (context) async {
await servePackages((builder) => builder
..serve('foo', '1.2.3')
..serve('foo', '2.2.3', deps: {'transitive': '^1.0.0'})
..serve('transitive', '1.0.0'));
await d.dir(appPath, [
d.pubspec({
'name': 'app',
'dependencies': {
'foo': '^1.0.0',
},
})
]).create();
await pubGet();
await listReportApply(context, [
_PackageVersion('foo', Version.parse('2.2.3')),
_PackageVersion('transitive', Version.parse('1.0.0'))
]);
});
testWithGolden('multibreaking', (context) async {
await servePackages((builder) => builder
..serve('foo', '1.0.0')
..serve('bar', '1.0.0'));
await d.dir(appPath, [
d.pubspec({
'name': 'app',
'dependencies': {
'foo': '^1.0.0',
'bar': '^1.0.0',
},
})
]).create();
await pubGet();
globalPackageServer!.add((builder) => builder
..serve('foo', '1.5.0') // compatible
..serve('foo', '2.0.0') // single breaking
..serve('foo', '3.0.0', deps: {'bar': '^2.0.0'}) // multi breaking
..serve('foo', '3.0.1', deps: {'bar': '^2.0.0'})
..serve('bar', '2.0.0', deps: {'foo': '^3.0.0'})
..serve('transitive', '1.0.0'));
await listReportApply(context, [
_PackageVersion('foo', Version.parse('3.0.1'),
constraint: VersionConstraint.parse('^3.0.0')),
_PackageVersion('bar', Version.parse('2.0.0'))
]);
});
}
class _PackageVersion {
String name;
Version? version;
VersionConstraint? constraint;
_PackageVersion(this.name, this.version, {this.constraint});
Map<String, Object?> toJson() => {
'name': name,
'version': version?.toString(),
if (constraint != null) 'constraint': constraint.toString()
};
}