blob: ca34e276acae478a8e15aa715a03c79ca9432201 [file] [log] [blame]
library server.driver;
import 'dart:async';
import 'package:logging/logging.dart';
import '../integration/integration_test_methods.dart';
import '../integration/integration_tests.dart';
import 'operation.dart';
/**
* [Driver] launches and manages an instance of analysis server,
* reads a stream of operations, sends requests to analysis server
* based upon those operations, and evaluates the results.
*/
class Driver extends IntegrationTestMixin {
/**
* The amount of time to give the server to respond to a shutdown request
* before forcibly terminating it.
*/
static const Duration SHUTDOWN_TIMEOUT = const Duration(seconds: 5);
final Logger logger;
/**
* A flag indicating whether the server is running.
*/
bool running = false;
@override
Server server;
/**
* The results collected while running analysis server.
*/
final Results results = new Results();
/**
* The [Completer] for [runComplete].
*/
Completer<Results> _runCompleter = new Completer<Results>();
Driver(this.logger);
/**
* Return a [Future] that completes with the [Results] of running
* the analysis server once all operations have been performed.
*/
Future<Results> get runComplete => _runCompleter.future;
/**
* Perform the given operation.
* Return a [Future] that completes when the next operation can be performed,
* or `null` if the next operation can be performed immediately
*/
Future perform(Operation op) {
return op.perform(this);
}
/**
* 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. If the server acknowledges the command with a
* normal (non-error) response, the future will be completed with the 'result'
* field from the response. If the server acknowledges the command with an
* error response, the future will be completed with an error.
*/
Future send(String method, Map<String, dynamic> params) {
return server.send(method, params);
}
/**
* Launch the analysis server.
* Return a [Future] that completes when analysis server has started.
*/
Future startServer() async {
logger.log(Level.FINE, 'starting server');
initializeInttestMixin();
server = new Server();
Completer serverConnected = new Completer();
onServerConnected.listen((_) {
logger.log(Level.FINE, 'connected to server');
serverConnected.complete();
});
running = true;
return server.start(/*profileServer: true*/).then((params) {
server.listenToOutput(dispatchNotification);
server.exitCode.then((_) {
logger.log(Level.FINE, 'server stopped');
running = false;
_resultsReady();
});
return serverConnected.future;
});
}
/**
* Shutdown the analysis server if it is running.
*/
Future stopServer() async {
if (running) {
logger.log(Level.FINE, 'requesting server shutdown');
// Give the server a short time to comply with the shutdown request; if it
// doesn't exit, then forcibly terminate it.
sendServerShutdown();
await server.exitCode.timeout(SHUTDOWN_TIMEOUT, onTimeout: () {
return server.kill();
});
}
_resultsReady();
}
/**
* If not already complete, signal the completer with the collected results.
*/
void _resultsReady() {
if (!_runCompleter.isCompleted) {
_runCompleter.complete(results);
}
}
}
/**
* [Results] contains information gathered by [Driver]
* while running the analysis server
*/
class Results {
Map<String, Measurement> measurements = new Map<String, Measurement>();
/**
* Display results on stdout.
*/
void printResults() {
print('==================================================================');
print('Results:');
for (String tag in measurements.keys.toList()..sort()) {
measurements[tag].printResults();
}
}
/**
* Record the elapsed time for the given operation.
*/
void record(String tag, Duration elapsed) {
Measurement measurement = measurements[tag];
if (measurement == null) {
measurement = new Measurement(tag);
measurements[tag] = measurement;
}
measurement.record(elapsed);
}
}
/**
* [Measurement] tracks elapsed time for a given operation.
*/
class Measurement {
final String tag;
final List<Duration> elapsedTimes = new List<Duration>();
Measurement(this.tag);
void record(Duration elapsed) {
elapsedTimes.add(elapsed);
}
void printResults() {
if (elapsedTimes.length == 0) {
return;
}
print('=== $tag');
for (Duration elapsed in elapsedTimes) {
print(elapsed);
}
}
}