Cleanup analysis_server_client API
This reduces the client functionality and renames some
of the classes and methods in the API.
Specifically:
- rename Server to Client
- rename ServerConnectionHandler to ConnectionHandler
- rename Client.start to Client.startServer
- rename Client.stop to Client.stopServer
- rename Client.kill to Client.killServer
- extract behavior from Client into Listeners
- move some of the listeners into dartfix
- make several Client fields private
Change-Id: Ie71b0ac55b489099a848764251e8369c27f6ea2d
Reviewed-on: https://dart-review.googlesource.com/c/84460
Commit-Queue: Dan Rubel <danrubel@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/tool/spec/codegen_dart_notification_handler.dart b/pkg/analysis_server/tool/spec/codegen_dart_notification_handler.dart
index 1fa6600..45c8f2e 100644
--- a/pkg/analysis_server/tool/spec/codegen_dart_notification_handler.dart
+++ b/pkg/analysis_server/tool/spec/codegen_dart_notification_handler.dart
@@ -44,10 +44,13 @@
/// and dispatches those notifications to different methods based upon
/// the type of notification. Clients may override
/// any of the "on<EventName>" methods that are of interest.
+///
+/// Clients may mix-in this class, but may not implement it.
mixin NotificationHandler {
- void handleEvent(String event, params) {
+ void handleEvent(Notification notification) {
+ Map<String, Object> params = notification.params;
ResponseDecoder decoder = new ResponseDecoder(null);
- switch (event) {
+ switch (notification.event) {
''');
for (_Notification notification in notifications) {
writeln(' case ${notification.constName}:');
@@ -57,7 +60,7 @@
writeln(' break;');
}
writeln(' default:');
- writeln(' onUnknownNotification(event, params);');
+ writeln(' onUnknownNotification(notification.event, params);');
writeln(' break;');
writeln(' }');
writeln(' }');
diff --git a/pkg/analysis_server_client/example/example.dart b/pkg/analysis_server_client/example/example.dart
index 91a68e8..21573b4 100644
--- a/pkg/analysis_server_client/example/example.dart
+++ b/pkg/analysis_server_client/example/example.dart
@@ -6,7 +6,7 @@
import 'dart:io' show Directory, Platform, ProcessSignal, exit;
import 'package:analysis_server_client/handler/notification_handler.dart';
-import 'package:analysis_server_client/handler/server_connection_handler.dart';
+import 'package:analysis_server_client/handler/connection_handler.dart';
import 'package:analysis_server_client/protocol.dart';
import 'package:analysis_server_client/server.dart';
import 'package:path/path.dart' as path;
@@ -43,31 +43,13 @@
});
}
-class _Handler with NotificationHandler, ServerConnectionHandler {
+class _Handler with NotificationHandler, ConnectionHandler {
final Server server;
int errorCount = 0;
_Handler(this.server);
@override
- void handleFailedToConnect() {
- print('Failed to connect to server');
- }
-
- @override
- void handleProtocolNotSupported(Version version) {
- print('Expected protocol version $PROTOCOL_VERSION, but found $version');
- }
-
- @override
- void handleServerError(String error, String trace) {
- print('Server Error: $error');
- if (trace != null) {
- print(trace);
- }
- }
-
- @override
void onAnalysisErrors(AnalysisErrorsParams params) {
List<AnalysisError> errors = params.errors;
bool first = true;
@@ -87,6 +69,29 @@
}
@override
+ void onFailedToConnect() {
+ print('Failed to connect to server');
+ }
+
+ @override
+ void onProtocolNotSupported(Version version) {
+ print('Expected protocol version $PROTOCOL_VERSION, but found $version');
+ }
+
+ @override
+ void onServerError(ServerErrorParams params) {
+ if (params.isFatal) {
+ print('Fatal Server Error: ${params.message}');
+ } else {
+ print('Server Error: ${params.message}');
+ }
+ if (params.stackTrace != null) {
+ print(params.stackTrace);
+ }
+ super.onServerError(params);
+ }
+
+ @override
void onServerStatus(ServerStatusParams params) {
if (!params.analysis.isAnalyzing) {
// Whenever the server stops analyzing,
diff --git a/pkg/analysis_server_client/lib/handler/server_connection_handler.dart b/pkg/analysis_server_client/lib/handler/connection_handler.dart
similarity index 71%
rename from pkg/analysis_server_client/lib/handler/server_connection_handler.dart
rename to pkg/analysis_server_client/lib/handler/connection_handler.dart
index 5ff36db..800f75d 100644
--- a/pkg/analysis_server_client/lib/handler/server_connection_handler.dart
+++ b/pkg/analysis_server_client/lib/handler/connection_handler.dart
@@ -13,12 +13,14 @@
import 'package:analysis_server_client/server.dart';
import 'package:pub_semver/pub_semver.dart';
-/// [ServerConnectionHandler] listens to analysis server notifications
+/// [ConnectionHandler] listens to analysis server notifications
/// and detects when a connection has been established with the server.
///
-/// Clients may override [handleFailedToConnect], [handleProtocolNotSupported],
-/// and [handleServerError] to display connection failure information.
-mixin ServerConnectionHandler on NotificationHandler {
+/// Clients may override [onFailedToConnect], [onProtocolNotSupported],
+/// and [onServerError] to display connection failure information.
+///
+/// Clients may mix-in this class, but may not extend or implement it.
+mixin ConnectionHandler on NotificationHandler {
Completer<bool> _connected = new Completer();
/// Clients should implement this method to return the server being managed.
@@ -26,11 +28,9 @@
/// established or if a server error occurs after connecting.
Server get server;
- void handleFailedToConnect() {}
+ void onFailedToConnect() {}
- void handleProtocolNotSupported(Version version) {}
-
- void handleServerError(String error, String trace) {}
+ void onProtocolNotSupported(Version version) {}
@override
void onServerConnected(ServerConnectedParams params) {
@@ -40,7 +40,7 @@
if (minVersion <= version && version < maxVersion) {
_connected.complete(true);
} else {
- handleProtocolNotSupported(version);
+ onProtocolNotSupported(version);
_connected.complete(false);
server.stop();
}
@@ -48,7 +48,6 @@
@override
void onServerError(ServerErrorParams params) {
- handleServerError(params.message, params.stackTrace);
server.stop();
}
@@ -57,14 +56,11 @@
Future<bool> serverConnected({Duration timeLimit}) {
Future<bool> future = _connected.future;
if (timeLimit != null) {
- future = future.timeout(
- timeLimit ?? const Duration(seconds: 15),
- onTimeout: () {
- handleFailedToConnect();
- server.stop();
- return false;
- },
- );
+ future = future.timeout(timeLimit, onTimeout: () {
+ onFailedToConnect();
+ server.stop();
+ return false;
+ });
}
return future;
}
diff --git a/pkg/analysis_server_client/lib/handler/notification_handler.dart b/pkg/analysis_server_client/lib/handler/notification_handler.dart
index b115169..8bdbf14 100644
--- a/pkg/analysis_server_client/lib/handler/notification_handler.dart
+++ b/pkg/analysis_server_client/lib/handler/notification_handler.dart
@@ -12,10 +12,13 @@
/// and dispatches those notifications to different methods based upon
/// the type of notification. Clients may override
/// any of the "on<EventName>" methods that are of interest.
+///
+/// Clients may mix-in this class, but may not implement it.
mixin NotificationHandler {
- void handleEvent(String event, params) {
+ void handleEvent(Notification notification) {
+ Map<String, Object> params = notification.params;
ResponseDecoder decoder = new ResponseDecoder(null);
- switch (event) {
+ switch (notification.event) {
case ANALYSIS_NOTIFICATION_ANALYZED_FILES:
onAnalysisAnalyzedFiles(new AnalysisAnalyzedFilesParams.fromJson(
decoder, 'params', params));
@@ -93,7 +96,7 @@
new ServerStatusParams.fromJson(decoder, 'params', params));
break;
default:
- onUnknownNotification(event, params);
+ onUnknownNotification(notification.event, params);
break;
}
}
diff --git a/pkg/analysis_server_client/lib/listener/client_listener.dart b/pkg/analysis_server_client/lib/listener/client_listener.dart
new file mode 100644
index 0000000..93272c0
--- /dev/null
+++ b/pkg/analysis_server_client/lib/listener/client_listener.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2018, 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.
+
+/// Instances of the class [ClientListener] receive information from [Client]
+/// about interactions with the server.
+///
+/// Clients may mix-in this class, but may not implement it.
+mixin ClientListener {
+ /// Called when the [Client] could not decode a message.
+ void badMessage(String trimmedLine, exception) {
+ log('JSON decode failure', '$exception');
+ }
+
+ /// Called when the [Client] receives a line on stderr.
+ void errorMessage(String line) {
+ log('ERR:', line);
+ }
+
+ /// Called when the [Client] is terminating the server process
+ /// rather than requesting that the server stop itself.
+ void killingServerProcess(String reason) {
+ log('FORCIBLY TERMINATING SERVER: ', reason);
+ }
+
+ /// Log a message about interaction with the server.
+ void log(String prefix, String details);
+
+ /// Called when the [Client] received a response or notification.
+ void messageReceived(String json) {
+ log('<== ', json);
+ }
+
+ /// Called when the [Client] sends a request.
+ void requestSent(String json) {
+ log('==> ', json);
+ }
+
+ /// Called when the [Client] starts the server process.
+ void startingServer(String dartBinary, List<String> arguments) {
+ log('Starting analysis server:', '$dartBinary ${arguments.join(' ')}');
+ }
+
+ /// Called when the [Client] receives an unexpected message
+ /// which is not a notification or response.
+ void unexpectedMessage(Map<String, dynamic> message) {
+ log('Unexpected message from server:', '$message');
+ }
+
+ /// Called when the [Client] recieved an unexpected response
+ /// where the [id] does not match the [id] of an outstanding request.
+ void unexpectedResponse(Map<String, dynamic> message, id) {
+ log('Unexpected response from server', 'id=$id');
+ }
+
+ /// Called when the server process unexpectedly exits
+ /// with a non-zero exit code.
+ void unexpectedStop(int exitCode) {
+ log('Server terminated with exit code', '$exitCode');
+ }
+}
diff --git a/pkg/analysis_server_client/lib/recording_server.dart b/pkg/analysis_server_client/lib/recording_server.dart
deleted file mode 100644
index 5fc8347..0000000
--- a/pkg/analysis_server_client/lib/recording_server.dart
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2018, 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 'package:analysis_server_client/server.dart';
-
-/// A subclass of [Server] that caches all messages exchanged with the server.
-/// This is primarily used when testing and debugging the analysis server.
-/// Most clients will want to use [Server] rather than this class.
-class RecordingServer extends Server {
- /// True if we are currently printing out messages exchanged with the server.
- bool _echoMessages = false;
-
- /// Messages which have been exchanged with the server; we buffer these
- /// up until the test finishes, so that they can be examined in the debugger
- /// or printed out in response to a call to [echoMessages].
- final _messages = <String>[];
-
- /// Print out any messages exchanged with the server. If some messages have
- /// already been exchanged with the server, they are printed out immediately.
- void echoMessages() {
- if (_echoMessages) {
- return;
- }
- _echoMessages = true;
- for (String line in _messages) {
- print(line);
- }
- }
-
- @override
- Future<int> kill([String reason = 'none']) {
- echoMessages();
- return super.kill(reason);
- }
-
- @override
- void logBadDataFromServer(String details, {bool silent: false}) {
- echoMessages();
- super.logBadDataFromServer(details, silent: silent);
- }
-
- /// Record a message that was exchanged with the server,
- /// and print it out if [echoMessages] has been called.
- @override
- void logMessage(String prefix, String details) {
- String line = '$currentElapseTime: $prefix $details';
- if (_echoMessages) {
- print(line);
- }
- _messages.add(line);
- }
-}
diff --git a/pkg/analysis_server_client/lib/server.dart b/pkg/analysis_server_client/lib/server.dart
index b43090f..dc485ce 100644
--- a/pkg/analysis_server_client/lib/server.dart
+++ b/pkg/analysis_server_client/lib/server.dart
@@ -6,15 +6,22 @@
import 'dart:convert';
import 'dart:io';
+import 'package:analysis_server_client/listener/client_listener.dart';
import 'package:analysis_server_client/protocol.dart';
import 'package:path/path.dart';
/// Type of callbacks used to process notifications.
-typedef void NotificationProcessor(String event, Map<String, dynamic> params);
+typedef void NotificationProcessor(Notification notification);
/// Instances of the class [Server] manage a server process,
/// and facilitate communication to and from the server.
+///
+/// Clients may not extend, implement or mix-in this class.
class Server {
+ /// If not `null`, [_listener] will be sent information
+ /// about interactions with the server.
+ ClientListener _listener;
+
/// Server process object, or `null` if server hasn't been started yet
/// or if the server has already been stopped.
Process _process;
@@ -28,39 +35,21 @@
/// to send in the next command sent to the server.
int _nextId = 0;
- /// True if we've received bad data from the server.
- bool _receivedBadDataFromServer = false;
-
/// The stderr subscription or `null` if either
/// [listenToOutput] has not been called or [stop] has been called.
- StreamSubscription<String> stderrSubscription;
+ StreamSubscription<String> _stderrSubscription;
/// The stdout subscription or `null` if either
/// [listenToOutput] has not been called or [stop] has been called.
- StreamSubscription<String> stdoutSubscription;
+ StreamSubscription<String> _stdoutSubscription;
- /// Stopwatch that we use to generate timing information for debug output.
- Stopwatch _time = new Stopwatch();
-
- /// The [currentElapseTime] at which the last communication was received from
- /// the server or `null` if no communication has been received.
- double lastCommunicationTime;
-
- Server([Process process]) : this._process = process;
-
- /// The current elapse time (seconds) since the server was started.
- double get currentElapseTime => _time.elapsedTicks / _time.frequency;
-
- /// Future that completes when the server process exits.
- Future<int> get exitCode => _process.exitCode;
-
- /// Return a future that will complete when all commands that have been sent
- /// to the server so far have been flushed to the OS buffer.
- Future<void> flushCommands() => _process.stdin.flush();
+ Server({ClientListener listener, Process process})
+ : this._listener = listener,
+ this._process = process;
/// Force kill the server. Returns exit code future.
- Future<int> kill([String reason = 'none']) {
- logMessage('FORCIBLY TERMINATING SERVER: ', reason);
+ Future<int> kill({String reason = 'none'}) {
+ _listener?.killingServerProcess(reason);
final process = _process;
_process = null;
process.kill();
@@ -70,11 +59,10 @@
/// Start listening to output from the server,
/// and deliver notifications to [notificationProcessor].
void listenToOutput({NotificationProcessor notificationProcessor}) {
- stdoutSubscription = _process.stdout
+ _stdoutSubscription = _process.stdout
.transform(utf8.decoder)
.transform(new LineSplitter())
.listen((String line) {
- lastCommunicationTime = currentElapseTime;
String trimmedLine = line.trim();
// Guard against lines like:
@@ -89,72 +77,50 @@
return;
}
- logMessage('<== ', trimmedLine);
+ _listener?.messageReceived(trimmedLine);
Map<String, dynamic> message;
try {
message = json.decoder.convert(trimmedLine);
} catch (exception) {
- logBadDataFromServer('JSON decode failure: $exception');
+ _listener?.badMessage(trimmedLine, exception);
return;
}
- final id = message['id'];
+ final id = message[Response.ID];
if (id != null) {
// Handle response
final completer = _pendingCommands.remove(id);
if (completer == null) {
- throw 'Unexpected response from server: id=$id';
+ _listener?.unexpectedResponse(message, id);
}
- if (message.containsKey('error')) {
- completer.completeError(new ServerErrorMessage(message));
+ if (message.containsKey(Response.ERROR)) {
+ completer.completeError(new RequestError.fromJson(
+ new ResponseDecoder(null), '.error', message[Response.ERROR]));
} else {
- completer.complete(message['result']);
+ completer.complete(message[Response.RESULT]);
}
} else {
// Handle notification
- final String event = message['event'];
+ final String event = message[Notification.EVENT];
if (event != null) {
if (notificationProcessor != null) {
- notificationProcessor(event, message['params']);
+ notificationProcessor(
+ new Notification(event, message[Notification.PARAMS]));
}
} else {
- logBadDataFromServer('Unexpected message from server');
+ _listener?.unexpectedMessage(message);
}
}
});
- stderrSubscription = _process.stderr
+ _stderrSubscription = _process.stderr
.transform(utf8.decoder)
.transform(new LineSplitter())
.listen((String line) {
String trimmedLine = line.trim();
- logMessage('ERR: ', trimmedLine);
- logBadDataFromServer('Message received on stderr', silent: true);
+ _listener?.errorMessage(trimmedLine);
});
}
- /// Deal with bad data received from the server.
- void logBadDataFromServer(String details, {bool silent: false}) {
- if (!silent) {
- logMessage('BAD DATA FROM SERVER: ', details);
- }
- if (_receivedBadDataFromServer) {
- // We're already dealing with it.
- return;
- }
- _receivedBadDataFromServer = true;
- // Give the server 1 second to continue outputting bad data
- // such as outputting a stacktrace.
- new Future.delayed(new Duration(seconds: 1), () {
- throw 'Bad data received from server: $details';
- });
- }
-
- /// Log a message that was exchanged with the server.
- /// Subclasses may override as needed.
- void logMessage(String prefix, String details) {
- // no-op
- }
-
/// Send a command to the server. An 'id' will be automatically assigned.
/// The returned [Future] will be completed when the server acknowledges
/// the command with a response.
@@ -166,16 +132,16 @@
String method, Map<String, dynamic> params) {
String id = '${_nextId++}';
Map<String, dynamic> command = <String, dynamic>{
- 'id': id,
- 'method': method
+ Request.ID: id,
+ Request.METHOD: method
};
if (params != null) {
- command['params'] = params;
+ command[Request.PARAMS] = params;
}
final completer = new Completer<Map<String, dynamic>>();
_pendingCommands[id] = completer;
String line = json.encode(command);
- logMessage('==> ', line);
+ _listener?.requestSent(line);
_process.stdin.add(utf8.encoder.convert("$line\n"));
return completer.future;
}
@@ -203,7 +169,6 @@
if (_process != null) {
throw new Exception('Process already started');
}
- _time.start();
String dartBinary = Platform.executable;
// The integration tests run 3x faster when run from snapshots
@@ -270,13 +235,12 @@
if (useAnalysisHighlight2) {
arguments.add('--useAnalysisHighlight2');
}
- logMessage(
- 'Starting analysis server: ', '$dartBinary ${arguments.join(' ')}');
+ _listener?.startingServer(dartBinary, arguments);
_process = await Process.start(dartBinary, arguments);
_process.exitCode.then((int code) {
if (code != 0 && _process != null) {
// Report an error if server abruptly terminated
- logBadDataFromServer('server terminated with exit code $code');
+ _listener?.unexpectedStop(code);
}
});
}
@@ -297,32 +261,18 @@
.timeout(timeLimit, onTimeout: () {
return null;
}).whenComplete(() async {
- await stderrSubscription?.cancel();
- stderrSubscription = null;
- await stdoutSubscription?.cancel();
- stdoutSubscription = null;
+ await _stderrSubscription?.cancel();
+ _stderrSubscription = null;
+ await _stdoutSubscription?.cancel();
+ _stdoutSubscription = null;
});
- return await process.exitCode.timeout(
+ return process.exitCode.timeout(
timeLimit,
onTimeout: () {
- logMessage('FORCIBLY TERMINATING SERVER: ', 'server failed to exit');
+ _listener?.killingServerProcess('server failed to exit');
process.kill();
return process.exitCode;
},
);
}
}
-
-/// An error result from a server request.
-class ServerErrorMessage {
- final Map<String, dynamic> message;
-
- ServerErrorMessage(this.message);
-
- Map<String, dynamic> get error => message['error'];
- get errorCode => error['code'];
- get errorMessage => error['message'];
- get stackTrace => error['stackTrace'];
-
- String toString() => message.toString();
-}
diff --git a/pkg/analysis_server_client/test/live_test.dart b/pkg/analysis_server_client/test/live_test.dart
index 15b0431..4156bcc 100644
--- a/pkg/analysis_server_client/test/live_test.dart
+++ b/pkg/analysis_server_client/test/live_test.dart
@@ -2,8 +2,9 @@
// 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 'package:analysis_server_client/listener/client_listener.dart';
import 'package:analysis_server_client/handler/notification_handler.dart';
-import 'package:analysis_server_client/handler/server_connection_handler.dart';
+import 'package:analysis_server_client/handler/connection_handler.dart';
import 'package:analysis_server_client/protocol.dart';
import 'package:analysis_server_client/server.dart';
import 'package:test/test.dart';
@@ -12,7 +13,7 @@
void main() {
test('live', () async {
- final server = _debug ? new TestServer() : new Server();
+ final server = new Server(listener: _debug ? new TestListener() : null);
await server.start(clientId: 'test', suppressAnalytics: true);
TestHandler handler = new TestHandler(server);
@@ -32,15 +33,15 @@
});
}
-class TestHandler with NotificationHandler, ServerConnectionHandler {
+class TestHandler with NotificationHandler, ConnectionHandler {
final Server server;
TestHandler(this.server);
}
-class TestServer extends Server {
+class TestListener with ClientListener {
@override
- void logMessage(String prefix, String details) {
+ void log(String prefix, String details) {
print('$prefix $details');
}
}
diff --git a/pkg/analysis_server_client/test/server_test.dart b/pkg/analysis_server_client/test/server_test.dart
index 82eaad7..9f3d643 100644
--- a/pkg/analysis_server_client/test/server_test.dart
+++ b/pkg/analysis_server_client/test/server_test.dart
@@ -7,6 +7,7 @@
import 'dart:io';
import 'package:analysis_server_client/server.dart';
+import 'package:analysis_server_client/protocol.dart';
import 'package:test/test.dart';
void main() {
@@ -15,7 +16,7 @@
setUp(() async {
process = new MockProcess();
- server = new Server(process);
+ server = new Server(process: process);
});
group('listenToOutput', () {
@@ -36,10 +37,10 @@
final future = server.send('blahMethod', null);
future.catchError((e) {
- expect(e, const TypeMatcher<ServerErrorMessage>());
- final error = e as ServerErrorMessage;
- expect(error.errorCode, 'someErrorCode');
- expect(error.errorMessage, 'something went wrong');
+ expect(e, const TypeMatcher<RequestError>());
+ final error = e as RequestError;
+ expect(error.code, RequestErrorCode.UNKNOWN_REQUEST);
+ expect(error.message, 'something went wrong');
expect(error.stackTrace, 'some long stack trace');
});
server.listenToOutput();
@@ -50,11 +51,11 @@
process.stderr = _noMessage();
final completer = new Completer();
- void eventHandler(String event, Map<String, Object> params) {
- expect(event, 'fooEvent');
- expect(params.length, 2);
- expect(params['foo'] as String, 'bar');
- expect(params['baz'] as String, 'bang');
+ void eventHandler(Notification notification) {
+ expect(notification.event, 'fooEvent');
+ expect(notification.params.length, 2);
+ expect(notification.params['foo'] as String, 'bar');
+ expect(notification.params['baz'] as String, 'bang');
completer.complete();
}
@@ -93,17 +94,17 @@
final mockout = new StreamController<List<int>>();
process.stdout = mockout.stream;
process.stderr = _noMessage();
- process.exitCode = new Future.delayed(const Duration(milliseconds: 20));
+ process.exitCode = new Future.delayed(const Duration(seconds: 1));
server.listenToOutput();
- await server.stop(timeLimit: const Duration(milliseconds: 1));
+ await server.stop(timeLimit: const Duration(milliseconds: 10));
expect(process.killed, isTrue);
});
});
}
final _badErrorMessage = {
- 'code': 'someErrorCode',
+ 'code': 'UNKNOWN_REQUEST',
'message': 'something went wrong',
'stackTrace': 'some long stack trace'
};
diff --git a/pkg/dartfix/lib/listener/bad_message_listener.dart b/pkg/dartfix/lib/listener/bad_message_listener.dart
new file mode 100644
index 0000000..ed919fb
--- /dev/null
+++ b/pkg/dartfix/lib/listener/bad_message_listener.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2018, 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 'package:analysis_server_client/listener/client_listener.dart';
+
+/// [BadMessageListener] throws an exception if the [Client] receives bad data.
+mixin BadMessageListener on ClientListener {
+ /// True if we've received bad data from the server.
+ bool _receivedBadDataFromServer = false;
+
+ void throwDelayedException(String prefix, String details) {
+ if (!_receivedBadDataFromServer) {
+ _receivedBadDataFromServer = true;
+ // Give the server 1 second to continue outputting bad data
+ // such as outputting a stacktrace.
+ new Future.delayed(new Duration(seconds: 1), () {
+ throw '$prefix $details';
+ });
+ }
+ }
+
+ @override
+ void badMessage(String trimmedLine, exception) {
+ super.badMessage(trimmedLine, exception);
+ throwDelayedException('JSON decode failure', '$exception');
+ }
+
+ @override
+ void errorMessage(String line) {
+ super.errorMessage(line);
+ throwDelayedException('ERR:', line);
+ }
+
+ @override
+ void unexpectedMessage(Map<String, dynamic> message) {
+ super.unexpectedMessage(message);
+ throwDelayedException(
+ 'BAD DATA FROM SERVER:', 'Unexpected message from server');
+ }
+
+ @override
+ void unexpectedResponse(Map<String, dynamic> message, id) {
+ super.unexpectedResponse(message, id);
+ throw 'Unexpected response from server: id=$id';
+ }
+
+ @override
+ void unexpectedStop(int exitCode) {
+ super.unexpectedStop(exitCode);
+ throwDelayedException('Server terminated with exit code', '$exitCode');
+ }
+}
diff --git a/pkg/dartfix/lib/listener/recording_listener.dart b/pkg/dartfix/lib/listener/recording_listener.dart
new file mode 100644
index 0000000..c04477d
--- /dev/null
+++ b/pkg/dartfix/lib/listener/recording_listener.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2018, 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 'package:analysis_server_client/server.dart';
+import 'package:analysis_server_client/listener/client_listener.dart';
+import 'package:dartfix/listener/bad_message_listener.dart';
+import 'package:dartfix/listener/timed_listener.dart';
+
+/// [RecordingListener] caches all messages exchanged with the server
+/// and print them if a problem occurs.
+///
+/// This is primarily used when testing and debugging the analysis server.
+class RecordingListener with ClientListener, BadMessageListener, TimedListener {
+ /// True if we are currently printing out messages exchanged with the server.
+ bool _echoMessages = false;
+
+ /// Messages which have been exchanged with the server; we buffer these
+ /// up until the test finishes, so that they can be examined in the debugger
+ /// or printed out in response to a call to [echoMessages].
+ final _messages = <String>[];
+
+ /// Print out any messages exchanged with the server. If some messages have
+ /// already been exchanged with the server, they are printed out immediately.
+ void echoMessages() {
+ if (_echoMessages) {
+ return;
+ }
+ _echoMessages = true;
+ for (String line in _messages) {
+ print(line);
+ }
+ }
+
+ /// Called when the [Server] is terminating the server process
+ /// rather than requesting that the server stop itself.
+ void killingServerProcess(String reason) {
+ echoMessages();
+ super.killingServerProcess(reason);
+ }
+
+ /// Log a timed message about interaction with the server.
+ void logTimed(double elapseTime, String prefix, String details) {
+ String line = '$elapseTime: $prefix $details';
+ if (_echoMessages) {
+ print(line);
+ }
+ _messages.add(line);
+ }
+
+ @override
+ void throwDelayedException(String prefix, String details) {
+ echoMessages();
+ super.throwDelayedException(prefix, details);
+ }
+}
diff --git a/pkg/dartfix/lib/listener/timed_listener.dart b/pkg/dartfix/lib/listener/timed_listener.dart
new file mode 100644
index 0000000..f4116ed
--- /dev/null
+++ b/pkg/dartfix/lib/listener/timed_listener.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2018, 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 'package:analysis_server_client/listener/client_listener.dart';
+
+/// [TimedListener] appends a timestamp (seconds since server startup)
+/// to each logged interaction with the server.
+mixin TimedListener on ClientListener {
+ /// Stopwatch that we use to generate timing information for debug output.
+ Stopwatch _time = new Stopwatch();
+
+ /// The [currentElapseTime] at which the last communication was received from
+ /// the server or `null` if no communication has been received.
+ double lastCommunicationTime;
+
+ /// The current elapse time (seconds) since the server was started.
+ double get currentElapseTime => _time.elapsedTicks / _time.frequency;
+
+ @override
+ void log(String prefix, String details) {
+ logTimed(currentElapseTime, prefix, details);
+ }
+
+ /// Log a timed message about interaction with the server.
+ void logTimed(double elapseTime, String prefix, String details);
+
+ @override
+ void messageReceived(String json) {
+ lastCommunicationTime = currentElapseTime;
+ super.messageReceived(json);
+ }
+
+ @override
+ void startingServer(String dartBinary, List<String> arguments) {
+ _time.start();
+ super.startingServer(dartBinary, arguments);
+ }
+}
diff --git a/pkg/dartfix/lib/src/driver.dart b/pkg/dartfix/lib/src/driver.dart
index 8902799..55950db 100644
--- a/pkg/dartfix/lib/src/driver.dart
+++ b/pkg/dartfix/lib/src/driver.dart
@@ -5,16 +5,17 @@
import 'dart:async';
import 'dart:io' show File, Platform;
-import 'package:analysis_server_client/handler/notification_handler.dart';
-import 'package:analysis_server_client/handler/server_connection_handler.dart';
-import 'package:analysis_server_client/protocol.dart';
import 'package:analysis_server_client/server.dart';
+import 'package:analysis_server_client/handler/connection_handler.dart';
+import 'package:analysis_server_client/handler/notification_handler.dart';
+import 'package:analysis_server_client/listener/client_listener.dart';
+import 'package:analysis_server_client/protocol.dart';
import 'package:cli_util/cli_logging.dart';
import 'package:dartfix/handler/analysis_complete_handler.dart';
+import 'package:dartfix/listener/bad_message_listener.dart';
import 'package:dartfix/src/context.dart';
import 'package:dartfix/src/options.dart';
import 'package:dartfix/src/util.dart';
-import 'package:dartfix/src/verbose_server.dart';
import 'package:pub_semver/pub_semver.dart';
class Driver {
@@ -37,7 +38,7 @@
targets = options.targets;
context = options.context;
logger = options.logger;
- server = logger.isVerbose ? new VerboseServer(logger) : new Server();
+ server = new Server(listener: new _Listener(logger));
handler = new _Handler(this);
if (!await startServer(options)) {
@@ -184,8 +185,22 @@
}
}
+class _Listener with ClientListener, BadMessageListener {
+ final Logger logger;
+ final bool verbose;
+
+ _Listener(this.logger) : verbose = logger.isVerbose;
+
+ @override
+ void log(String prefix, String details) {
+ if (verbose) {
+ logger.trace('$prefix $details');
+ }
+ }
+}
+
class _Handler
- with NotificationHandler, ServerConnectionHandler, AnalysisCompleteHandler {
+ with NotificationHandler, ConnectionHandler, AnalysisCompleteHandler {
final Driver driver;
final Logger logger;
final Server server;
@@ -195,22 +210,27 @@
server = driver.server;
@override
- void handleFailedToConnect() {
+ void onFailedToConnect() {
logger.stderr('Failed to connect to server');
}
@override
- void handleProtocolNotSupported(Version version) {
+ void onProtocolNotSupported(Version version) {
logger.stderr('Expected protocol version $PROTOCOL_VERSION,'
' but found $version');
}
@override
- void handleServerError(String error, String trace) {
- logger.stderr('Server Error: $error');
- if (trace != null) {
- logger.stderr(trace);
+ void onServerError(ServerErrorParams params) {
+ if (params.isFatal) {
+ logger.stderr('Fatal Server Error: ${params.message}');
+ } else {
+ logger.stderr('Server Error: ${params.message}');
}
+ if (params.stackTrace != null) {
+ logger.stderr(params.stackTrace);
+ }
+ super.onServerError(params);
}
@override
diff --git a/pkg/dartfix/lib/src/verbose_server.dart b/pkg/dartfix/lib/src/verbose_server.dart
deleted file mode 100644
index a96f426..0000000
--- a/pkg/dartfix/lib/src/verbose_server.dart
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2018, 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 'package:analysis_server_client/server.dart';
-import 'package:cli_util/cli_logging.dart';
-
-class VerboseServer extends Server {
- final Logger logger;
-
- VerboseServer(this.logger);
-
- @override
- void logMessage(String prefix, String details) {
- logger.trace('$prefix $details');
- }
-}