blob: f9064829c5df5b31b80947c4bdda647040dc812c [file]
// Copyright (c) 2026, 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:io';
import 'package:boring/src/hook_helpers/build_options.dart';
import 'package:boring/src/hook_helpers/fetch.dart';
import 'package:boring/src/hook_helpers/hashes.dart';
import 'package:boring/src/hook_helpers/targets.dart';
import 'package:code_assets/code_assets.dart';
import 'package:hooks/hooks.dart';
import 'package:test/test.dart';
void main() {
group('hashes.dart', () {
// If the asset naming and the hashes file drift apart, `fetch` finds no
// hash and fails for every user.
test('pins exactly the assets of the prebuilt targets', () {
final expected = {
for (final (os, arch, iosSdk) in prebuiltTargets)
for (final static in [false, true])
releaseAssetName(os, arch, iosSdk: iosSdk, static: static),
};
if (sourceCommit.isEmpty) {
// A release built elsewhere may predate some of the prebuilt targets.
expect(fileHashes, isNotEmpty);
expect(expected, containsAll(fileHashes.keys));
} else {
expect(fileHashes.keys, unorderedEquals(expected));
}
});
test('has SHA-256 hashes', () {
for (final MapEntry(key: asset, value: hash) in fileHashes.entries) {
expect(hash, matches(RegExp(r'^[0-9a-f]{64}$')), reason: asset);
}
});
test('pins a release', () {
expect(releaseRepository, matches(RegExp(r'^[\w.-]+/[\w.-]+$')));
expect(releaseTag, isNotEmpty);
expect(sourceCommit, anyOf(isEmpty, matches(RegExp(r'^[0-9a-f]{40}$'))));
expect(
releaseAssetUrl(
repository: releaseRepository,
tag: releaseTag,
assetName: 'boring-linux-x64-libbssl_dart.so',
).toString(),
'https://github.com/$releaseRepository/releases/download/$releaseTag/'
'boring-linux-x64-libbssl_dart.so',
);
});
});
group('releaseAssetName', () {
test('uses the platform library names', () {
expect(
releaseAssetName(OS.linux, Architecture.x64, static: false),
'boring-linux-x64-libbssl_dart.so',
);
expect(
releaseAssetName(OS.linux, Architecture.arm64, static: true),
'boring-linux-arm64-libbssl_dart_static.a',
);
expect(
releaseAssetName(OS.macOS, Architecture.arm64, static: false),
'boring-macos-arm64-libbssl_dart.dylib',
);
expect(
releaseAssetName(OS.windows, Architecture.x64, static: false),
'boring-windows-x64-bssl_dart.dll',
);
expect(
releaseAssetName(OS.windows, Architecture.x64, static: true),
'boring-windows-x64-bssl_dart_static.lib',
);
});
test('distinguishes the iOS SDKs', () {
expect(
releaseAssetName(
OS.iOS,
Architecture.arm64,
iosSdk: IOSSdk.iPhoneSimulator,
static: true,
),
'boring-ios-arm64-iphonesimulator-libbssl_dart_static.a',
);
});
});
group('BuildOptions.fromDefines', () {
test('defaults to fetch', () {
final options = BuildOptions.fromDefines(_userDefines({}));
expect(options.buildMode, BuildModeEnum.fetch);
expect(options.localPath, isNull);
});
test('parses every build mode', () {
for (final mode in BuildModeEnum.values) {
expect(
BuildOptions.fromDefines(_userDefines({'buildMode': mode.name}))
.buildMode,
mode,
);
}
});
test('resolves localPath against the pubspec', () {
final options = BuildOptions.fromDefines(
_userDefines({'buildMode': 'local', 'localPath': 'lib/libfoo.so'}),
);
expect(options.buildMode, BuildModeEnum.local);
expect(options.localPath, _basePath.resolve('lib/libfoo.so'));
});
test('rejects an unknown build mode instead of fetching', () {
expect(
() => BuildOptions.fromDefines(_userDefines({'buildMode': 'chekout'})),
throwsA(
isA<BuildError>().having(
(e) => e.message,
'message',
allOf(contains("'chekout'"), contains('`checkout`')),
),
),
);
});
test('rejects a build mode that is not a string', () {
expect(
() => BuildOptions.fromDefines(_userDefines({'buildMode': true})),
throwsA(isA<BuildError>()),
);
});
});
group('fetchPrebuiltLibrary', () {
test('throws a BuildError when no prebuilt binary exists for target', () {
final builder = BuildInputBuilder()
..setupShared(
packageRoot: _basePath,
packageName: 'boring',
outputDirectoryShared: _basePath.resolve('.dart_tool/shared/'),
outputFile: _basePath.resolve('.dart_tool/output.json'),
)
..config.setupBuild(linkingEnabled: false)
..addExtension(
CodeAssetExtension(
targetOS: OS.linux,
targetArchitecture: Architecture.riscv64,
linkModePreference: LinkModePreference.dynamic,
),
);
expect(
() => fetchPrebuiltLibrary(builder.build(), static: false),
throwsA(
isA<BuildError>().having(
(e) => e.message,
'message',
allOf(
contains('linux-riscv64'),
contains('buildMode: checkout'),
),
),
),
);
});
});
}
final _basePath = Directory.current.uri;
/// The user-defines of a root package whose pubspec.yaml has [defines] under
/// `hooks.user_defines.boring`.
HookInputUserDefines _userDefines(Map<String, Object?> defines) {
final builder = BuildInputBuilder()
..setupShared(
packageRoot: _basePath,
packageName: 'boring',
outputDirectoryShared: _basePath.resolve('.dart_tool/shared/'),
outputFile: _basePath.resolve('.dart_tool/output.json'),
userDefines: PackageUserDefines(
workspacePubspec: PackageUserDefinesSource(
defines: defines,
basePath: _basePath,
),
),
)
..config.setupBuild(linkingEnabled: false);
return builder.build().userDefines;
}