Skip to content

Commit

Permalink
Refactor reading Entry
Browse files Browse the repository at this point in the history
  • Loading branch information
pdenert committed Nov 6, 2024
1 parent 5821653 commit ec476e0
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 76 deletions.
3 changes: 3 additions & 0 deletions packages/patrol_log/lib/src/ansi_codes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ class AnsiCodes {
/// `[0m` - Reset all styles.
static const String reset = '$escape[0m';

/// `[1m` - Set text to bold.
static const String bold = '$escape[1m';

/// `[30m` - Set text color to gray.
static const String gray = '$escape[30m';

Expand Down
225 changes: 156 additions & 69 deletions packages/patrol_log/lib/src/patrol_log_reader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,24 @@ class PatrolLogReader {
}) listenStdOut;
final DisposeScope _scope;

/// Stopwatch measuring the whole tests duration.
final _stopwatch = Stopwatch();

/// List of tests entries.
final List<PatrolSingleTestEntry> _singleEntries = [];

/// List of tests names that were skipped.
final List<String> _skippedTests = [];

final StreamController<Entry> _controller =
StreamController<Entry>.broadcast();
late final StreamSubscription<Entry> _streamSubscription;

void listen() {
read();
// Listen to the entry stream and pretty print the patrol logs.
readEntries();

// Parse the output from the process.
listenStdOut(parse).disposedBy(_scope);
}

Expand Down Expand Up @@ -69,44 +78,60 @@ class PatrolLogReader {
String get failedTestsList =>
failedTests.map((e) => ' - ${e.nameWithPath}').join('\n');

/// Parse the line from the process output.
void parse(String line) {
if (line.contains('PATROL_LOG')) {
final regExp = RegExp(r'PATROL_LOG \{(.*?)\}');
final match = regExp.firstMatch(line);
if (match != null) {
final matchedText = match.group(1)!;
final json = '{$matchedText}';
final entry = parseEntry(json);
if (entry case TestEntry _) {
final testEntry = entry;
// Skip info test is returned multiple times, so we need to filter it
if (testEntry.status == TestEntryStatus.skip &&
!_skippedTests.contains(testEntry.name)) {
_skippedTests.add(testEntry.name);
_controller.add(entry);
} else if (testEntry.status != TestEntryStatus.skip) {
_controller.add(entry);
}
} else {
_controller.add(entry);
}
}
_parsePatrolLog(line);
} else if (showFlutterLogs && line.contains('Runner: (Flutter) flutter:')) {
final regExp = RegExp(r'Runner: \(Flutter\) (.*)');
final match = regExp.firstMatch(line);
if (match != null) {
final matchedText = match.group(1)!;
log(matchedText);
}
_parseFlutterIOsLog(line);
} else if (showFlutterLogs && line.contains('I flutter :')) {
final regExp = RegExp('I (.*)');
final match = regExp.firstMatch(line);
if (match != null) {
final matchedText = match.group(1)!;
log(matchedText);
_parseFlutterAndroidLog(line);
}
}

/// Take line containg PATROL_LOG tag, parse it to [Entry] and add to stream.
void _parsePatrolLog(String line) {
final regExp = RegExp(r'PATROL_LOG \{(.*?)\}');
final match = regExp.firstMatch(line);
if (match != null) {
final matchedText = match.group(1)!;
final json = '{$matchedText}';
final entry = parseEntry(json);

if (entry case TestEntry _) {
final testEntry = entry;
// Skip info test is returned multiple times, so we need to filter it
if (testEntry.status == TestEntryStatus.skip &&
!_skippedTests.contains(testEntry.name)) {
_skippedTests.add(testEntry.name);
_controller.add(entry);
} else if (testEntry.status != TestEntryStatus.skip) {
_controller.add(entry);
}
} else {
_controller.add(entry);
}
}
return;
}

/// Parse the line containing Flutter logs on iOS and print them.
void _parseFlutterIOsLog(String line) {
final regExp = RegExp(r'Runner: \(Flutter\) (.*)');
final match = regExp.firstMatch(line);
if (match != null) {
final matchedText = match.group(1)!;
log(matchedText);
}
}

/// Parse the line containing Flutter logs on Android and print them.
void _parseFlutterAndroidLog(String line) {
final regExp = RegExp('I (.*)');
final match = regExp.firstMatch(line);
if (match != null) {
final matchedText = match.group(1)!;
log(matchedText);
}
}

/// Parses patrol log entry from JSON.
Expand All @@ -127,46 +152,108 @@ class PatrolLogReader {
}

/// Read the entries from the stream and print them to the console.
Future<void> read() async {
Future<void> readEntries() async {
var stepsCounter = 0;
var logsCounter = 0;

_streamSubscription = _controller.stream.listen((entry) {
if (entry is TestEntry &&
(entry.status == TestEntryStatus.skip ||
entry.status == TestEntryStatus.start)) {
_singleEntries.add(PatrolSingleTestEntry([entry]));
} else {
final lastEntry = _singleEntries.last;
lastEntry.entries.add(entry);
}
_streamSubscription = _controller.stream.listen(
(entry) {
switch (entry) {
case TestEntry()
when entry.status == TestEntryStatus.skip ||
entry.status == TestEntryStatus.start:
// Create a new single test entry for the test that is starting or is skipped.
_singleEntries.add(PatrolSingleTestEntry(entry));

if (entry is TestEntry && entry.isFinished) {
final executionTime = entry
.executionTime(_singleEntries.last.entries.first.timestamp)
.inSeconds;
if (!showFlutterLogs) {
_clearLines(stepsCounter + logsCounter + 1);
}
log('${entry.pretty()} ${AnsiCodes.gray}(${executionTime}s)${AnsiCodes.reset}');
stepsCounter = 0;
logsCounter = 0;
} else if (entry is StepEntry) {
if (!hideTestSteps) {
if (entry.status != StepEntryStatus.start) {
_clearPreviousLine();
} else {
stepsCounter++;
}
log(entry.pretty(number: stepsCounter));
}
} else {
if (entry is LogEntry) {
logsCounter++;
// Print the test entry to the console.
log(entry.pretty());
case TestEntry():
// Close the single test entry for the test that is finished.
_singleEntries.last.closeTest(entry);

// Optionally clear all printed [StepEntry] and [LogEntry].
if (!showFlutterLogs) {
_clearLines(stepsCounter + logsCounter + 1);
}

final executionTime = _singleEntries.last.executionTime.inSeconds;
// Print test entry summary to console.
log('${entry.pretty()} ${AnsiCodes.gray}(${executionTime}s)${AnsiCodes.reset}');

// Reset the counters needed for clearing the lines.
stepsCounter = 0;
logsCounter = 0;
case StepEntry():
_singleEntries.last.addEntry(entry);
if (!hideTestSteps) {
// Clear the previous line it's not the new step, or increment counter
// for new step
if (entry.status != StepEntryStatus.start) {
_clearPreviousLine();
} else {
stepsCounter++;
}

// Print the step entry to the console.
log(entry.pretty(number: stepsCounter));
}
case LogEntry():
_singleEntries.last.addEntry(entry);
logsCounter++;

// Print the log entry to the console.
log(entry.pretty());
}
log(entry.pretty());
}
});
},
);

// _streamSubscription = _controller.stream.listen((entry) {
// if (entry is TestEntry &&
// (entry.status == TestEntryStatus.skip ||
// entry.status == TestEntryStatus.start)) {
// // Create a new single test entry for the test that is starting or is skipped.
// _singleEntries.add(PatrolSingleTestEntry(entry));

// // Print the test entry to the console.
// log(entry.pretty());
// } else if (entry is TestEntry && entry.isFinished) {
// // Close the single test entry for the test that is finished.
// _singleEntries.last.closeTest(entry);

// // Optionally clear all printed [StepEntry] and [LogEntry].
// if (!showFlutterLogs) {
// _clearLines(stepsCounter + logsCounter + 1);
// }

// final executionTime = _singleEntries.last.executionTime.inSeconds;
// // Print test entry summary to console.
// log('${entry.pretty()} ${AnsiCodes.gray}(${executionTime}s)${AnsiCodes.reset}');

// // Reset the counters needed for clearing the lines.
// stepsCounter = 0;
// logsCounter = 0;
// } else if (entry is StepEntry) {
// _singleEntries.last.addEntry(entry);
// if (!hideTestSteps) {
// // Clear the previous line it's not the new step, or increment counter
// // for new step
// if (entry.status != StepEntryStatus.start) {
// _clearPreviousLine();
// } else {
// stepsCounter++;
// }

// // Print the step entry to the console.
// log(entry.pretty(number: stepsCounter));
// }
// } else if (entry is LogEntry) {
// _singleEntries.last.addEntry(entry);
// logsCounter++;

// // Print the log entry to the console.
// log(entry.pretty());
// }
// });
}

/// Returns a summary of the test results. That contains:
Expand All @@ -178,7 +265,7 @@ class PatrolLogReader {
/// - Number of skipped tests
/// - Path to the report file
/// - Duration of the tests
String get summary => 'Test summary:\n'
String get summary => '\n${AnsiCodes.bold}Test summary:${AnsiCodes.reset}\n'
'${Emojis.total} Total: $totalTests\n'
'${Emojis.success} Successful: $successfulTests\n'
'${Emojis.failure} Failed: $failedTestsCount\n'
Expand Down
28 changes: 21 additions & 7 deletions packages/patrol_log/lib/src/patrol_single_test_entry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,33 @@ import 'package:patrol_log/patrol_log.dart';
/// The last entry in the collection is a test entry with status
/// [TestEntryStatus.success] or [TestEntryStatus.failure].
class PatrolSingleTestEntry {
PatrolSingleTestEntry(this.entries);
PatrolSingleTestEntry(this.openingTestEntry);

final List<Entry> entries;
final TestEntry openingTestEntry;
TestEntry? closingTestEntry;

/// List contains StepEntry and LogEntry entries.
final List<Entry> _entries = [];

void addEntry(Entry entry) {
_entries.add(entry);
}

void closeTest(TestEntry testEntry) {
closingTestEntry = testEntry;
}

/// The name of the test.
String get name =>
(entries.firstWhere((t) => t is TestEntry) as TestEntry).name;
String get name => openingTestEntry.name;

/// The status of the test.
TestEntryStatus get status =>
(entries.lastWhere((t) => t is TestEntry) as TestEntry).status;
closingTestEntry?.status ?? openingTestEntry.status;

/// The name of the test with the path.
String get nameWithPath =>
(entries.lastWhere((t) => t is TestEntry) as TestEntry).nameWithPath;
String get nameWithPath => openingTestEntry.nameWithPath;

/// The execution time of the test.
Duration get executionTime =>
closingTestEntry!.executionTime(openingTestEntry.timestamp);
}

0 comments on commit ec476e0

Please sign in to comment.