Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,17 @@ void main() {
logStatus('looking for line 55');

// Look for the line 55 gutter item:
final gutter55Finder = findGutterItemWithText('55');
final gutter55Finder = await retryUntilFound(
findGutterItemWithText('55'),
tester: tester,
);
expect(gutter55Finder, findsOneWidget);

// Look for the line 55 line item:
final line55Finder = findLineItemWithText("print('Hello!');");
final line55Finder = await retryUntilFound(
findLineItemWithText("print('Hello!');"),
tester: tester,
);
expect(line55Finder, findsOneWidget);

await tester.pumpAndSettle(safePumpDuration);
Expand Down Expand Up @@ -102,11 +108,17 @@ void main() {
logStatus('looking for line 28');

// Look for the line 30 gutter item:
final gutter28Finder = findGutterItemWithText('28');
final gutter28Finder = await retryUntilFound(
findGutterItemWithText('28'),
tester: tester,
);
expect(gutter28Finder, findsOneWidget);

// Look for the line 28 line item:
final line28Finder = findLineItemWithText('count++;');
final line28Finder = await retryUntilFound(
findLineItemWithText('count++;'),
tester: tester,
);
expect(line28Finder, findsOneWidget);

// Verify that the gutter item and line item are aligned:
Expand Down Expand Up @@ -162,11 +174,17 @@ void main() {
logStatus('looking for the focused line');

// Look for the line 46 gutter item:
final gutter46Finder = findGutterItemWithText('46');
final gutter46Finder = await retryUntilFound(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why were these changes added?

findGutterItemWithText('46'),
tester: tester,
);
expect(gutter46Finder, findsOneWidget);

// Look for the line 46 line item:
final line46Finder = findLineItemWithText('_action();');
final line46Finder = await retryUntilFound(
findLineItemWithText('_action();'),
tester: tester,
);
expect(line46Finder, findsOneWidget);

// Verify that the gutter item and line item are aligned:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,19 @@ final class _NetworkScreenHelper {
final WidgetTester _tester;

Future<void> clear() async {
final controller = screenControllers.lookup<NetworkController>();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as above - why was this test changed?


// Pause polling to avoid immediately repopulating rows after clear.
await controller.togglePolling(false);

// Press the 'Clear' button between tests.
await _tester.tap(find.text('Clear'));
await _tester.pumpAndSettle(safePumpDuration);

await controller.togglePolling(true);
await _tester.pump(safePumpDuration);
expect(
screenControllers.lookup<NetworkController>().requests.value,
isEmpty,
);

expect(controller.requests.value, isEmpty);
}

Future<void> triggerExit() async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,13 +293,13 @@ class LoggingController extends DevToolsScreenController

void _handleConnectionStart(VmServiceWrapper service) {
// Log stdout events.
final stdoutHandler = _StdoutEventHandler(this, 'stdout');
final stdoutHandler = StdoutEventHandler(this, 'stdout');
autoDisposeStreamSubscription(
service.onStdoutEventWithHistorySafe.listen(stdoutHandler.handle),
);

// Log stderr events.
final stderrHandler = _StdoutEventHandler(this, 'stderr', isError: true);
final stderrHandler = StdoutEventHandler(this, 'stderr', isError: true);
autoDisposeStreamSubscription(
service.onStderrEventWithHistorySafe.listen(stderrHandler.handle),
);
Expand Down Expand Up @@ -841,44 +841,21 @@ extension type _LogRecord(Map<String, dynamic> json) {
/// stdout message and its newline. Currently, `foo\n` is sent as two VM events;
/// we wait for up to 1ms when we get the `foo` event, to see if the next event
/// is a single newline. If so, we add the newline to the previous log message.
class _StdoutEventHandler {
_StdoutEventHandler(
this.loggingController,
this.name, {
this.isError = false,
});
@visibleForTesting
class StdoutEventHandler {
StdoutEventHandler(this.loggingController, this.name, {this.isError = false});

final LoggingController loggingController;
final String name;
final bool isError;

LogData? buffer;
Timer? timer;
LogData? _buffer;
Timer? _timer;

void handle(Event e) {
final message = decodeBase64(e.bytes!);

if (buffer != null) {
timer?.cancel();

if (message == '\n') {
loggingController.log(
LogData(
buffer!.kind,
buffer!.details! + message,
buffer!.timestamp,
summary: buffer!.summary! + message,
isError: buffer!.isError,
isolateRef: e.isolateRef,
),
);
buffer = null;
return;
}

loggingController.log(buffer!);
buffer = null;
}
if (_handleBufferedMessage(message, e)) return;

const maxLength = 200;

Expand All @@ -899,13 +876,70 @@ class _StdoutEventHandler {
if (message == '\n') {
loggingController.log(data);
} else {
buffer = data;
timer = Timer(const Duration(milliseconds: 1), () {
loggingController.log(buffer!);
buffer = null;
});
_setBuffer(data);
}
}

bool _handleBufferedMessage(String message, Event e) {
if (_buffer case final currentBuffer?) {
_timer?.cancel();

if (message == '\n') {
loggingController.log(
LogData(
currentBuffer.kind,
currentBuffer.details! + message,
currentBuffer.timestamp,
summary: currentBuffer.summary! + message,
isError: currentBuffer.isError,
isolateRef: e.isolateRef,
),
);
_buffer = null;
return true;
}

// If the buffered message ends with a newline, the next message is a
// continuation of the same print statement (e.g. debugPrint('line1\nline2')
// is sent by the VM as two events: 'line1\n' and 'line2'). Combine them
// into a single log entry.
// See: https://github.com/flutter/devtools/issues/9557
if (currentBuffer.details!.endsWith('\n')) {
_setBuffer(
LogData(
currentBuffer.kind,
currentBuffer.details! + message,
currentBuffer.timestamp,
summary: currentBuffer.summary,
isError: currentBuffer.isError,
isolateRef: e.isolateRef,
),
);
return true;
}

loggingController.log(currentBuffer);
_buffer = null;
}
return false;
}

void _setBuffer(LogData data) {
_buffer = data;
_timer?.cancel();
_timer = Timer(const Duration(milliseconds: 1), () {
if (_buffer case final currentBuffer?) {
loggingController.log(currentBuffer);
_buffer = null;
}
});
}

@visibleForTesting
LogData? get buffer => _buffer;

@visibleForTesting
Timer? get timer => _timer;
}

bool _isNotNull(InstanceRef? serviceRef) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ class _ConsoleOutputState extends State<_ConsoleOutput>
padding: const EdgeInsets.symmetric(horizontal: denseSpacing),
child: SelectionArea(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: ListView.separated(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ TODO: Remove this section if there are not any updates.

- Added support for searching within the log details view (raw text mode). [#9712](https://github.com/flutter/devtools/pull/9712)
![Search in log details](images/log_details_search.png "Searching within the log details view")
- Fixed an issue where log messages containing newline characters were incorrectly split into multiple separate entries in the Logging screen (#9557).

## App size tool updates

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,55 @@ void main() {
},
);
});

group('StdoutEventHandler', () {
late LoggingController controller;
late StdoutEventHandler stdoutHandler;

Event stdoutEvent(String message, {int? timestamp}) {
return Event(
bytes: base64Encode(utf8.encode(message)),
timestamp: timestamp ?? ++timestampCounter,
);
}

setUp(() {
setGlobal(ServiceConnectionManager, FakeServiceConnectionManager());
setGlobal(MessageBus, MessageBus());
setGlobal(PreferencesController, PreferencesController());

controller = LoggingController()..init();
stdoutHandler = StdoutEventHandler(controller, 'stdout');
});

test('combines newline-terminated continuation into one log', () async {
stdoutHandler.handle(stdoutEvent('line1\n'));
stdoutHandler.handle(stdoutEvent('line2'));

await Future<void>.delayed(const Duration(milliseconds: 10));

expect(controller.data, hasLength(1));
expect(controller.data.single.kind, 'stdout');
expect(controller.data.single.details, 'line1\nline2');
});

test('still combines message followed by lone newline', () {
stdoutHandler.handle(stdoutEvent('line1'));
stdoutHandler.handle(stdoutEvent('\n'));

expect(controller.data, hasLength(1));
expect(controller.data.single.details, 'line1\n');
});

test('keeps separate entries for distinct non-newline chunks', () async {
stdoutHandler.handle(stdoutEvent('line1'));
stdoutHandler.handle(stdoutEvent('line2'));

await Future<void>.delayed(const Duration(milliseconds: 10));

expect(controller.data, hasLength(2));
expect(controller.data[0].details, 'line1');
expect(controller.data[1].details, 'line2');
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -609,13 +609,23 @@ class EvalOnDartLibrary extends DisposableController
int? count,
}) {
return addRequest<T>(isAlive, () async {
final T value = await service.getObject(
_isolateRef!.id!,
instance.id!,
offset: offset,
count: count,
) as T;
return value;
try {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this (and the DWDS specific code) is solving a different issue than #9557

Could you pull this code into a separate PR and file/link the issue it's fixing? Thanks!

final T value = await service.getObject(
_isolateRef!.id!,
instance.id!,
offset: offset,
count: count,
) as T;
return value;
} on RPCError catch (e, st) {
// During teardown (especially on web/wasm), object references can be
// invalidated before queued getObject requests complete.
if (e.code == RPCErrorKind.kInvalidParams.code) {
_log.info('Ignoring stale getObject for ${instance.id}: $e', e, st);
return null;
}
rethrow;
}
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
import 'package:vm_service/vm_service.dart';

extension RpcErrorExtension on RPCError {
/// Whether this [RPCError] represents a transient DWDS/CDP failure where a
/// JS promise was garbage collected before completion.
bool get isDwdsPromiseCollectedError {
return message.contains('Promise was collected') ||
toString().contains('Promise was collected');
}

/// Whether this [RPCError] is some kind of "VM Service connection has gone"
/// error that may occur if the VM is shut down.
bool get isServiceDisposedError {
Expand Down
Loading