blob: 0249015309947475e70c17ea1e9e414b00097b1c [file] [log] [blame] [edit]
// Copyright (c) 2025, 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' as io;
import 'package:code_assets/code_assets.dart' show OS;
import 'package:dartdev/src/sdk_cache.dart';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:hooks_runner/hooks_runner.dart' show Target;
import 'package:http/http.dart' as http;
import 'package:http/testing.dart' as http;
import 'package:test/test.dart';
const dartArchiveUri = 'https://storage.googleapis.com/dart-archive';
void main() {
late SdkCache cache;
late FileSystem fs;
late StringBuffer stderr;
late Map<String, http.Response> expectedRequests;
late Map<String, io.ProcessResult> chmodRuns;
setUp(() {
// Make sure we support both path separators.
fs = MemoryFileSystem(
style: io.Platform.isWindows
? FileSystemStyle.windows
: FileSystemStyle.posix,
);
stderr = StringBuffer();
expectedRequests = <String, http.Response>{};
chmodRuns = <String, io.ProcessResult>{};
cache = SdkCache(
directory: fs.directory(Uri.file('/tmp/cache')).path,
stderr: stderr,
verbose: true,
fs: fs,
httpClient: http.MockClient((request) async {
final key = '${request.method.toUpperCase()} ${request.url}';
if (!expectedRequests.containsKey(key)) {
throw Exception('Unexpected request $key');
}
return expectedRequests[key]!;
}),
chmod: (path) => chmodRuns[path]!,
);
});
group('resolveStage', () {
test('Uses signed on macOS dev for executables', () {
expect(
SdkCache.resolveStage(
channel: Channel.dev,
isExecutable: true,
hostOS: OS.macOS,
),
Stage.signed,
);
});
test('Uses raw on macOS main for executables', () {
expect(
SdkCache.resolveStage(
channel: Channel.main,
isExecutable: true,
hostOS: OS.macOS,
),
Stage.raw,
);
});
test('Uses raw on macOS stable for non-executables', () {
expect(
SdkCache.resolveStage(
channel: Channel.stable,
isExecutable: false,
hostOS: OS.macOS,
),
Stage.raw,
);
});
});
group('resolveVersion', () {
test('Resolves release version', () async {
final version = '3.4.4';
final revision = '60465149414572c8ca189d8f65fdb39795c4b97d';
final folder = await cache.resolveVersion(
version: version,
revision: revision,
channelName: 'stable',
host: Target.linuxArm64,
);
expect(folder.version, version);
expect(folder.revision, revision);
expect(folder.channel, Channel.stable);
expect(
folder.fileUri('VERSION', stage: Stage.raw).toString(),
'$dartArchiveUri/channels/stable/raw/hash/$revision/VERSION',
);
});
test('Falls back to the latest on empty main revision', () async {
final version = '3.8.0';
final revision = '';
final latestVersion =
'3.8.0-edge.7ee7416a62efb7eb23eb2a87eb34d2895559ba06';
final latestRevision = '7ee7416a62efb7eb23eb2a87eb34d2895559ba06';
expectedRequests['GET $dartArchiveUri/channels/main/raw/latest/VERSION'] =
http.Response(
json.encode({'version': latestVersion, 'revision': latestRevision}),
io.HttpStatus.ok,
);
final folder = await cache.resolveVersion(
version: version,
revision: revision,
channelName: 'main',
host: Target.macOSArm64,
);
expect(folder.version, latestVersion);
expect(folder.revision, latestRevision);
expect(folder.channel, Channel.main);
});
test('Falls back to the latest on unknown main revision', () async {
final version = '3.8.0';
final revision = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
final latestVersion =
'3.8.0-edge.7ee7416a62efb7eb23eb2a87eb34d2895559ba06';
final latestRevision = '7ee7416a62efb7eb23eb2a87eb34d2895559ba06';
expectedRequests.addAll({
// Revision validation request.
'HEAD $dartArchiveUri/channels/main/raw/hash/$revision/VERSION':
http.Response('', io.HttpStatus.notFound),
// Revision resolution request.
'GET $dartArchiveUri/channels/main/raw/latest/VERSION': http.Response(
json.encode({'version': latestVersion, 'revision': latestRevision}),
io.HttpStatus.ok,
),
});
final folder = await cache.resolveVersion(
version: version,
revision: revision,
channelName: 'main',
host: Target.macOSArm64,
);
expect(folder.version, latestVersion);
expect(folder.revision, latestRevision);
expect(folder.channel, Channel.main);
});
test('Reports unknown channel', () async {
try {
await cache.resolveVersion(
channelName: 'wat',
host: Target.linuxArm64,
revision: '',
version: '4.0',
);
fail('expected to throw');
} on ArgumentError catch (e) {
expect(e.message, contains('Unsupported channel'));
}
});
});
group('ensureArtifact', () {
test('Skips download when file exists', () async {
final version = '3.4.4';
final revision = '60465149414572c8ca189d8f65fdb39795c4b97d';
final genSnapshotFile = fs.file(
Uri.file(
'/tmp/cache/$version/gen_snapshot_windows_arm64_linux_x64.exe',
),
);
genSnapshotFile.createSync(exclusive: true, recursive: true);
final path = await cache.ensureGenSnapshot(
archiveFolder: ArchiveFolder(
channel: Channel.stable,
version: version,
revision: revision,
),
host: Target.windowsArm64,
target: Target.linuxX64,
);
expect(path, genSnapshotFile.path);
});
test('Downloads signed gen_snapshot on macOS beta', () async {
final version = '3.8.0-171.2.beta';
final revision = '54cec4d7d36e7a5066770287998f425606a2f983';
final archiveFolder = ArchiveFolder(
channel: Channel.beta,
version: version,
revision: revision,
);
expectedRequests['GET $dartArchiveUri/channels/beta/signed/hash/'
'$revision/sdk/gen_snapshot_macos_arm64_linux_x64'] = http.Response(
'i am gen_snapshot',
io.HttpStatus.ok,
);
final path = await cache.ensureGenSnapshot(
archiveFolder: archiveFolder,
host: Target.macOSArm64,
target: Target.linuxX64,
);
expect(
path,
fs
.file(
Uri.file(
'/tmp/cache/$version/gen_snapshot_macos_arm64_linux_x64',
),
)
.path,
);
expect(fs.file(path).readAsStringSync(), 'i am gen_snapshot');
});
test('Downloads raw dartaotruntime on macOS beta', () async {
final version = '3.8.0-171.2.beta';
final revision = '54cec4d7d36e7a5066770287998f425606a2f983';
final archiveFolder = ArchiveFolder(
channel: Channel.beta,
version: version,
revision: revision,
);
expectedRequests['GET $dartArchiveUri/channels/beta/raw/hash/'
'$revision/sdk/dartaotruntime_linux_x64'] = http.Response(
'i am dartaotruntime',
io.HttpStatus.ok,
);
final path = await cache.ensureDartAotRuntime(
archiveFolder: archiveFolder,
host: Target.macOSArm64,
target: Target.linuxX64,
);
expect(
path,
fs.file(Uri.file('/tmp/cache/$version/dartaotruntime_linux_x64')).path,
);
expect(fs.file(path).readAsStringSync(), 'i am dartaotruntime');
});
test('Downloads gen_snapshot.exe on Windows stable', () async {
final version = '3.7.2';
final channel = Channel.stable;
final revision = '9594995093f642957b780603c6435d9e7a61b923';
final binary = 'gen_snapshot_windows_x64_linux_x64.exe';
final archiveFolder = ArchiveFolder(
channel: channel,
version: version,
revision: revision,
);
expectedRequests['GET $dartArchiveUri/channels/stable/raw/hash/'
'$revision/sdk/$binary'] = http.Response(
'i am gen_snapshot',
io.HttpStatus.ok,
);
final path = await cache.ensureGenSnapshot(
archiveFolder: archiveFolder,
host: Target.windowsX64,
target: Target.linuxX64,
);
expect(path, fs.file(Uri.file('/tmp/cache/$version/$binary')).path);
expect(fs.file(path).readAsStringSync(), 'i am gen_snapshot');
});
test('Downloads dartaotruntime on Windows stable', () async {
final version = '3.7.2';
final channel = Channel.stable;
final revision = '9594995093f642957b780603c6435d9e7a61b923';
final binary = 'dartaotruntime_linux_x64';
final archiveFolder = ArchiveFolder(
channel: channel,
version: version,
revision: revision,
);
expectedRequests['GET $dartArchiveUri/channels/stable/raw/hash/'
'$revision/sdk/$binary'] = http.Response(
'i am dartaotruntime',
io.HttpStatus.ok,
);
final path = await cache.ensureDartAotRuntime(
archiveFolder: archiveFolder,
host: Target.windowsX64,
target: Target.linuxX64,
);
expect(path, fs.file(Uri.file('/tmp/cache/$version/$binary')).path);
expect(fs.file(path).readAsStringSync(), 'i am dartaotruntime');
});
});
}