blob: e1f711d106ad91aa67a7b34c4c3d453a81dfbb8b [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 'dart:convert';
import 'dart:io';
import 'package:dartdev/src/telemetry/pubspec_scanner.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
void main() {
group('scanPubspecTelemetry', () {
late Directory tempDir;
late Directory originalCurrentDir;
setUp(() {
tempDir = Directory.systemTemp.createTempSync('dartdev_scanner_test_');
originalCurrentDir = Directory.current;
});
tearDown(() {
Directory.current = originalCurrentDir;
try {
tempDir.deleteSync(recursive: true);
} catch (_) {}
});
void createPackageConfig(
Directory dir,
List<Map<String, String>> packages,
) {
final dotDartTool = Directory(path.join(dir.path, '.dart_tool'))
..createSync(recursive: true);
final file = File(path.join(dotDartTool.path, 'package_config.json'));
file.writeAsStringSync(
jsonEncode({'configVersion': 2, 'packages': packages}),
);
}
test('returns null when no pubspec.yaml exists', () {
expect(scanPubspecTelemetry(tempDir), isNull);
});
test(
'returns null when pubspec exists but package_config.json is missing (conservative fallback)',
() {
File(path.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync('''
name: test_project
dependencies:
path: ^1.8.0
''');
expect(scanPubspecTelemetry(tempDir), isNull);
},
);
test('finds pubspec.yaml and package_config.json up to 5 levels deep', () {
File(path.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync('''
name: test_project
dependencies:
path: ^1.8.0
''');
createPackageConfig(tempDir, [
{
'name': 'path',
'rootUri': 'file:///home/user/.pub-cache/hosted/pub.dev/path-1.8.0',
'packageUri': 'lib/',
},
]);
var deepDir = tempDir;
for (var i = 0; i < 5; i++) {
deepDir = Directory(path.join(deepDir.path, 'level_$i'))..createSync();
}
final telemetry = scanPubspecTelemetry(deepDir);
expect(telemetry, isNotNull);
expect(telemetry!.publicDependencies, {'path'});
expect(telemetry.hasFlutterSdk, isFalse);
expect(telemetry.environmentSdk, isNull);
});
test('finds pubspec.yaml even > 5 levels deep', () {
File(path.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync('''
name: test_project
dependencies:
path: ^1.8.0
''');
createPackageConfig(tempDir, [
{
'name': 'path',
'rootUri': 'file:///home/user/.pub-cache/hosted/pub.dev/path-1.8.0',
'packageUri': 'lib/',
},
]);
var deepDir = tempDir;
for (var i = 0; i < 6; i++) {
deepDir = Directory(path.join(deepDir.path, 'level_$i'))..createSync();
}
final telemetry = scanPubspecTelemetry(deepDir);
expect(telemetry, isNotNull);
expect(telemetry!.publicDependencies, {'path'});
});
test(
'strictly strips private workspace packages in a Dart 3.5+ Pub Workspace',
() {
// Simulate a workspace root with .dart_tool/package_config.json
createPackageConfig(tempDir, [
{
'name': 'path',
'rootUri': 'file:///home/user/.pub-cache/hosted/pub.dev/path-1.8.0',
'packageUri': 'lib/',
},
{
'name': 'secret_trading_algo',
'rootUri': '../secret_trading_algo',
'packageUri': 'lib/',
},
]);
// Simulate member package inside workspace
final memberDir = Directory(path.join(tempDir.path, 'pkgs', 'app'))
..createSync(recursive: true);
File(path.join(memberDir.path, 'pubspec.yaml')).writeAsStringSync('''
name: app
resolution: workspace
dependencies:
path: ^1.8.0
secret_trading_algo:
''');
final telemetry = scanPubspecTelemetry(memberDir);
expect(telemetry, isNotNull);
// 'path' is kept because rootUri is hosted on pub.dev
// 'secret_trading_algo' is stripped because rootUri is a relative workspace path!
expect(telemetry!.publicDependencies, {'path'});
expect(telemetry.hasFlutterSdk, isFalse);
},
);
test('extracts both dependencies and dev_dependencies', () {
File(path.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync('''
name: test_project
dependencies:
path: ^1.8.0
dev_dependencies:
test: ^1.24.0
lints: ^3.0.0
''');
createPackageConfig(tempDir, [
{
'name': 'path',
'rootUri': 'file:///home/user/.pub-cache/hosted/pub.dev/path-1.8.0',
'packageUri': 'lib/',
},
{
'name': 'test',
'rootUri': 'file:///home/user/.pub-cache/hosted/pub.dev/test-1.24.0',
'packageUri': 'lib/',
},
{
'name': 'lints',
'rootUri': 'file:///home/user/.pub-cache/hosted/pub.dev/lints-3.0.0',
'packageUri': 'lib/',
},
]);
final telemetry = scanPubspecTelemetry(tempDir);
expect(telemetry, isNotNull);
expect(telemetry!.publicDependencies, {'path', 'test', 'lints'});
expect(telemetry.hasFlutterSdk, isFalse);
});
test('detects Flutter SDK in environment', () {
File(path.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync('''
name: test_project
environment:
sdk: ^3.5.0
flutter: '>=3.0.0'
dependencies:
path: ^1.8.0
''');
createPackageConfig(tempDir, [
{
'name': 'path',
'rootUri': 'file:///home/user/.pub-cache/hosted/pub.dev/path-1.8.0',
'packageUri': 'lib/',
},
]);
final telemetry = scanPubspecTelemetry(tempDir);
expect(telemetry, isNotNull);
expect(telemetry!.publicDependencies, {'path'});
expect(telemetry.hasFlutterSdk, isTrue);
expect(telemetry.environmentSdk, '^3.5.0');
});
test('detects Flutter SDK in dependencies', () {
File(path.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync('''
name: test_project
environment:
sdk: ^3.5.0
dependencies:
path: ^1.8.0
flutter:
sdk: flutter
''');
createPackageConfig(tempDir, [
{
'name': 'path',
'rootUri': 'file:///home/user/.pub-cache/hosted/pub.dev/path-1.8.0',
'packageUri': 'lib/',
},
]);
final telemetry = scanPubspecTelemetry(tempDir);
expect(telemetry, isNotNull);
expect(telemetry!.publicDependencies, {'path'});
expect(telemetry.hasFlutterSdk, isTrue);
expect(telemetry.environmentSdk, '^3.5.0');
});
test('extracts and truncates long sdk constraint', () {
final longSdk = '^3.0.0 ${'a' * 110}';
File(path.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync('''
name: test_project
environment:
sdk: "$longSdk"
dependencies:
path: ^1.8.0
''');
createPackageConfig(tempDir, [
{
'name': 'path',
'rootUri': 'file:///home/user/.pub-cache/hosted/pub.dev/path-1.8.0',
'packageUri': 'lib/',
},
]);
final telemetry = scanPubspecTelemetry(tempDir);
expect(telemetry, isNotNull);
expect(telemetry!.environmentSdk!.length, 100);
expect(telemetry.environmentSdk, longSdk.substring(0, 100));
});
test('supports custom PUB_CACHE environment variable', () {
final customCacheDir = Directory(
path.join(tempDir.path, 'my_custom_cache'),
)..createSync(recursive: true);
File(path.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync('''
name: test_project
environment:
sdk: ^3.5.0
dependencies:
shelf: ^1.4.0
fake_hosted: ^1.0.0
''');
createPackageConfig(tempDir, [
{
'name': 'shelf',
'rootUri': path
.toUri(
path.join(
customCacheDir.path,
'hosted',
'pub.dev',
'shelf-1.4.0',
),
)
.toString(),
'packageUri': 'lib/',
},
{
'name': 'fake_hosted',
'rootUri': path
.toUri(
path.join(
tempDir.path,
'not_in_cache',
'hosted',
'pub.dev',
'fake_hosted-1.0.0',
),
)
.toString(),
'packageUri': 'lib/',
},
]);
final telemetry = scanPubspecTelemetry(
tempDir,
environment: {'PUB_CACHE': customCacheDir.path},
);
expect(telemetry, isNotNull);
expect(telemetry!.publicDependencies, {'shelf'});
});
});
group('findPubspecFile', () {
late Directory tempDir;
setUp(() {
tempDir = Directory.systemTemp.createTempSync(
'dartdev_find_pubspec_test_',
);
});
tearDown(() {
try {
tempDir.deleteSync(recursive: true);
} catch (_) {}
});
test('returns null when no pubspec exists', () {
expect(findPubspecFile(tempDir), isNull);
});
test('finds pubspec.yaml in current directory', () {
final pubspec = File(path.join(tempDir.path, 'pubspec.yaml'))
..writeAsStringSync('name: test_pkg\n');
final found = findPubspecFile(tempDir);
expect(found, isNotNull);
expect(found!.path, pubspec.path);
});
test('finds pubspec.yaml in deeply nested directory', () {
final pubspec = File(path.join(tempDir.path, 'pubspec.yaml'))
..writeAsStringSync('name: test_pkg\n');
final nestedDir = Directory(
path.join(
tempDir.path,
'level1',
'level2',
'level3',
'level4',
'l5',
'l6',
'l7',
),
)..createSync(recursive: true);
final found = findPubspecFile(nestedDir);
expect(found, isNotNull);
expect(found!.path, pubspec.path);
});
});
}