Skip to content

Commit c1366a4

Browse files
scheglovCommit Queue
authored andcommitted
Fine. Add a few tests that ask element model manually.
These tests just record requirements, but don't verify that these requirements cause re-computation of the result if changed. My current thinking is that we might have two main axis of tests: library manifests that verify that we give new IDs to changed elements; and requirements that verify that asking a piece of data from the element model is recorded as a requirement (or not, if this piece of data already cause the element ID to change). We need at least one test for each requirement kind that verifies that such requirement does cause re-computation. But we don't need to do this in every test. So, we avoid combinatorial explosion of cases? E.g. if there are two requirements recorded, we don't want to try all 4 combinations how they change. Change-Id: Iab9b0f1cd7749bc4f3c404efa340b4722bb0214e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/426661 Reviewed-by: Paul Berry <[email protected]> Commit-Queue: Konstantin Shcheglov <[email protected]>
1 parent 50e0e0d commit c1366a4

File tree

4 files changed

+357
-9
lines changed

4 files changed

+357
-9
lines changed

pkg/analyzer/lib/src/dart/analysis/driver.dart

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import 'dart:typed_data';
88
import 'package:analyzer/dart/analysis/analysis_options.dart';
99
import 'package:analyzer/dart/analysis/declared_variables.dart';
1010
import 'package:analyzer/dart/analysis/results.dart';
11-
import 'package:analyzer/dart/ast/ast.dart';
1211
import 'package:analyzer/diagnostic/diagnostic.dart';
1312
import 'package:analyzer/error/error.dart';
1413
import 'package:analyzer/error/listener.dart';
@@ -36,6 +35,7 @@ import 'package:analyzer/src/dart/analysis/session.dart';
3635
import 'package:analyzer/src/dart/analysis/status.dart';
3736
import 'package:analyzer/src/dart/analysis/testing_data.dart';
3837
import 'package:analyzer/src/dart/analysis/unlinked_unit_store.dart';
38+
import 'package:analyzer/src/dart/ast/ast.dart';
3939
import 'package:analyzer/src/dart/element/element.dart';
4040
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
4141
import 'package:analyzer/src/dartdoc/dartdoc_directive_info.dart';
@@ -65,6 +65,15 @@ import 'package:analyzer/src/workspace/pub.dart';
6565
import 'package:collection/collection.dart';
6666
import 'package:meta/meta.dart';
6767

68+
/// This function is used to test recording requirements during analysis.
69+
///
70+
/// Some [ElementImpl2] APIs are not trivial, or maybe even impossible, to
71+
/// trigger. For example because this API is not used during normal resolution
72+
/// of Dart code, but can be used by a linter rule.
73+
@visibleForTesting
74+
void Function(List<CompilationUnitImpl> units)?
75+
testFineAfterLibraryAnalyzerHook;
76+
6877
/// This class computes analysis results for Dart files.
6978
///
7079
/// Let the set of "explicitly analyzed files" denote the set of paths that have
@@ -1376,6 +1385,12 @@ class AnalysisDriver {
13761385
).analyze();
13771386
});
13781387

1388+
// Invoke the test only hook to trigger additional requirements.
1389+
if (testFineAfterLibraryAnalyzerHook case var hook?) {
1390+
var units = results.map((result) => result.unit).toList();
1391+
hook(units);
1392+
}
1393+
13791394
if (withFineDependencies) {
13801395
globalResultRequirements = null;
13811396
}

pkg/analyzer/test/src/dart/analysis/driver_test.dart

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import 'package:analyzer/src/dart/analysis/driver_event.dart' as driver_events;
1515
import 'package:analyzer/src/dart/analysis/file_state.dart';
1616
import 'package:analyzer/src/dart/analysis/status.dart';
1717
import 'package:analyzer/src/dart/ast/ast.dart';
18+
import 'package:analyzer/src/dart/element/element.dart';
1819
import 'package:analyzer/src/error/codes.dart';
1920
import 'package:analyzer/src/fine/requirements.dart';
2021
import 'package:analyzer/src/lint/linter.dart';
@@ -5450,6 +5451,7 @@ class FineAnalysisDriverTest extends PubPackageResolutionTest
54505451

54515452
@override
54525453
Future<void> tearDown() async {
5454+
testFineAfterLibraryAnalyzerHook = null;
54535455
withFineDependencies = false;
54545456
return super.tearDown();
54555457
}
@@ -36890,6 +36892,251 @@ int get b => 0;
3689036892
);
3689136893
}
3689236894

36895+
test_req_instanceElement_getGetter() async {
36896+
newFile('$testPackageLibPath/a.dart', r'''
36897+
class A {
36898+
static int get foo {}
36899+
}
36900+
''');
36901+
36902+
newFile(testFile.path, r'''
36903+
import 'a.dart';
36904+
''');
36905+
36906+
_ManualRequirements.install((state) {
36907+
var A = state.singleUnit.scopeInstanceElement('A');
36908+
A.getGetter2('foo');
36909+
});
36910+
36911+
await _runManualRequirementsRecording(
36912+
expectedEvents: r'''
36913+
[status] working
36914+
[operation] linkLibraryCycle SDK
36915+
[operation] linkLibraryCycle
36916+
package:test/a.dart
36917+
manifest
36918+
A: #M0
36919+
declaredMembers
36920+
foo.getter: #M1
36921+
requirements
36922+
topLevels
36923+
dart:core
36924+
int: #M2
36925+
[operation] linkLibraryCycle
36926+
package:test/test.dart
36927+
requirements
36928+
[operation] analyzedLibrary
36929+
file: /home/test/lib/test.dart
36930+
requirements
36931+
topLevels
36932+
dart:core
36933+
A: <null>
36934+
package:test/a.dart
36935+
A: #M0
36936+
instances
36937+
package:test/a.dart
36938+
A
36939+
requestedMethods
36940+
foo: #M1
36941+
[status] idle
36942+
''',
36943+
);
36944+
}
36945+
36946+
test_req_instanceElement_getMethod() async {
36947+
newFile('$testPackageLibPath/a.dart', r'''
36948+
class A {
36949+
static int foo() {}
36950+
}
36951+
''');
36952+
36953+
newFile(testFile.path, r'''
36954+
import 'a.dart';
36955+
''');
36956+
36957+
_ManualRequirements.install((state) {
36958+
var A = state.singleUnit.scopeInstanceElement('A');
36959+
A.getMethod2('foo');
36960+
});
36961+
36962+
await _runManualRequirementsRecording(
36963+
expectedEvents: r'''
36964+
[status] working
36965+
[operation] linkLibraryCycle SDK
36966+
[operation] linkLibraryCycle
36967+
package:test/a.dart
36968+
manifest
36969+
A: #M0
36970+
declaredMembers
36971+
foo.method: #M1
36972+
requirements
36973+
topLevels
36974+
dart:core
36975+
int: #M2
36976+
[operation] linkLibraryCycle
36977+
package:test/test.dart
36978+
requirements
36979+
[operation] analyzedLibrary
36980+
file: /home/test/lib/test.dart
36981+
requirements
36982+
topLevels
36983+
dart:core
36984+
A: <null>
36985+
package:test/a.dart
36986+
A: #M0
36987+
instances
36988+
package:test/a.dart
36989+
A
36990+
requestedMethods
36991+
foo: #M1
36992+
[status] idle
36993+
''',
36994+
);
36995+
}
36996+
36997+
test_req_instanceElement_getMethod_doesNotExist() async {
36998+
newFile('$testPackageLibPath/a.dart', r'''
36999+
class A {}
37000+
''');
37001+
37002+
newFile(testFile.path, r'''
37003+
import 'a.dart';
37004+
''');
37005+
37006+
_ManualRequirements.install((state) {
37007+
var A = state.singleUnit.scopeInstanceElement('A');
37008+
A.getMethod2('foo');
37009+
});
37010+
37011+
await _runManualRequirementsRecording(
37012+
expectedEvents: r'''
37013+
[status] working
37014+
[operation] linkLibraryCycle SDK
37015+
[operation] linkLibraryCycle
37016+
package:test/a.dart
37017+
manifest
37018+
A: #M0
37019+
requirements
37020+
[operation] linkLibraryCycle
37021+
package:test/test.dart
37022+
requirements
37023+
[operation] analyzedLibrary
37024+
file: /home/test/lib/test.dart
37025+
requirements
37026+
topLevels
37027+
dart:core
37028+
A: <null>
37029+
package:test/a.dart
37030+
A: #M0
37031+
instances
37032+
package:test/a.dart
37033+
A
37034+
requestedMethods
37035+
foo: <null>
37036+
[status] idle
37037+
''',
37038+
);
37039+
}
37040+
37041+
test_req_instanceElement_getSetter() async {
37042+
newFile('$testPackageLibPath/a.dart', r'''
37043+
class A {
37044+
static set foo(int _) {}
37045+
}
37046+
''');
37047+
37048+
newFile(testFile.path, r'''
37049+
import 'a.dart';
37050+
''');
37051+
37052+
_ManualRequirements.install((state) {
37053+
var A = state.singleUnit.scopeInstanceElement('A');
37054+
A.getSetter2('foo');
37055+
});
37056+
37057+
await _runManualRequirementsRecording(
37058+
expectedEvents: r'''
37059+
[status] working
37060+
[operation] linkLibraryCycle SDK
37061+
[operation] linkLibraryCycle
37062+
package:test/a.dart
37063+
manifest
37064+
A: #M0
37065+
declaredMembers
37066+
foo.setter: #M1
37067+
requirements
37068+
topLevels
37069+
dart:core
37070+
int: #M2
37071+
[operation] linkLibraryCycle
37072+
package:test/test.dart
37073+
requirements
37074+
[operation] analyzedLibrary
37075+
file: /home/test/lib/test.dart
37076+
requirements
37077+
topLevels
37078+
dart:core
37079+
A: <null>
37080+
package:test/a.dart
37081+
A: #M0
37082+
instances
37083+
package:test/a.dart
37084+
A
37085+
requestedMethods
37086+
foo=: #M1
37087+
[status] idle
37088+
''',
37089+
);
37090+
}
37091+
37092+
test_req_interfaceElement_getConstructor_named() async {
37093+
newFile('$testPackageLibPath/a.dart', r'''
37094+
class A {
37095+
A.named();
37096+
}
37097+
''');
37098+
37099+
newFile(testFile.path, r'''
37100+
import 'a.dart';
37101+
''');
37102+
37103+
_ManualRequirements.install((state) {
37104+
var A = state.singleUnit.scopeInterfaceElement('A');
37105+
A.getNamedConstructor2('named');
37106+
});
37107+
37108+
await _runManualRequirementsRecording(
37109+
expectedEvents: r'''
37110+
[status] working
37111+
[operation] linkLibraryCycle SDK
37112+
[operation] linkLibraryCycle
37113+
package:test/a.dart
37114+
manifest
37115+
A: #M0
37116+
declaredMembers
37117+
named.constructor: #M1
37118+
requirements
37119+
[operation] linkLibraryCycle
37120+
package:test/test.dart
37121+
requirements
37122+
[operation] analyzedLibrary
37123+
file: /home/test/lib/test.dart
37124+
requirements
37125+
topLevels
37126+
dart:core
37127+
A: <null>
37128+
package:test/a.dart
37129+
A: #M0
37130+
interfaces
37131+
package:test/a.dart
37132+
A
37133+
constructors
37134+
named: #M1
37135+
[status] idle
37136+
''',
37137+
);
37138+
}
37139+
3689337140
Future<void> _runChangeScenario({
3689437141
required _FineOperation operation,
3689537142
String? expectedInitialEvents,
@@ -37031,6 +37278,27 @@ int get b => 0;
3703137278
assertDriverStateString(testFile, expectedUpdatedDriverState);
3703237279
}
3703337280
}
37281+
37282+
/// Works together with [_ManualRequirements] to execute manual requests to
37283+
/// the element model, and observe which requirements are recorded.
37284+
Future<void> _runManualRequirementsRecording({
37285+
required String expectedEvents,
37286+
}) async {
37287+
withFineDependencies = true;
37288+
configuration
37289+
..withAnalyzeFileEvents = false
37290+
..withLibraryManifest = true
37291+
..withLinkBundleEvents = true
37292+
..withGetErrorsEvents = false
37293+
..withResultRequirements = true
37294+
..withStreamResolvedUnitResults = false;
37295+
37296+
var driver = driverFor(testFile);
37297+
var collector = DriverEventCollector(driver, idProvider: idProvider);
37298+
37299+
collector.getErrors('T1', testFile);
37300+
await assertEventsText(collector, expectedEvents);
37301+
}
3703437302
}
3703537303

3703637304
/// A lint that is always reported for all linted files.
@@ -37122,6 +37390,52 @@ final class _FineOperationTestFileGetErrors extends _FineOperation {
3712237390
const _FineOperationTestFileGetErrors();
3712337391
}
3712437392

37393+
/// Helper for triggering requirements manually.
37394+
///
37395+
/// Some [Element] APIs are not trivial, or maybe even impossible, to
37396+
/// trigger. For example because this API is not used during normal resolution
37397+
/// of Dart code, but can be used by a linter rule.
37398+
class _ManualRequirements {
37399+
final List<CompilationUnitImpl> units;
37400+
37401+
_ManualRequirements(this.units);
37402+
37403+
_ManualRequirementsUnit get singleUnit {
37404+
var unit = units.single;
37405+
return _ManualRequirementsUnit(unit);
37406+
}
37407+
37408+
static void install(void Function(_ManualRequirements) operation) {
37409+
testFineAfterLibraryAnalyzerHook = (units) {
37410+
var self = _ManualRequirements(units);
37411+
operation(self);
37412+
};
37413+
}
37414+
}
37415+
37416+
class _ManualRequirementsUnit {
37417+
final CompilationUnitImpl unit;
37418+
37419+
_ManualRequirementsUnit(this.unit);
37420+
37421+
LibraryFragmentImpl get libraryFragment {
37422+
return unit.declaredFragment!;
37423+
}
37424+
37425+
ClassElementImpl2 scopeClassElement(String name) {
37426+
return scopeInterfaceElement(name) as ClassElementImpl2;
37427+
}
37428+
37429+
InstanceElementImpl2 scopeInstanceElement(String name) {
37430+
var lookupResult = libraryFragment.scope.lookup(name);
37431+
return lookupResult.getter2 as InstanceElementImpl2;
37432+
}
37433+
37434+
InterfaceElementImpl2 scopeInterfaceElement(String name) {
37435+
return scopeInstanceElement(name) as InterfaceElementImpl2;
37436+
}
37437+
}
37438+
3712537439
extension on AnalysisDriver {
3712637440
Future<void> assertFilesDefiningClassMemberName(
3712737441
String name,

0 commit comments

Comments
 (0)