halkyon/test/deploy/image_resolution_test.dart

153 lines
4.5 KiB
Dart
Raw Normal View History

import 'dart:io';
import 'package:test/test.dart';
import '../../tool/src/images.dart';
/// tool/check.dart's replay of the images' resolution (4 Nov 2026: two
/// deploys failed on what pub refuses in Dart's image alone).
void main() {
group('a build stage read', () {
const dockerfile = r'''
# A comment.
FROM dart:3.13.4 AS build
WORKDIR /app
COPY pubspec.yaml pubspec.lock ./
COPY tool/workspace_only.sh tool/
RUN sh tool/workspace_only.sh apps/a \
packages/b
RUN dart pub get
WORKDIR /app/apps/a
RUN dart build cli --target bin/main.dart --output build
FROM ubuntu:24.04
COPY --from=build /app/apps/a/build /srv
''';
test('names its Dart and its steps up to and including dart pub get, '
'continued lines joined', () {
final resolution = imageResolution(dockerfile);
expect(resolution.dartVersion, '3.13.4');
expect(
[
for (final step in resolution.steps)
switch (step) {
CopyStep(:final sources, :final destination) =>
'COPY ${sources.join(' ')} -> $destination',
RunStep(:final command) => 'RUN $command',
WorkdirStep(:final path) => 'WORKDIR $path',
},
],
[
'WORKDIR /app',
'COPY pubspec.yaml pubspec.lock -> ./',
'COPY tool/workspace_only.sh -> tool/',
'RUN sh tool/workspace_only.sh apps/a packages/b',
'RUN dart pub get',
],
);
});
test('refuses a stage without Dart, without pub get, or with an '
'instruction it would replay wrongly', () {
expect(
() => imageResolution('FROM ubuntu:24.04\nRUN true\n'),
throwsFormatException,
);
expect(
() => imageResolution('FROM dart:3.13.4 AS build\nRUN true\n'),
throwsFormatException,
);
expect(
() => imageResolution(
'FROM dart:3.13.4 AS build\nENV A=b\nRUN dart pub get\n',
),
throwsFormatException,
);
});
});
group("COPY's files land as Docker puts them", () {
const files = [
'pubspec.yaml',
'pubspec.lock',
'tool/workspace_only.sh',
'tool/check.dart',
'apps/a/pubspec.yaml',
'apps/a/lib/a.dart',
'apps/ab/pubspec.yaml',
];
test('a folder: its content under the destination, nothing beside it', () {
expect(copiedFiles(const CopyStep(['apps/a'], 'apps/a'), files), {
'apps/a/pubspec.yaml': '/app/apps/a/pubspec.yaml',
'apps/a/lib/a.dart': '/app/apps/a/lib/a.dart',
});
});
test('files into a folder named with a slash, or several at once', () {
expect(
copiedFiles(const CopyStep(['tool/workspace_only.sh'], 'tool/'), files),
{'tool/workspace_only.sh': '/app/tool/workspace_only.sh'},
);
expect(
copiedFiles(
const CopyStep(['pubspec.yaml', 'pubspec.lock'], './'),
files,
),
{
'pubspec.yaml': '/app/pubspec.yaml',
'pubspec.lock': '/app/pubspec.lock',
},
);
});
test('a file as the destination named without a slash', () {
expect(
copiedFiles(
const CopyStep(['pubspec.yaml'], 'other.yaml'),
files,
workdir: '/app/x',
),
{'pubspec.yaml': '/app/x/other.yaml'},
);
});
});
test("the SDK's archive is this machine's", () {
expect(
dartSdkArchive('linux', '3.13.4 (stable) (Tue) on "linux_x64"'),
'dartsdk-linux-x64-release.zip',
);
expect(
dartSdkArchive('linux', '3.13.4 (stable) (Tue) on "linux_arm64"'),
'dartsdk-linux-arm64-release.zip',
);
});
group('the repository', () {
test('every image of the list is replayed up to its pub get', () {
for (final path in dartImages) {
final resolution = imageResolution(File(path).readAsStringSync());
expect(resolution.dartVersion, matches(RegExp(r'^\d+\.\d+\.\d+$')));
expect(resolution.steps.last, isA<RunStep>(), reason: path);
}
});
test('every Dockerfile building with Dart is on the list, so none '
'escapes the replay', () {
final dockerfiles = Process.runSync('git', [
'ls-files',
'*Dockerfile',
]).stdout.toString().split('\n').where((path) => path.isNotEmpty);
final dart = [
for (final path in dockerfiles)
if (File(path)
.readAsStringSync()
.contains(RegExp('^FROM dart:', multiLine: true)))
path,
];
expect(dart.toSet(), dartImages.toSet());
});
});
}