blob: 2589a6b26de303456d708860149f6da2c5608204 [file] [log] [blame]
// Copyright (c) 2024, 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:async';
import 'dart:io';
import 'package:_macro_client/macro_client.dart';
import 'package:_test_macros/declare_x_macro.dart';
import 'package:_test_macros/query_class.dart';
import 'package:async/async.dart';
import 'package:dart_model/dart_model.dart';
import 'package:macro_service/macro_service.dart';
import 'package:test/test.dart';
void main() {
final fooTarget = QualifiedName(name: 'Foo', uri: 'package:foo/foo.dart');
final fooModel = Scope.query.run(
() =>
Model()
..uris[fooTarget.uri] =
(Library()
..scopes['Foo'] = Interface(
properties: Properties(isClass: true),
)),
);
for (final protocol in [
Protocol(encoding: ProtocolEncoding.json, version: ProtocolVersion.macros1),
Protocol(
encoding: ProtocolEncoding.binary,
version: ProtocolVersion.macros1,
),
]) {
group('MacroClient using ${protocol.encoding}', () {
/// Waits for [HandshakRequest] on [socket], sends response choosing
/// [protocol], returns next responses decoded using [protocol].
Future<StreamQueue<Map<String, Object?>>> handshake(Socket socket) async {
final broadcastStream = socket.asBroadcastStream();
final handshakeRequest =
await Protocol.handshakeProtocol.decode(broadcastStream).first;
final result = StreamQueue(protocol.decode(broadcastStream));
expect(handshakeRequest, {
'protocols': [
{'encoding': 'json', 'version': 'macros1'},
{'encoding': 'binary', 'version': 'macros1'},
],
});
Protocol.handshakeProtocol.send(
socket.add,
HandshakeResponse(protocol: protocol).node,
);
return result;
}
test('connects to service', () async {
final serverSocket = await ServerSocket.bind('localhost', 0);
addTearDown(serverSocket.close);
unawaited(
MacroClient.run(
endpoint: HostEndpoint(port: serverSocket.port),
macros: [DeclareXImplementation()],
),
);
final socket = await serverSocket.first;
await handshake(socket);
});
test('error response if no such macro', () async {
final serverSocket = await ServerSocket.bind('localhost', 0);
unawaited(
MacroClient.run(
endpoint: HostEndpoint(port: serverSocket.port),
macros: [QueryClassImplementation()],
),
);
final socket = await serverSocket.first;
final responses = await handshake(socket);
// MacroStartedRequest, ignore.
await responses.next;
final requestId = nextRequestId;
protocol.send(
socket.add,
HostRequest.augmentRequest(
id: requestId,
macroAnnotation: QualifiedName(
uri: 'package:_test_macros/declare_x_macro.dart',
name: 'DeclareX',
),
AugmentRequest(phase: 2, target: fooTarget, model: fooModel),
).node,
);
final augmentResponse = await responses.next;
expect(augmentResponse, {
'requestId': requestId,
'type': 'ErrorResponse',
'value': {
'error':
'No macro for annotation: '
'package:_test_macros/declare_x_macro.dart#DeclareX',
},
});
});
test('sends augmentation requests to macros, sends response', () async {
final serverSocket = await ServerSocket.bind('localhost', 0);
unawaited(
MacroClient.run(
endpoint: HostEndpoint(port: serverSocket.port),
macros: [DeclareXImplementation()],
),
);
final socket = await serverSocket.first;
final responses = await handshake(socket);
final descriptionResponse = await responses.next;
expect(descriptionResponse, {
'id': descriptionResponse['id'],
'type': 'MacroStartedRequest',
'value': {
'macroDescription': {
'annotation': {
'uri': 'package:_test_macros/declare_x_macro.dart',
'name': 'DeclareX',
},
'runsInPhases': [2],
},
},
});
final requestId = nextRequestId;
protocol.send(
socket.add,
HostRequest.augmentRequest(
id: requestId,
macroAnnotation: QualifiedName(
uri: 'package:_test_macros/declare_x_macro.dart',
name: 'DeclareX',
),
AugmentRequest(phase: 2, target: fooTarget, model: fooModel),
).node,
);
final augmentResponse = await responses.next;
expect(augmentResponse, {
'requestId': requestId,
'type': 'AugmentResponse',
'value': {
'enumValueAugmentations': <String, Object?>{},
'extendsTypeAugmentations': <String, Object?>{},
'interfaceAugmentations': <String, Object?>{},
'mixinAugmentations': <String, Object?>{},
'libraryAugmentations': <Object?>[],
'newTypeNames': <String>[],
'typeAugmentations': {
'Foo': [
{
'code': [
{'type': 'String', 'value': 'int get x => 3;'},
],
},
],
},
},
});
});
test('sends query requests to host, sends response', () async {
final serverSocket = await ServerSocket.bind('localhost', 0);
unawaited(
MacroClient.run(
endpoint: HostEndpoint(port: serverSocket.port),
macros: [QueryClassImplementation()],
),
);
final socket = await serverSocket.first;
final responses = await handshake(socket);
final descriptionResponse = await responses.next;
expect(descriptionResponse, {
'id': descriptionResponse['id'],
'type': 'MacroStartedRequest',
'value': {
'macroDescription': {
'annotation': {
'uri': 'package:_test_macros/query_class.dart',
'name': 'QueryClass',
},
'runsInPhases': [3],
},
},
});
final requestId = nextRequestId;
protocol.send(
socket.add,
HostRequest.augmentRequest(
id: requestId,
macroAnnotation: QualifiedName(
uri: 'package:_test_macros/query_class.dart',
name: 'QueryClass',
),
AugmentRequest(phase: 3, target: fooTarget, model: fooModel),
).node,
);
final queryRequest = await responses.next;
final queryRequestId = MacroRequest.fromJson(queryRequest).id;
expect(queryRequest, {
'id': queryRequestId,
'type': 'QueryRequest',
'value': {
'query': {'target': fooTarget},
},
});
Scope.query.run(
() => protocol.send(
socket.add,
Response.queryResponse(
QueryResponse(model: fooModel),
requestId: queryRequestId,
).node,
),
);
final augmentResponse = await responses.next;
expect(augmentResponse, {
'requestId': requestId,
'type': 'AugmentResponse',
'value': {
'enumValueAugmentations': <String, Object?>{},
'extendsTypeAugmentations': <String, Object?>{},
'interfaceAugmentations': <String, Object?>{},
'mixinAugmentations': <String, Object?>{},
'libraryAugmentations': <Object?>[],
'newTypeNames': <String>[],
'typeAugmentations': {
'Foo': [
{
'code': [
{
'type': 'String',
'value':
'// {"uris":{"${fooTarget.uri}":{"scopes":{"Foo":'
'{"members":{},"properties":{"isClass":true}}}}}}',
},
],
},
],
},
},
});
});
test('handles concurrent queries', () async {
final serverSocket = await ServerSocket.bind('localhost', 0);
unawaited(
MacroClient.run(
endpoint: HostEndpoint(port: serverSocket.port),
macros: [QueryClassImplementation()],
),
);
final socket = await serverSocket.first;
final responses = await handshake(socket);
// MacroStartedRequest, ignore.
await responses.next;
final requestId1 = nextRequestId;
final requestId2 = nextRequestId;
for (final requestId in [requestId1, requestId2]) {
protocol.send(
socket.add,
HostRequest.augmentRequest(
id: requestId,
macroAnnotation: QualifiedName(
uri: 'package:_test_macros/query_class.dart',
name: 'QueryClass',
),
AugmentRequest(phase: 3, target: fooTarget, model: fooModel),
).node,
);
}
final queryRequest1 = await responses.next;
final queryRequestId1 = MacroRequest.fromJson(queryRequest1).id;
Scope.query.run(
() => protocol.send(
socket.add,
Response.queryResponse(
QueryResponse(
model: Model()..uris['package:foo/foo1.dart'] = Library(),
),
requestId: queryRequestId1,
).node,
),
);
final queryRequest2 = await responses.next;
final queryRequestId2 = MacroRequest.fromJson(queryRequest2).id;
Scope.query.run(
() => protocol.send(
socket.add,
Response.queryResponse(
QueryResponse(
model: Model()..uris['package:foo/foo2.dart'] = Library(),
),
requestId: queryRequestId2,
).node,
),
);
final augmentResponse1 = await responses.next;
final augmentResponse2 = await responses.next;
expect(augmentResponse1, {
'requestId': requestId1,
'type': 'AugmentResponse',
'value': {
'enumValueAugmentations': <String, Object?>{},
'extendsTypeAugmentations': <String, Object?>{},
'interfaceAugmentations': <String, Object?>{},
'mixinAugmentations': <String, Object?>{},
'libraryAugmentations': <Object?>[],
'newTypeNames': <String>[],
'typeAugmentations': {
'Foo': [
{
'code': [
{
'type': 'String',
'value':
'// {"uris":{"package:foo/foo1.dart":{"scopes":{}}}}',
},
],
},
],
},
},
});
expect(augmentResponse2, {
'requestId': requestId2,
'type': 'AugmentResponse',
'value': {
'enumValueAugmentations': <String, Object?>{},
'extendsTypeAugmentations': <String, Object?>{},
'interfaceAugmentations': <String, Object?>{},
'mixinAugmentations': <String, Object?>{},
'libraryAugmentations': <Object?>[],
'newTypeNames': <String>[],
'typeAugmentations': {
'Foo': [
{
'code': [
{
'type': 'String',
'value':
'// {"uris":{"package:foo/foo2.dart":{"scopes":{}}}}',
},
],
},
],
},
},
});
});
});
}
}