blob: 8f688a4c598bb724422a7c47fefec138c5491eb8 [file] [log] [blame]
// Copyright (c) 2020, 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 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/micro/cider_byte_store.dart';
import 'package:analyzer/src/dart/micro/library_graph.dart';
import 'package:analyzer/src/dart/micro/resolve_file.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/test_utilities/find_element.dart';
import 'package:analyzer/src/test_utilities/find_node.dart';
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer/src/workspace/bazel.dart';
import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart';
import 'package:linter/src/rules.dart';
import 'package:path/path.dart';
import 'package:test/test.dart';
import '../resolution/resolution.dart';
/// [FileResolver] based implementation of [ResolutionTest].
class FileResolutionTest with ResourceProviderMixin, ResolutionTest {
static final String _testFile = '/workspace/dart/test/lib/test.dart';
final MemoryCiderByteStore byteStore = MemoryCiderByteStore();
final FileResolverTestView testData = FileResolverTestView();
final StringBuffer logBuffer = StringBuffer();
late PerformanceLog logger;
late FileResolver fileResolver;
final _KeyShorter _keyShorter = _KeyShorter();
FileSystemState get fsState => fileResolver.fsState!;
LibraryContext get libraryContext {
return fileResolver.libraryContext!;
}
Folder get sdkRoot => newFolder('/sdk');
File get testFile => getFile(testFilePath);
@override
String get testFilePath => _testFile;
String get testPackageLibPath => '$testPackageRootPath/lib';
String get testPackageRootPath => '$workspaceRootPath/dart/test';
String get workspaceRootPath => '/workspace';
@override
void addTestFile(String content) {
newFile(_testFile, content);
}
void assertStateString(String expected) {
final buffer = StringBuffer();
ResolverStatePrinter(resourceProvider, buffer, _keyShorter)
.write(byteStore, fileResolver.fsState!, libraryContext, testData);
final actual = buffer.toString();
if (actual != expected) {
print(actual);
}
expect(actual, expected);
}
/// Create a new [FileResolver] into [fileResolver].
///
/// We do this the first time, and to test reusing results from [byteStore].
void createFileResolver() {
var workspace = BazelWorkspace.find(
resourceProvider,
convertPath(_testFile),
)!;
byteStore.testView = CiderByteStoreTestView();
fileResolver = FileResolver(
logger: logger,
resourceProvider: resourceProvider,
byteStore: byteStore,
sourceFactory: workspace.createSourceFactory(
FolderBasedDartSdk(resourceProvider, sdkRoot),
null,
),
getFileDigest: (String path) => _getDigest(path),
workspace: workspace,
prefetchFiles: null,
isGenerated: null,
);
fileResolver.testView = testData;
}
Future<ErrorsResult> getTestErrors() async {
var path = convertPath(_testFile);
return fileResolver.getErrors2(path: path);
}
@override
Future<ResolvedUnitResult> resolveFile(
String path, {
OperationPerformanceImpl? performance,
}) async {
result = await fileResolver.resolve2(
path: path,
performance: performance,
);
return result;
}
@override
Future<void> resolveTestFile() async {
var path = convertPath(_testFile);
result = await resolveFile(path);
findNode = FindNode(result.content, result.unit);
findElement = FindElement(result.unit);
}
void setUp() {
registerLintRules();
logger = PerformanceLog(logBuffer);
createMockSdk(
resourceProvider: resourceProvider,
root: sdkRoot,
);
newFile('/workspace/WORKSPACE', '');
newFile('/workspace/dart/test/BUILD', r'''
dart_package(
null_safety = True,
)
''');
createFileResolver();
}
String _getDigest(String path) {
try {
var content = resourceProvider.getFile(path).readAsStringSync();
var contentBytes = utf8.encode(content);
return md5.convert(contentBytes).toString();
} catch (_) {
return '';
}
}
}
class ResolverStatePrinter {
final ResourceProvider _resourceProvider;
/// The target sink to print.
final StringSink _sink;
final _KeyShorter _keyShorter;
String _indent = '';
ResolverStatePrinter(this._resourceProvider, this._sink, this._keyShorter);
void write(MemoryCiderByteStore byteStore, FileSystemState fileSystemState,
LibraryContext libraryContext, FileResolverTestView testData) {
_writelnWithIndent('files');
_withIndent(() {
final fileMap = testData.fileSystemTestData.files;
final fileDataList = fileMap.values.toList();
fileDataList.sortBy((fileData) => fileData.file.path);
for (final fileData in fileDataList) {
final file = fileData.file;
_writelnWithIndent(_posixPath(file));
_withIndent(() {
final current = fileSystemState.getExistingFileForResource(file);
if (current != null) {
_writelnWithIndent('current');
_withIndent(() {
final unlinkedShort = _keyShorter.shortKey(current.unlinkedKey);
_writelnWithIndent('unlinkedKey: $unlinkedShort');
});
}
final shortGets = _keyShorter.shortKeys(fileData.unlinkedKeyGet);
final shortPuts = _keyShorter.shortKeys(fileData.unlinkedKeyPut);
_writelnWithIndent('unlinkedGet: $shortGets');
_writelnWithIndent('unlinkedPut: $shortPuts');
});
}
});
_writelnWithIndent('libraryCycles');
_withIndent(() {
final entries = testData.libraryCycles.entries
.mapKey((key) => key.map(_posixPath).join(' '))
.toList();
entries.sortBy((e) => e.key);
final loadedBundlesMap = Map.fromEntries(
libraryContext.loadedBundles.map((cycle) {
final key = cycle.libraries
.map((fileState) => fileState.resource)
.map(_posixPath)
.join(' ');
return MapEntry(key, cycle);
}),
);
for (final entry in entries) {
_writelnWithIndent(entry.key);
_withIndent(() {
final current = loadedBundlesMap[entry.key];
if (current != null) {
_writelnWithIndent('current');
_withIndent(() {
final short = _keyShorter.shortKey(current.resolutionKey!);
_writelnWithIndent('key: $short');
});
}
final shortGets = _keyShorter.shortKeys(entry.value.getKeys);
final shortPuts = _keyShorter.shortKeys(entry.value.putKeys);
_writelnWithIndent('get: $shortGets');
_writelnWithIndent('put: $shortPuts');
});
}
});
_writelnWithIndent('elementFactory');
_withIndent(() {
final elementFactory = libraryContext.elementFactory;
_writeUriList(
'hasElement',
elementFactory.uriListWithLibraryElements,
);
_writeUriList(
'hasReader',
elementFactory.uriListWithLibraryReaders,
);
});
_writelnWithIndent('byteStore');
_withIndent(() {
final groups = byteStore.map.entries.groupListsBy((element) {
return element.value.refCount;
});
for (final groupEntry in groups.entries) {
final keys = groupEntry.value.map((e) => e.key).toList();
final shortKeys = _keyShorter.shortKeys(keys)..sort();
_writelnWithIndent('${groupEntry.key}: $shortKeys');
}
});
}
/// If the path style is `Windows`, returns the corresponding Posix path.
/// Otherwise the path is already a Posix path, and it is returned as is.
String _posixPath(File file) {
final pathContext = _resourceProvider.pathContext;
if (pathContext.style == Style.windows) {
final components = pathContext.split(file.path);
return '/${components.skip(1).join('/')}';
} else {
return file.path;
}
}
void _withIndent(void Function() f) {
var indent = _indent;
_indent = '$_indent ';
f();
_indent = indent;
}
void _writelnWithIndent(String line) {
_sink.write(_indent);
_sink.writeln(line);
}
void _writeUriList(String name, Iterable<Uri> uriIterable) {
final uriStrList = uriIterable.map((uri) => '$uri').toList();
if (uriStrList.isNotEmpty) {
uriStrList.sort();
_writelnWithIndent(name);
_withIndent(() {
for (final uriStr in uriStrList) {
_writelnWithIndent(uriStr);
}
});
}
}
}
/// Keys in the byte store are long hashes, which are hard to read.
/// So, we generate short unique versions for them.
class _KeyShorter {
final Map<String, String> _keyToShort = {};
final Map<String, String> _shortToKey = {};
String shortKey(String key) {
var short = _keyToShort[key];
if (short == null) {
short = 'k${_keyToShort.length.toString().padLeft(2, '0')}';
_keyToShort[key] = short;
_shortToKey[short] = key;
}
return short;
}
List<String> shortKeys(List<String> keys) {
return keys.map(shortKey).toList();
}
}
extension<K, V> on Iterable<MapEntry<K, V>> {
Iterable<MapEntry<K2, V>> mapKey<K2>(K2 Function(K key) convertKey) {
return map((e) {
final newKey = convertKey(e.key);
return MapEntry(newKey, e.value);
});
}
}