blob: b37e58616a3a10b8d4118a18f906f98bced6daa9 [file] [edit]
// 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 'package:build_integration/file_system/multi_root.dart'
as front_end
show MultiRootFileSystem;
import 'package:shadow_sources/src/shadow_options.dart';
import 'package:front_end/src/api_unstable/vm.dart'
as front_end
show FileSystem;
import 'package:test/test.dart';
import 'util/fss.dart';
void main() {
const root = '/google/src/cloud/user/workspace/google3';
group('validatePrefix', () {
test('accepts valid prefix characters [a-z0-9_.]', () {
expect(() => validatePrefix('shadowed'), returnsNormally);
expect(() => validatePrefix('custom_prefix_123'), returnsNormally);
expect(() => validatePrefix('pkg.shadow'), returnsNormally);
expect(() => validatePrefix('a'), returnsNormally);
expect(() => validatePrefix('123'), returnsNormally);
});
test('throws ArgumentError for invalid prefixes', () {
expect(() => validatePrefix(''), throwsA(isA<ArgumentError>()));
expect(() => validatePrefix('Shadowed'), throwsA(isA<ArgumentError>()));
expect(() => validatePrefix('shadow-pkg'), throwsA(isA<ArgumentError>()));
expect(() => validatePrefix('shadow pkg'), throwsA(isA<ArgumentError>()));
expect(() => validatePrefix('shadow@pkg'), throwsA(isA<ArgumentError>()));
expect(() => validatePrefix('shadow/pkg'), throwsA(isA<ArgumentError>()));
});
});
group('ShadowOptions.fromArgs', () {
test('parses options and ensures trailing slash on outputDir', () {
final options = ShadowOptions.fromArgs([
'--script=google3:///app/main.dart',
'--packages=google3:///app/main.packages',
'--dynamic-interface=google3:///app/dynamic_interface.yaml',
'--prefix=shadowed',
'--output-dir=/tmp/output_dir_no_slash',
]);
expect(options.scriptUri, equals(Uri.parse('google3:///app/main.dart')));
expect(
options.packagesUri,
equals(Uri.parse('google3:///app/main.packages')),
);
expect(
options.dynamicInterfaceUri,
equals(Uri.parse('google3:///app/dynamic_interface.yaml')),
);
expect(options.prefix, equals('shadowed'));
expect(options.outputDir.path.endsWith('/'), isTrue);
expect(
options.outputDir.toString(),
equals('file:///tmp/output_dir_no_slash/'),
);
});
test('configures multi-root filesystem when scheme and roots provided', () {
final options = ShadowOptions.fromArgs([
'--script=google3:///app/main.dart',
'--packages=google3:///app/main.packages',
'--dynamic-interface=google3:///app/dynamic_interface.yaml',
'--prefix=shadowed',
'--output-dir=/tmp/output_dir/',
'--filesystem-scheme=google3',
'--filesystem-root=/google/src/cloud/user/workspace/google3',
'--filesystem-root=/google/src/cloud/user/workspace/google3/blaze-bin',
]);
expect(options.frontendFs, isA<front_end.MultiRootFileSystem>());
final mrfs = options.frontendFs as front_end.MultiRootFileSystem;
expect(mrfs.markerScheme, equals('google3'));
});
});
group('ShadowOptions.create', () {
late front_end.FileSystem frontendFs;
setUp(() {
final (fs, _) = fssForTests(root, {});
frontendFs = fs;
});
test('enables relative flags when schemes match and are absolute', () {
final options = ShadowOptions.create(
scriptUri: 'google3:///app/main.dart',
packagesUri: 'google3:///app/main.packages',
dynamicInterfaceUri: 'google3:///app/dynamic_interface.yaml',
frontendFs: frontendFs,
prefix: 'shadowed',
outputDir: 'file:///tmp/output/dir/',
);
expect(options.enableRelativeDynamicInterfaceLibraries, isTrue);
expect(options.enableRelativeEntrypointScriptDirectives, isTrue);
});
test('disables enableRelativeDynamicInterfaceLibraries when dynamicInterfacePath scheme differs', () {
final options = ShadowOptions.create(
scriptUri: 'google3:///app/main.dart',
packagesUri: 'google3:///app/main.packages',
dynamicInterfaceUri: 'file:///tmp/app/dynamic_interface.yaml',
frontendFs: frontendFs,
prefix: 'shadowed',
outputDir: 'file:///tmp/output/dir/',
);
expect(options.enableRelativeDynamicInterfaceLibraries, isFalse);
expect(options.enableRelativeEntrypointScriptDirectives, isTrue);
});
test('disables enableRelativeEntrypointScriptDirectives when packagesPath scheme differs', () {
final options = ShadowOptions.create(
scriptUri: 'google3:///app/main.dart',
packagesUri: 'file:///tmp/app/main.packages',
dynamicInterfaceUri: 'google3:///app/dynamic_interface.yaml',
frontendFs: frontendFs,
prefix: 'shadowed',
outputDir: 'file:///tmp/output/dir/',
);
expect(options.enableRelativeDynamicInterfaceLibraries, isTrue);
expect(options.enableRelativeEntrypointScriptDirectives, isFalse);
});
test('throws ArgumentError when outputDir is inside multi-root filesystem scheme', () {
expect(
() => ShadowOptions.create(
scriptUri: 'google3:///app/main.dart',
packagesUri: 'google3:///app/main.packages',
dynamicInterfaceUri: 'google3:///app/dynamic_interface.yaml',
frontendFs: frontendFs,
prefix: 'shadowed',
outputDir: 'google3:///tmp/output/dir/',
),
throwsA(
isA<ArgumentError>().having(
(e) => e.message,
'message',
contains('Cannot write to multi-root filesystem.'),
),
),
);
});
test('ensures trailing slash on outputDir', () {
final options = ShadowOptions.create(
scriptUri: 'google3:///app/main.dart',
packagesUri: 'google3:///app/main.packages',
dynamicInterfaceUri: 'google3:///app/dynamic_interface.yaml',
frontendFs: frontendFs,
prefix: 'shadowed',
outputDir: 'file:///tmp/output_dir_no_slash',
);
expect(options.outputDir.path.endsWith('/'), isTrue);
expect(
options.outputDir.toString(),
equals('file:///tmp/output_dir_no_slash/'),
);
});
});
}