[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:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
d3f4debece
commit
bb220724dc
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user