Files
Paul Berry afcfbbeba8 Migrate developer experience packages to new constructor decl syntax.
(Part of https://github.com/dart-lang/sdk/issues/63288)

This change migrates the packages owned by the developer experience
team to use the new constructor declaration syntax, described in
https://github.com/dart-lang/language/blob/main/accepted/future-releases/primary-constructors/feature-specification.md#abbreviations-of-in-body-constructor-declarations.

This change was performed in an automated fashion, by (a) bumping the
packages' SDK constraints to `3.13.0-0`, (b) enabling the lints
`unnecessary_type_name_in_constructor` and
`unnecessary_const_in_enum_constructor`, (c) fixing the resulting lint
failures using `dart fix`, and then (d) reformatting the affected
files.

To ease code review, I've reverted unrelated formatting changes.

Since this change requires bumping SDK constaints to `3.13.0-0`, it
was only performed on packages that are *not* published on
pub. (Packages that *are* published on pub should remain on lower
language versions until at least after the stable version of 3.13 is
released, so that we don't block users on the stable channel from
receiving updates to those packages.)

Change-Id: Ibb4daebafd239da58251e838ea6a3f336a6a6964
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505046
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
2026-05-27 14:52:58 -07:00

302 lines
9.5 KiB
Dart

// Copyright (c) 2015, 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 'dart:math' show max, sqrt;
import 'package:logging/logging.dart';
import 'package:path/path.dart';
import '../../integration_test/support/integration_test_methods.dart';
import '../../integration_test/support/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 IntegrationTest {
/// The amount of time to give the server to respond to a shutdown request
/// before forcibly terminating it.
static const Duration shutdownTimeout = Duration(seconds: 5);
final Logger logger = Logger('Driver');
/// The diagnostic port for Analysis Server or `null` if none.
final int? diagnosticPort;
/// A flag indicating whether the server is running.
bool running = false;
@override
late Server server;
/// The results collected while running analysis server.
final Results results = Results();
/// The [Completer] for [runComplete].
final Completer<Results> _runCompleter = Completer<Results>();
new({this.diagnosticPort});
/// 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<void>? 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<Map<String, Object?>?> 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<void> startServer() {
logger.log(Level.FINE, 'starting server');
server = Server();
var serverConnected = Completer<void>();
onServerConnected.listen((_) {
logger.log(Level.FINE, 'connected to server');
serverConnected.complete();
});
running = true;
var dartSdkPath = dirname(dirname(Platform.resolvedExecutable));
return server
.start(dartSdkPath: dartSdkPath, diagnosticPort: diagnosticPort)
.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<void> stopServer([Duration timeout = shutdownTimeout]) 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.
unawaited(sendServerShutdown());
await server.exitCode.timeout(
timeout,
onTimeout: () {
return server.kill('server failed to exit');
},
);
}
_resultsReady();
}
/// If not already complete, signal the completer with the collected results.
void _resultsReady() {
if (!_runCompleter.isCompleted) {
_runCompleter.complete(results);
}
}
}
/// [Measurement] tracks elapsed time for a given operation.
class Measurement {
final String tag;
final bool notification;
final List<Duration> elapsedTimes = <Duration>[];
int errorCount = 0;
int unexpectedResultCount = 0;
new(this.tag, this.notification);
int get count => elapsedTimes.length;
void printSummary(int keyLen) {
var count = 0;
var maxTime = elapsedTimes[0];
var minTime = elapsedTimes[0];
var totalTimeMicros = 0;
for (var elapsed in elapsedTimes) {
++count;
var timeMicros = elapsed.inMicroseconds;
maxTime = maxTime.compareTo(elapsed) > 0 ? maxTime : elapsed;
minTime = minTime.compareTo(elapsed) < 0 ? minTime : elapsed;
totalTimeMicros += timeMicros;
}
var meanTime = (totalTimeMicros / count).round();
var sorted = elapsedTimes.toList()..sort();
var time90th = sorted[(sorted.length * 0.90).round() - 1];
var time99th = sorted[(sorted.length * 0.99).round() - 1];
var differenceFromMeanSquared = 0;
for (var elapsed in elapsedTimes) {
var timeMicros = elapsed.inMicroseconds;
var differenceFromMean = timeMicros - meanTime;
differenceFromMeanSquared += differenceFromMean * differenceFromMean;
}
var variance = differenceFromMeanSquared / count;
var standardDeviation = sqrt(variance).round();
var buffer = StringBuffer();
buffer.writePadRight(tag, keyLen);
buffer.writePadLeft(count.toString(), 6);
buffer.writePadLeft(errorCount.toString(), 6);
buffer.writePadLeft(unexpectedResultCount.toString(), 6);
buffer.writeDuration(Duration(microseconds: meanTime));
buffer.writeDuration(time90th);
buffer.writeDuration(time99th);
buffer.writeDuration(Duration(microseconds: standardDeviation));
buffer.writeDuration(minTime);
buffer.writeDuration(maxTime);
buffer.writeDuration(Duration(microseconds: totalTimeMicros));
print(buffer.toString());
}
void record(bool success, Duration elapsed) {
if (!success) {
++errorCount;
}
elapsedTimes.add(elapsed);
}
void recordUnexpectedResults() {
++unexpectedResultCount;
}
}
/// [Results] contains information gathered by [Driver] while running the
/// analysis server.
class Results {
Map<String, Measurement> measurements = <String, Measurement>{};
/// Display results on stdout.
void printResults() {
print('');
print('==================================================================');
print('');
var sortedEntries = measurements.entries.toList();
sortedEntries.sort((a, b) => a.key.compareTo(b.key));
var keyLen = sortedEntries
.map((e) => e.key)
.fold(0, (int len, String key) => max(len, key.length));
_printGroupHeader('Request/Response', keyLen);
var totalCount = 0;
var totalErrorCount = 0;
var totalUnexpectedResultCount = 0;
for (var entry in sortedEntries) {
var m = entry.value;
if (!m.notification) {
m.printSummary(keyLen);
totalCount += m.count;
totalErrorCount += m.errorCount;
totalUnexpectedResultCount += m.unexpectedResultCount;
}
}
_printTotals(
keyLen,
totalCount,
totalErrorCount,
totalUnexpectedResultCount,
);
print('');
_printGroupHeader('Notifications', keyLen);
for (var entry in sortedEntries) {
var m = entry.value;
if (m.notification) {
m.printSummary(keyLen);
}
}
// TODO(danrubel): print warnings if driver caches are not empty.
print('''
(1) uxr = UneXpected Results or responses received from the server
that do not match the recorded response for that request.
(2) all times in milliseconds''');
}
/// Record the elapsed time for the given operation.
void record(
String tag,
Duration elapsed, {
bool notification = false,
bool success = true,
}) {
var measurement = measurements[tag];
if (measurement == null) {
measurement = Measurement(tag, notification);
measurements[tag] = measurement;
}
measurement.record(success, elapsed);
}
void recordUnexpectedResults(String tag) {
measurements[tag]!.recordUnexpectedResults();
}
static void _printGroupHeader(String groupName, int keyLength) {
var buffer = StringBuffer();
buffer.writePadRight(groupName, keyLength);
buffer.writePadLeft('count', 6);
buffer.writePadLeft('error', 6);
buffer.writePadLeft('uxr(1)', 6);
buffer.write(' ');
buffer.writePadRight('mean(2)', 15);
buffer.writePadRight('90th', 15);
buffer.writePadRight('99th', 15);
buffer.writePadRight('std-dev', 15);
buffer.writePadRight('minimum', 15);
buffer.writePadRight('maximum', 15);
buffer.writePadRight('total', 15);
print(buffer.toString());
}
static void _printTotals(
int keyLength,
int totalCount,
int totalErrorCount,
int totalUnexpectedResultCount,
) {
var buffer = StringBuffer();
buffer.writePadRight('Totals', keyLength);
buffer.writePadLeft(totalCount.toString(), 6);
buffer.writePadLeft(totalErrorCount.toString(), 6);
buffer.writePadLeft(totalUnexpectedResultCount.toString(), 6);
print(buffer.toString());
}
}
extension on StringBuffer {
void writeDuration(Duration duration) {
writePadLeft(duration.inMilliseconds.toString(), 15);
}
void writePadLeft(String text, int keyLength) {
write(text.padLeft(keyLength));
write(' ');
}
void writePadRight(String text, int keyLength) {
write(text.padRight(keyLength));
write(' ');
}
}