mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Some tests are assuming the flutter sdk code is being checked out to flutter and checking the code to a different repository makes them fail. Bug: https://github.com/flutter/flutter/issues/144487
361 lines
18 KiB
Dart
361 lines
18 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// The purpose of this test is to verify the end-to-end behavior of
|
|
// "flutter run" and other such commands, as closely as possible to
|
|
// the default behavior. To that end, it avoids the use of any test
|
|
// features that are not critical (-dflutter-test being the primary
|
|
// example of a test feature that it does use). For example, no use
|
|
// is made of "--machine" in these tests.
|
|
|
|
// There are a number of risks when it comes to writing a test such
|
|
// as this one. Typically these tests are hard to debug if they are
|
|
// in a failing condition, because they just hang as they await the
|
|
// next expected line that never comes. To avoid this, here we have
|
|
// the policy of looking for multiple lines, printing what expected
|
|
// lines were not seen when a short timeout expires (but timing out
|
|
// does not cause the test to fail, to reduce flakes), and wherever
|
|
// possible recording all output and comparing the actual output to
|
|
// the expected output only once the test is completed.
|
|
|
|
// To aid in debugging, consider passing the `debug: true` argument
|
|
// to the runFlutter function.
|
|
|
|
// This file intentionally assumes the tests run in order.
|
|
@Tags(<String>['no-shuffle'])
|
|
library;
|
|
|
|
import 'dart:io';
|
|
|
|
import '../src/common.dart';
|
|
import 'test_utils.dart' show fileSystem;
|
|
import 'transition_test_utils.dart';
|
|
|
|
void main() {
|
|
testWithoutContext('flutter run writes and clears pidfile appropriately', () async {
|
|
final String tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_overall_experience_test.').resolveSymbolicLinksSync();
|
|
final String pidFile = fileSystem.path.join(tempDirectory, 'flutter.pid');
|
|
final String testDirectory = fileSystem.path.join(flutterRoot, 'examples', 'hello_world');
|
|
bool? existsDuringTest;
|
|
try {
|
|
expect(fileSystem.file(pidFile).existsSync(), isFalse);
|
|
final ProcessTestResult result = await runFlutter(
|
|
<String>['run', '-dflutter-tester', '--pid-file', pidFile],
|
|
testDirectory,
|
|
<Transition>[
|
|
Barrier('q Quit (terminate the application on the device).', handler: (String line) {
|
|
existsDuringTest = fileSystem.file(pidFile).existsSync();
|
|
return 'q';
|
|
}),
|
|
const Barrier('Application finished.'),
|
|
],
|
|
);
|
|
expect(existsDuringTest, isNot(isNull));
|
|
expect(existsDuringTest, isTrue);
|
|
expect(result.exitCode, 0, reason: 'subprocess failed; $result');
|
|
expect(fileSystem.file(pidFile).existsSync(), isFalse);
|
|
// This first test ignores the stdout and stderr, so that if the
|
|
// first run outputs "building flutter", or the "there's a new
|
|
// flutter" banner, or other such first-run messages, they won't
|
|
// fail the tests. This does mean that running this test first is
|
|
// actually important in the case where you're running the tests
|
|
// manually. (On CI, all those messages are expected to be seen
|
|
// long before we get here, e.g. because we run "flutter doctor".)
|
|
} finally {
|
|
tryToDelete(fileSystem.directory(tempDirectory));
|
|
}
|
|
}, skip: Platform.isWindows); // [intended] Windows doesn't support sending signals so we don't care if it can store the PID.
|
|
|
|
testWithoutContext('flutter run handle SIGUSR1/2 run', () async {
|
|
final String tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_overall_experience_test.').resolveSymbolicLinksSync();
|
|
final String pidFile = fileSystem.path.join(tempDirectory, 'flutter.pid');
|
|
final String testDirectory = fileSystem.path.join(flutterRoot, 'dev', 'integration_tests', 'ui');
|
|
final String testScript = fileSystem.path.join('lib', 'commands.dart');
|
|
late int pid;
|
|
final List<String> command = <String>[
|
|
'run',
|
|
'-dflutter-tester',
|
|
'--report-ready',
|
|
'--pid-file',
|
|
pidFile,
|
|
'--no-devtools',
|
|
testScript,
|
|
];
|
|
try {
|
|
final ProcessTestResult result = await runFlutter(
|
|
command,
|
|
testDirectory,
|
|
<Transition>[
|
|
Multiple(<Pattern>['Flutter run key commands.', 'called paint'], handler: (String line) {
|
|
pid = int.parse(fileSystem.file(pidFile).readAsStringSync());
|
|
processManager.killPid(pid, ProcessSignal.sigusr1);
|
|
return null;
|
|
}),
|
|
Barrier('Performing hot reload...'.padRight(progressMessageWidth), logging: true),
|
|
Multiple(<Pattern>[RegExp(r'^Reloaded 0 libraries in [0-9]+ms \(compile: \d+ ms, reload: \d+ ms, reassemble: \d+ ms\)\.$'), 'called reassemble', 'called paint'], handler: (String line) {
|
|
processManager.killPid(pid, ProcessSignal.sigusr2);
|
|
return null;
|
|
}),
|
|
Barrier('Performing hot restart...'.padRight(progressMessageWidth)),
|
|
// This could look like 'Restarted application in 1,237ms.'
|
|
Multiple(<Pattern>[RegExp(r'^Restarted application in .+m?s.$'), 'called main', 'called paint'], handler: (String line) {
|
|
return 'q';
|
|
}),
|
|
const Barrier('Application finished.'),
|
|
],
|
|
logging: false, // we ignore leading log lines to avoid making this test sensitive to e.g. the help message text
|
|
);
|
|
// We check the output from the app (all starts with "called ...") and the output from the tool
|
|
// (everything else) separately, because their relative timing isn't guaranteed. Their rough timing
|
|
// is verified by the expected transitions above.
|
|
expect(result.stdout.where((String line) => line.startsWith('called ')), <Object>[
|
|
// logs start after we receive the response to sending SIGUSR1
|
|
// SIGUSR1:
|
|
'called reassemble',
|
|
'called paint',
|
|
// SIGUSR2:
|
|
'called main',
|
|
'called paint',
|
|
]);
|
|
expect(
|
|
result.stdout.where((String line) => !line.startsWith('called ')),
|
|
<Object>[
|
|
// logs start after we receive the response to sending SIGUSR1
|
|
'Performing hot reload...'.padRight(progressMessageWidth),
|
|
startsWith('Reloaded 0 libraries in '),
|
|
'Performing hot restart...'.padRight(progressMessageWidth),
|
|
startsWith('Restarted application in '),
|
|
'', // this newline is the one for after we hit "q"
|
|
'Application finished.',
|
|
'ready',
|
|
],
|
|
reason: 'stdout from command ${command.join(' ')} was unexpected, '
|
|
'full Stdout:\n\n${result.stdout.join('\n')}\n\n'
|
|
'Stderr:\n\n${result.stderr.join('\n')}',
|
|
);
|
|
expect(result.exitCode, 0);
|
|
} finally {
|
|
tryToDelete(fileSystem.directory(tempDirectory));
|
|
}
|
|
}, skip: Platform.isWindows); // [intended] Windows doesn't support sending signals.
|
|
|
|
testWithoutContext('flutter run can hot reload and hot restart, handle "p" key', () async {
|
|
final String tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_overall_experience_test.').resolveSymbolicLinksSync();
|
|
final String testDirectory = fileSystem.path.join(flutterRoot, 'dev', 'integration_tests', 'ui');
|
|
final String testScript = fileSystem.path.join('lib', 'commands.dart');
|
|
final List<String> command = <String>[
|
|
'run',
|
|
'-dflutter-tester',
|
|
'--report-ready',
|
|
'--no-devtools',
|
|
testScript,
|
|
];
|
|
try {
|
|
final ProcessTestResult result = await runFlutter(
|
|
command,
|
|
testDirectory,
|
|
<Transition>[
|
|
Multiple(<Pattern>['Flutter run key commands.', 'called main', 'called paint'], handler: (String line) {
|
|
return 'r';
|
|
}),
|
|
Barrier('Performing hot reload...'.padRight(progressMessageWidth), logging: true),
|
|
Multiple(<Pattern>['ready', 'called reassemble', 'called paint'], handler: (String line) {
|
|
return 'R';
|
|
}),
|
|
Barrier('Performing hot restart...'.padRight(progressMessageWidth)),
|
|
Multiple(<Pattern>['ready', 'called main', 'called paint'], handler: (String line) {
|
|
return 'p';
|
|
}),
|
|
Multiple(<Pattern>['ready', 'called paint', 'called debugPaintSize'], handler: (String line) {
|
|
return 'p';
|
|
}),
|
|
Multiple(<Pattern>['ready', 'called paint'], handler: (String line) {
|
|
return 'q';
|
|
}),
|
|
const Barrier('Application finished.'),
|
|
],
|
|
logging: false, // we ignore leading log lines to avoid making this test sensitive to e.g. the help message text
|
|
);
|
|
// We check the output from the app (all starts with "called ...") and the output from the tool
|
|
// (everything else) separately, because their relative timing isn't guaranteed. Their rough timing
|
|
// is verified by the expected transitions above.
|
|
expect(result.stdout.where((String line) => line.startsWith('called ')), <Object>[
|
|
// logs start after we initiate the hot reload
|
|
// hot reload:
|
|
'called reassemble',
|
|
'called paint',
|
|
// hot restart:
|
|
'called main',
|
|
'called paint',
|
|
// debugPaintSizeEnabled = true:
|
|
'called paint',
|
|
'called debugPaintSize',
|
|
// debugPaintSizeEnabled = false:
|
|
'called paint',
|
|
]);
|
|
expect(
|
|
result.stdout.where((String line) => !line.startsWith('called ')), <Object>[
|
|
// logs start after we receive the response to hitting "r"
|
|
'Performing hot reload...'.padRight(progressMessageWidth),
|
|
startsWith('Reloaded 0 libraries in '),
|
|
'ready',
|
|
'', // this newline is the one for after we hit "R"
|
|
'Performing hot restart...'.padRight(progressMessageWidth),
|
|
startsWith('Restarted application in '),
|
|
'ready',
|
|
'', // newline for after we hit "p" the first time
|
|
'ready',
|
|
'', // newline for after we hit "p" the second time
|
|
'ready',
|
|
'', // this newline is the one for after we hit "q"
|
|
'Application finished.',
|
|
'ready',
|
|
],
|
|
reason: 'stdout from command ${command.join(' ')} was unexpected, '
|
|
'full Stdout:\n\n${result.stdout.join('\n')}\n\n'
|
|
'Stderr:\n\n${result.stderr.join('\n')}',
|
|
);
|
|
expect(result.exitCode, 0);
|
|
} finally {
|
|
tryToDelete(fileSystem.directory(tempDirectory));
|
|
}
|
|
});
|
|
|
|
testWithoutContext('flutter error messages include a DevTools link', () async {
|
|
final String testDirectory = fileSystem.path.join(flutterRoot, 'dev', 'integration_tests', 'ui');
|
|
final String tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_overall_experience_test.').resolveSymbolicLinksSync();
|
|
final String testScript = fileSystem.path.join('lib', 'overflow.dart');
|
|
try {
|
|
final ProcessTestResult result = await runFlutter(
|
|
<String>['run', '-dflutter-tester', testScript],
|
|
testDirectory,
|
|
<Transition>[
|
|
Barrier(RegExp(r'^A Dart VM Service on Flutter test device is available at: ')),
|
|
Barrier(RegExp(r'^The Flutter DevTools debugger and profiler on Flutter test device is available at: '), handler: (String line) {
|
|
return 'r';
|
|
}),
|
|
Barrier('Performing hot reload...'.padRight(progressMessageWidth), logging: true),
|
|
Barrier(RegExp(r'^Reloaded 0 libraries in [0-9]+ms.'), handler: (String line) {
|
|
return 'q';
|
|
}),
|
|
],
|
|
logging: false,
|
|
);
|
|
expect(result.exitCode, 0);
|
|
expect(result.stdout, <Object>[
|
|
startsWith('Performing hot reload...'),
|
|
'',
|
|
'══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════',
|
|
'The following assertion was thrown during layout:',
|
|
'A RenderFlex overflowed by 69200 pixels on the right.',
|
|
'',
|
|
'The relevant error-causing widget was:',
|
|
matches(RegExp(r'^ Row.+/dev/integration_tests/ui/lib/overflow\.dart:32:18$')),
|
|
'',
|
|
'To inspect this widget in Flutter DevTools, visit:',
|
|
startsWith('http'),
|
|
'',
|
|
'The overflowing RenderFlex has an orientation of Axis.horizontal.',
|
|
'The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and',
|
|
'black striped pattern. This is usually caused by the contents being too big for the RenderFlex.',
|
|
'Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the',
|
|
'RenderFlex to fit within the available space instead of being sized to their natural size.',
|
|
'This is considered an error condition because it indicates that there is content that cannot be',
|
|
'seen. If the content is legitimately bigger than the available space, consider clipping it with a',
|
|
'ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex,',
|
|
'like a ListView.',
|
|
matches(RegExp(r'^The specific RenderFlex in question is: RenderFlex#..... OVERFLOWING:$')),
|
|
startsWith(' creator: Row ← Test ← '),
|
|
contains(' ← '),
|
|
endsWith(' ⋯'),
|
|
' parentData: <none> (can use size)',
|
|
' constraints: BoxConstraints(w=800.0, h=600.0)',
|
|
' size: Size(800.0, 600.0)',
|
|
' direction: horizontal',
|
|
' mainAxisAlignment: start',
|
|
' mainAxisSize: max',
|
|
' crossAxisAlignment: center',
|
|
' textDirection: ltr',
|
|
' verticalDirection: down',
|
|
'◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤',
|
|
'════════════════════════════════════════════════════════════════════════════════════════════════════',
|
|
'',
|
|
startsWith('Reloaded 0 libraries in '),
|
|
'',
|
|
'Application finished.',
|
|
]);
|
|
} finally {
|
|
tryToDelete(fileSystem.directory(tempDirectory));
|
|
}
|
|
});
|
|
|
|
testWithoutContext('flutter run help output', () async {
|
|
// This test enables all logging so that it checks the exact text of starting up an application.
|
|
// The idea is to verify that we're not outputting spurious messages.
|
|
// WHEN EDITING THIS TEST PLEASE CAREFULLY CONSIDER WHETHER THE NEW OUTPUT IS AN IMPROVEMENT.
|
|
final String testDirectory = fileSystem.path.join(flutterRoot, 'examples', 'hello_world');
|
|
final RegExp finalLine = RegExp(r'^The Flutter DevTools');
|
|
final ProcessTestResult result = await runFlutter(
|
|
<String>['run', '-dflutter-tester'],
|
|
testDirectory,
|
|
<Transition>[
|
|
Barrier(finalLine, handler: (String line) {
|
|
return 'h';
|
|
}),
|
|
Barrier(finalLine, handler: (String line) {
|
|
return 'q';
|
|
}),
|
|
const Barrier('Application finished.'),
|
|
],
|
|
);
|
|
expect(result.exitCode, 0);
|
|
expect(result.stderr, isEmpty);
|
|
expect(result.stdout, containsAllInOrder(<Object>[
|
|
startsWith('Launching '),
|
|
startsWith('Syncing files to device Flutter test device...'),
|
|
'',
|
|
'Flutter run key commands.',
|
|
startsWith('r Hot reload.'),
|
|
'R Hot restart.',
|
|
'h List all available interactive commands.',
|
|
'd Detach (terminate "flutter run" but leave application running).',
|
|
'c Clear the screen',
|
|
'q Quit (terminate the application on the device).',
|
|
'',
|
|
startsWith('A Dart VM Service on Flutter test device is available at: http://'),
|
|
startsWith('The Flutter DevTools debugger and profiler on Flutter test device is available at: http://'),
|
|
'',
|
|
'Flutter run key commands.',
|
|
startsWith('r Hot reload.'),
|
|
'R Hot restart.',
|
|
'v Open Flutter DevTools.',
|
|
'w Dump widget hierarchy to the console. (debugDumpApp)',
|
|
't Dump rendering tree to the console. (debugDumpRenderTree)',
|
|
'L Dump layer tree to the console. (debugDumpLayerTree)',
|
|
'f Dump focus tree to the console. (debugDumpFocusTree)',
|
|
'S Dump accessibility tree in traversal order. (debugDumpSemantics)',
|
|
'U Dump accessibility tree in inverse hit test order. (debugDumpSemantics)',
|
|
'i Toggle widget inspector. (WidgetsApp.showWidgetInspectorOverride)',
|
|
'p Toggle the display of construction lines. (debugPaintSizeEnabled)',
|
|
'I Toggle oversized image inversion. (debugInvertOversizedImages)',
|
|
'o Simulate different operating systems. (defaultTargetPlatform)',
|
|
'b Toggle platform brightness (dark and light mode). (debugBrightnessOverride)',
|
|
'P Toggle performance overlay. (WidgetsApp.showPerformanceOverlay)',
|
|
'a Toggle timeline events for all widget build methods. (debugProfileWidgetBuilds)',
|
|
'M Write SkSL shaders to a unique file in the project directory.',
|
|
'g Run source code generators.',
|
|
'j Dump frame raster stats for the current frame. (Unsupported for web)',
|
|
'h Repeat this help message.',
|
|
'd Detach (terminate "flutter run" but leave application running).',
|
|
'c Clear the screen',
|
|
'q Quit (terminate the application on the device).',
|
|
'',
|
|
startsWith('A Dart VM Service on Flutter test device is available at: http://'),
|
|
startsWith('The Flutter DevTools debugger and profiler on Flutter test device is available at: http://'),
|
|
'',
|
|
'Application finished.',
|
|
]));
|
|
});
|
|
}
|