Unverified Commit b6a6ea70 authored by Dan Field's avatar Dan Field Committed by GitHub

Allow writing startup_info.json to somewhere other than the build dir (#79338)

parent cbf9fb08
......@@ -39,7 +39,10 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
argParser
..addFlag('trace-startup',
negatable: false,
help: 'Trace application startup, then exit, saving the trace to a file.',
help: 'Trace application startup, then exit, saving the trace to a file. '
'By default, this will be saved in the "build" directory. If the '
'FLUTTER_TEST_OUTPUTS_DIR environment variable is set, the file '
'will be written there instead.',
)
..addFlag('verbose-system-logs',
negatable: false,
......
......@@ -18,6 +18,7 @@ import 'resident_runner.dart';
import 'tracing.dart';
import 'vmservice.dart';
const String kFlutterTestOutputsDirEnvName = 'FLUTTER_TEST_OUTPUTS_DIR';
class ColdRunner extends ResidentRunner {
ColdRunner(
List<FlutterDevice> devices, {
......@@ -118,11 +119,12 @@ class ColdRunner extends ResidentRunner {
final FlutterDevice device = flutterDevices.first;
if (device.vmService != null) {
globals.printStatus('Tracing startup on ${device.device.name}.');
final String outputPath = globals.platform.environment[kFlutterTestOutputsDirEnvName] ?? getBuildDirectory();
await downloadStartupTrace(
device.vmService,
awaitFirstFrame: awaitFirstFrameWhenTracing,
logger: globals.logger,
output: globals.fs.directory(getBuildDirectory()),
output: globals.fs.directory(outputPath),
);
}
appFinished();
......
......@@ -92,7 +92,7 @@ class Tracing {
}
/// Download the startup trace information from the given observatory client and
/// store it to build/start_up_info.json.
/// store it to `$output/start_up_info.json`.
Future<void> downloadStartupTrace(FlutterVmService vmService, {
bool awaitFirstFrame = true,
@required Logger logger,
......
......@@ -7,14 +7,17 @@
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/run_cold.dart';
import 'package:flutter_tools/src/tracing.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
import 'package:vm_service/vm_service.dart';
import '../src/common.dart';
import '../src/context.dart';
......@@ -50,7 +53,6 @@ void main() {
group('cleanupAtFinish()', () {
MockFlutterDevice mockFlutterDeviceFactory(Device device) {
final MockFlutterDevice mockFlutterDevice = MockFlutterDevice();
when(mockFlutterDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) => Future<void>.value(null));
when(mockFlutterDevice.device).thenReturn(device);
return mockFlutterDevice;
}
......@@ -69,13 +71,20 @@ void main() {
).cleanupAtFinish();
verify(mockDevice1.dispose());
verify(mockFlutterDevice1.stopEchoingDeviceLog());
expect(mockFlutterDevice1.stopEchoingDeviceLogCount, 1);
verify(mockDevice2.dispose());
verify(mockFlutterDevice2.stopEchoingDeviceLog());
expect(mockFlutterDevice2.stopEchoingDeviceLogCount, 1);
});
});
group('cold run', () {
MemoryFileSystem memoryFileSystem;
FakePlatform fakePlatform;
setUp(() {
memoryFileSystem = MemoryFileSystem();
fakePlatform = FakePlatform(environment: <String, String>{});
});
testUsingContext('calls runCold on attached device', () async {
final MockDevice mockDevice = MockDevice();
final MockFlutterDevice mockFlutterDevice = MockFlutterDevice();
......@@ -101,10 +110,78 @@ void main() {
route: anyNamed('route'),
));
});
testUsingContext('with traceStartup, no env variable', () async {
final MockDevice mockDevice = MockDevice();
final MockFlutterDevice mockFlutterDevice = MockFlutterDevice();
when(mockFlutterDevice.device).thenReturn(mockDevice);
when(mockFlutterDevice.runCold(
coldRunner: anyNamed('coldRunner'),
route: anyNamed('route')
)).thenAnswer((Invocation invocation) => Future<int>.value(0));
final List<FlutterDevice> devices = <FlutterDevice>[mockFlutterDevice];
final File applicationBinary = MemoryFileSystem.test().file('binary');
final int result = await ColdRunner(
devices,
applicationBinary: applicationBinary,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
traceStartup: true,
).run(
enableDevTools: false,
);
expect(result, 0);
expect(memoryFileSystem.directory(getBuildDirectory()).childFile('start_up_info.json').existsSync(), true);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
ProcessManager: () => FakeProcessManager.any(),
Platform: () => fakePlatform,
});
testUsingContext('with traceStartup, env variable', () async {
fakePlatform.environment[kFlutterTestOutputsDirEnvName] = 'test_output_dir';
final MockDevice mockDevice = MockDevice();
final MockFlutterDevice mockFlutterDevice = MockFlutterDevice();
when(mockFlutterDevice.device).thenReturn(mockDevice);
when(mockFlutterDevice.runCold(
coldRunner: anyNamed('coldRunner'),
route: anyNamed('route')
)).thenAnswer((Invocation invocation) => Future<int>.value(0));
final List<FlutterDevice> devices = <FlutterDevice>[mockFlutterDevice];
final File applicationBinary = MemoryFileSystem.test().file('binary');
final int result = await ColdRunner(
devices,
applicationBinary: applicationBinary,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
traceStartup: true,
).run(
enableDevTools: false,
);
expect(result, 0);
expect(memoryFileSystem.directory('test_output_dir').childFile('start_up_info.json').existsSync(), true);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFileSystem,
ProcessManager: () => FakeProcessManager.any(),
Platform: () => fakePlatform,
});
});
}
class MockFlutterDevice extends Mock implements FlutterDevice {}
class MockFlutterDevice extends Mock implements FlutterDevice {
int stopEchoingDeviceLogCount = 0;
@override
Future<void> stopEchoingDeviceLog() async {
stopEchoingDeviceLogCount += 1;
}
@override
FlutterVmService get vmService => FakeFlutterVmService();
}
class MockDevice extends Mock implements Device {
MockDevice() {
when(isSupported()).thenReturn(true);
......@@ -141,3 +218,51 @@ class TestFlutterDevice extends FlutterDevice {
}
class FakeResidentCompiler extends Fake implements ResidentCompiler {}
class FakeFlutterVmService extends Fake implements FlutterVmService {
@override
VmService get service => FakeVmService();
@override
Future<List<FlutterView>> getFlutterViews({bool returnEarly = false, Duration delay = const Duration(milliseconds: 50)}) async {
return <FlutterView>[];
}
@override
Future<bool> flutterAlreadyPaintedFirstUsefulFrame({String isolateId}) async => true;
@override
Future<Response> getTimeline() async {
return Response.parse(<String, dynamic>{
'traceEvents': <dynamic>[
<String, dynamic>{
'name': kFlutterEngineMainEnterEventName,
'ts': 123,
},
<String, dynamic>{
'name': kFirstFrameBuiltEventName,
'ts': 124,
},
<String, dynamic>{
'name': kFirstFrameRasterizedEventName,
'ts': 124,
},
],
});
}
@override
Future<void> setTimelineFlags(List<String> recordedStreams) async {}
}
class FakeVmService extends Fake implements VmService {
@override
Future<Success> streamListen(String streamId) async => Success();
@override
Stream<Event> get onExtensionEvent {
return Stream<Event>.fromIterable(<Event>[
Event(kind: 'Extension', extensionKind: 'Flutter.FirstFrame', timestamp: 1),
]);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment