[analysis_server] Convert LSP spec tests to test_reflective_loader

No functional changes, this just converts the tests from group/test to test_reflective_loader to match other tests.

Changing the group()/test() calls to methods results in the tests being re-sorted by member name.

To possibly simplify reviewing, I've pushed this to Gerrit as two patch sets:

- PS1: does the conversion but includes index numbers in each test to preserve the order
- PS2: removes the index numbers and re-orders the tests

Change-Id: I72ebe3d3066b181a77052bec5082e4bd34066939
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/504580
Reviewed-by: Keerti Parthasarathy <keertip@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Danny Tuppeny
2026-05-20 13:05:22 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent d3f4debece
commit bb220724dc
6 changed files with 1309 additions and 1305 deletions
@@ -4,56 +4,62 @@
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../tool/lsp_spec/codegen_dart.dart';
import '../../../tool/lsp_spec/meta_model.dart';
void main() {
group('names', () {
test('handles fields named "default"', () {
var generatedCode = generateDartForTypes([
Interface(
name: 'x',
members: [
Field(
name: 'default',
type: TypeReference.string,
allowsNull: false,
allowsUndefined: true,
),
],
),
]);
// Verify the generated code parses with no errors.
parseString(content: generatedCode);
// Verify some expected code.
expect(generatedCode, contains('final String? defaultValue'));
expect(generatedCode, contains('this.defaultValue'));
expect(generatedCode, contains('defaultValue.hashCode'));
// JSON still uses the original protocol name.
expect(generatedCode, contains("result['default'] = defaultValue"));
expect(generatedCode, contains("defaultValueJson = json['default']"));
});
test('handles enum members named "new"', () {
var generatedCode = generateDartForTypes([
LspEnum(
name: 'x',
typeOfValues: TypeReference.int,
members: [
Constant(name: 'new', type: TypeReference.string, value: '1'),
],
),
]);
// Verify the generated code parses with no errors.
parseString(content: generatedCode);
// Verify some expected code.
expect(generatedCode, contains('static const new_ = x(1)'));
});
defineReflectiveSuite(() {
defineReflectiveTests(CodegenTest);
});
}
@reflectiveTest
class CodegenTest {
void test_enumMembersNamedNew() {
var generatedCode = generateDartForTypes([
LspEnum(
name: 'x',
typeOfValues: TypeReference.int,
members: [
Constant(name: 'new', type: TypeReference.string, value: '1'),
],
),
]);
// Verify the generated code parses with no errors.
parseString(content: generatedCode);
// Verify some expected code.
expect(generatedCode, contains('static const new_ = x(1)'));
}
void test_fieldsNamedDefault() {
var generatedCode = generateDartForTypes([
Interface(
name: 'x',
members: [
Field(
name: 'default',
type: TypeReference.string,
allowsNull: false,
allowsUndefined: true,
),
],
),
]);
// Verify the generated code parses with no errors.
parseString(content: generatedCode);
// Verify some expected code.
expect(generatedCode, contains('final String? defaultValue'));
expect(generatedCode, contains('this.defaultValue'));
expect(generatedCode, contains('defaultValue.hashCode'));
// JSON still uses the original protocol name.
expect(generatedCode, contains("result['default'] = defaultValue"));
expect(generatedCode, contains("defaultValueJson = json['default']"));
}
}
@@ -3,29 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../tool/lsp_spec/meta_model.dart' as ast;
void main() {
group('dartType mapping', () {
test('handles basic types', () {
expect(_simple('string').dartType, equals('String'));
expect(_simple('boolean').dartType, equals('bool'));
expect(_simple('object').dartType, equals('Object?'));
expect(_simple('int').dartType, equals('int'));
expect(_simple('num').dartType, equals('num'));
});
test('handles union types', () {
expect(
_union(['string', 'int']).dartTypeWithTypeArgs,
equals('Either2<int, String>'),
);
});
test('handles arrays', () {
expect(_array('string').dartTypeWithTypeArgs, equals('List<String>'));
});
defineReflectiveSuite(() {
defineReflectiveTests(DartTest);
});
}
@@ -35,3 +19,25 @@ ast.TypeReference _simple(String name) => ast.TypeReference(name);
ast.UnionType _union(List<String> names) =>
ast.UnionType(names.map(_simple).toList());
@reflectiveTest
class DartTest {
void test_mapping_arrays() {
expect(_array('string').dartTypeWithTypeArgs, equals('List<String>'));
}
void test_mapping_basicTypes() {
expect(_simple('string').dartType, equals('String'));
expect(_simple('boolean').dartType, equals('bool'));
expect(_simple('object').dartType, equals('Object?'));
expect(_simple('int').dartType, equals('int'));
expect(_simple('num').dartType, equals('num'));
}
void test_mapping_unionTypes() {
expect(
_union(['string', 'int']).dartTypeWithTypeArgs,
equals('Either2<int, String>'),
);
}
}
@@ -4,128 +4,132 @@
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
void main() {
group('generated classes', () {
test('can be checked for equality', () {
var a = TextDocumentIdentifier(uri: Uri.file('/a'));
var b = TextDocumentIdentifier(uri: Uri.file('/a'));
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
});
test('with list fields can be checked for equality', () {
var a = ClientCodeActionKindOptions(valueSet: [CodeActionKind.QuickFix]);
var b = ClientCodeActionKindOptions(valueSet: [CodeActionKind.QuickFix]);
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
});
test('with aliased list fields can be checked for equality', () {
var a = TextDocumentRegistrationOptions(
documentSelector: [
TextDocumentFilterScheme(language: 'dart', scheme: 'file'),
],
);
var b = TextDocumentRegistrationOptions(
documentSelector: [
TextDocumentFilterScheme(language: 'dart', scheme: 'file'),
],
);
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
});
test('with map fields can be checked for equality', () {
var a = WorkspaceEdit(
changes: {
Uri.file('/a'): [
TextEdit(
range: Range(
start: Position(line: 0, character: 0),
end: Position(line: 0, character: 0),
),
newText: 'a',
),
],
},
);
var b = WorkspaceEdit(
changes: {
Uri.file('/a'): [
TextEdit(
range: Range(
start: Position(line: 0, character: 0),
end: Position(line: 0, character: 0),
),
newText: 'a',
),
],
},
);
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
});
test('with unions of lists can be checked for equality', () {
var a = Either2<List<String>, List<int>>.t1(['test']);
var b = Either2<List<String>, List<int>>.t1(['test']);
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
});
test('with union fields can be checked for equality', () {
var a = SignatureInformation(
label: 'a',
documentation: Either2<MarkupContent, String>.t2('a'),
parameters: [],
);
var b = SignatureInformation(
label: 'a',
documentation: Either2<MarkupContent, String>.t2('a'),
parameters: [],
);
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
});
test('consider subclasses when checking for equality', () {
var a = TextDocumentRegistrationOptions(
documentSelector: [
TextDocumentFilterScheme(language: 'dart', scheme: 'file'),
],
);
var b = TextDocumentSaveRegistrationOptions(
includeText: true,
documentSelector: [
TextDocumentFilterScheme(language: 'dart', scheme: 'file'),
],
);
expect(a, isNot(equals(b)));
expect(b, isNot(equals(a)));
});
});
group('interactive forms', () {
test('can deserialize FormFields into the correct subclasses', () {
var stringField = FormField.fromJson({
'type': {'kind': 'string'},
'description': '',
});
expect(stringField.type, isA<FormFieldTypeString>());
var boolField = FormField.fromJson({
'type': {'kind': 'bool'},
'description': '',
});
expect(boolField.type, isA<FormFieldTypeBool>());
});
defineReflectiveSuite(() {
defineReflectiveTests(GeneratedClassesTest);
});
}
@reflectiveTest
class GeneratedClassesTest {
void test_generatedClasses_equality() {
var a = TextDocumentIdentifier(uri: Uri.file('/a'));
var b = TextDocumentIdentifier(uri: Uri.file('/a'));
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
}
void test_generatedClasses_equality_aliasedListFields() {
var a = TextDocumentRegistrationOptions(
documentSelector: [
TextDocumentFilterScheme(language: 'dart', scheme: 'file'),
],
);
var b = TextDocumentRegistrationOptions(
documentSelector: [
TextDocumentFilterScheme(language: 'dart', scheme: 'file'),
],
);
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
}
void test_generatedClasses_equality_listField() {
var a = ClientCodeActionKindOptions(valueSet: [CodeActionKind.QuickFix]);
var b = ClientCodeActionKindOptions(valueSet: [CodeActionKind.QuickFix]);
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
}
void test_generatedClasses_equality_mapField() {
var a = WorkspaceEdit(
changes: {
Uri.file('/a'): [
TextEdit(
range: Range(
start: Position(line: 0, character: 0),
end: Position(line: 0, character: 0),
),
newText: 'a',
),
],
},
);
var b = WorkspaceEdit(
changes: {
Uri.file('/a'): [
TextEdit(
range: Range(
start: Position(line: 0, character: 0),
end: Position(line: 0, character: 0),
),
newText: 'a',
),
],
},
);
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
}
void test_generatedClasses_equality_subclasses() {
var a = TextDocumentRegistrationOptions(
documentSelector: [
TextDocumentFilterScheme(language: 'dart', scheme: 'file'),
],
);
var b = TextDocumentSaveRegistrationOptions(
includeText: true,
documentSelector: [
TextDocumentFilterScheme(language: 'dart', scheme: 'file'),
],
);
expect(a, isNot(equals(b)));
expect(b, isNot(equals(a)));
}
void test_generatedClasses_equality_unionFields() {
var a = SignatureInformation(
label: 'a',
documentation: Either2<MarkupContent, String>.t2('a'),
parameters: [],
);
var b = SignatureInformation(
label: 'a',
documentation: Either2<MarkupContent, String>.t2('a'),
parameters: [],
);
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
}
void test_generatedClasses_equality_unionsOfLists() {
var a = Either2<List<String>, List<int>>.t1(['test']);
var b = Either2<List<String>, List<int>>.t1(['test']);
expect(a, equals(b));
expect(a.hashCode, equals(b.hashCode));
}
void test_interactiveForms_deserialize_formFieldsIntoSubclasses() {
var stringField = FormField.fromJson({
'type': {'kind': 'string'},
'description': '',
});
expect(stringField.type, isA<FormFieldTypeString>());
var boolField = FormField.fromJson({
'type': {'kind': 'bool'},
'description': '',
});
expect(boolField.type, isA<FormFieldTypeBool>());
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -9,90 +9,100 @@ import 'package:analysis_server/src/lsp/handlers/handler_states.dart';
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../tool/lsp_spec/meta_model.dart';
void main() {
var serverPkgPath = _getAnalysisServerPkgPath();
var readmeFile = File(path.join(serverPkgPath, 'tool/lsp_spec/README.md'));
var metaModelJsonFile = File(
defineReflectiveSuite(() {
defineReflectiveTests(ReadmeTest);
});
}
@reflectiveTest
class ReadmeTest {
late final metaModelJsonFile = File(
path.join(
serverPkgPath,
'../../third_party/pkg/language_server_protocol/lsp_meta_model.json',
),
);
group('LSP readme', () {
test('contains all methods', () {
var readmeContent = readmeFile.readAsStringSync();
var model = LspMetaModelReader().readFile(metaModelJsonFile);
model = LspMetaModelCleaner().cleanModel(model);
late final readmeFile = File(
path.join(serverPkgPath, 'tool/lsp_spec/README.md'),
);
var missingMethods = StringBuffer();
for (var method in model.methods) {
// Handle `foo/*` in the readme as well as `foo/bar`.
var methodName = method.value;
var methodWildcard = methodName.replaceAll(RegExp(r'\/[^\/]+$'), '/*');
if (!readmeContent.contains(' $methodName ') &&
!readmeContent.contains(' $methodWildcard ')) {
missingMethods.writeln(methodName);
late final serverPkgPath = _getAnalysisServerPkgPath();
void test_readme_containsAllMethods() {
var readmeContent = readmeFile.readAsStringSync();
var model = LspMetaModelReader().readFile(metaModelJsonFile);
model = LspMetaModelCleaner().cleanModel(model);
var missingMethods = StringBuffer();
for (var method in model.methods) {
// Handle `foo/*` in the readme as well as `foo/bar`.
var methodName = method.value;
var methodWildcard = methodName.replaceAll(RegExp(r'\/[^\/]+$'), '/*');
if (!readmeContent.contains(' $methodName ') &&
!readmeContent.contains(' $methodWildcard ')) {
missingMethods.writeln(methodName);
}
}
if (missingMethods.isNotEmpty) {
fail(
'The following Methods are not listed in the README.md file:\n\n'
'$missingMethods',
);
}
}
void test_readme_implementedMethodsTicked() {
var readmeContent = readmeFile.readAsStringSync();
var handlerGenerators = [
...InitializedLspStateMessageHandler.lspHandlerGenerators,
...InitializedStateMessageHandler.sharedHandlerGenerators,
];
var missingMethods = StringBuffer();
for (var generator in handlerGenerators) {
var handler = generator(_MockServer());
var method = handler.handlesMessage.toString();
if (method.startsWith('experimental/')) {
// Experimental handlers may change frequently, exclude them.
} else if (method.startsWith('dart')) {
// Dart methods are included under their own heading.
var expectedHeading = '### $method Method';
if (!readmeContent.contains(expectedHeading)) {
missingMethods.writeln('$method does not have a section');
}
} else {
// Standard methods should be listed in the table and ticked.
var escapedMethod = RegExp.escape(method);
var expectedMarkdown = RegExp(' $escapedMethod .*\\| ✅ \\|');
if (!readmeContent.contains(expectedMarkdown)) {
missingMethods.writeln('$method is not listed/ticked in the table');
}
}
}
if (missingMethods.isNotEmpty) {
fail(
'The following Methods are not listed in the README.md file:\n\n'
'$missingMethods',
);
}
});
if (missingMethods.isNotEmpty) {
fail(
'The following are not listed correctly in the README.md file:\n\n'
'$missingMethods',
);
}
}
test('has implemented methods ticked', () {
var readmeContent = readmeFile.readAsStringSync();
var handlerGenerators = [
...InitializedLspStateMessageHandler.lspHandlerGenerators,
...InitializedStateMessageHandler.sharedHandlerGenerators,
];
var missingMethods = StringBuffer();
for (var generator in handlerGenerators) {
var handler = generator(_MockServer());
var method = handler.handlesMessage.toString();
if (method.startsWith('experimental/')) {
// Experimental handlers may change frequently, exclude them.
} else if (method.startsWith('dart')) {
// Dart methods are included under their own heading.
var expectedHeading = '### $method Method';
if (!readmeContent.contains(expectedHeading)) {
missingMethods.writeln('$method does not have a section');
}
} else {
// Standard methods should be listed in the table and ticked.
var escapedMethod = RegExp.escape(method);
var expectedMarkdown = RegExp(' $escapedMethod .*\\| ✅ \\|');
if (!readmeContent.contains(expectedMarkdown)) {
missingMethods.writeln('$method is not listed/ticked in the table');
}
}
}
if (missingMethods.isNotEmpty) {
fail(
'The following are not listed correctly in the README.md file:\n\n'
'$missingMethods',
);
}
});
});
}
String _getAnalysisServerPkgPath() {
var script = Platform.script.toFilePath();
var components = path.split(script);
var index = components.indexOf('analysis_server');
return path.joinAll(components.sublist(0, index + 1));
String _getAnalysisServerPkgPath() {
var script = Platform.script.toFilePath();
var components = path.split(script);
var index = components.indexOf('analysis_server');
return path.joinAll(components.sublist(0, index + 1));
}
}
class _MockServer implements LspAnalysisServer {