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 ...@@ -39,7 +39,10 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
argParser argParser
..addFlag('trace-startup', ..addFlag('trace-startup',
negatable: false, 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', ..addFlag('verbose-system-logs',
negatable: false, negatable: false,
......
...@@ -18,6 +18,7 @@ import 'resident_runner.dart'; ...@@ -18,6 +18,7 @@ import 'resident_runner.dart';
import 'tracing.dart'; import 'tracing.dart';
import 'vmservice.dart'; import 'vmservice.dart';
const String kFlutterTestOutputsDirEnvName = 'FLUTTER_TEST_OUTPUTS_DIR';
class ColdRunner extends ResidentRunner { class ColdRunner extends ResidentRunner {
ColdRunner( ColdRunner(
List<FlutterDevice> devices, { List<FlutterDevice> devices, {
...@@ -118,11 +119,12 @@ class ColdRunner extends ResidentRunner { ...@@ -118,11 +119,12 @@ class ColdRunner extends ResidentRunner {
final FlutterDevice device = flutterDevices.first; final FlutterDevice device = flutterDevices.first;
if (device.vmService != null) { if (device.vmService != null) {
globals.printStatus('Tracing startup on ${device.device.name}.'); globals.printStatus('Tracing startup on ${device.device.name}.');
final String outputPath = globals.platform.environment[kFlutterTestOutputsDirEnvName] ?? getBuildDirectory();
await downloadStartupTrace( await downloadStartupTrace(
device.vmService, device.vmService,
awaitFirstFrame: awaitFirstFrameWhenTracing, awaitFirstFrame: awaitFirstFrameWhenTracing,
logger: globals.logger, logger: globals.logger,
output: globals.fs.directory(getBuildDirectory()), output: globals.fs.directory(outputPath),
); );
} }
appFinished(); appFinished();
......
...@@ -92,7 +92,7 @@ class Tracing { ...@@ -92,7 +92,7 @@ class Tracing {
} }
/// Download the startup trace information from the given observatory client and /// 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, { Future<void> downloadStartupTrace(FlutterVmService vmService, {
bool awaitFirstFrame = true, bool awaitFirstFrame = true,
@required Logger logger, @required Logger logger,
......
...@@ -7,14 +7,17 @@ ...@@ -7,14 +7,17 @@
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.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/build_info.dart';
import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/run_cold.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:flutter_tools/src/vmservice.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:vm_service/vm_service.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
...@@ -50,7 +53,6 @@ void main() { ...@@ -50,7 +53,6 @@ void main() {
group('cleanupAtFinish()', () { group('cleanupAtFinish()', () {
MockFlutterDevice mockFlutterDeviceFactory(Device device) { MockFlutterDevice mockFlutterDeviceFactory(Device device) {
final MockFlutterDevice mockFlutterDevice = MockFlutterDevice(); final MockFlutterDevice mockFlutterDevice = MockFlutterDevice();
when(mockFlutterDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) => Future<void>.value(null));
when(mockFlutterDevice.device).thenReturn(device); when(mockFlutterDevice.device).thenReturn(device);
return mockFlutterDevice; return mockFlutterDevice;
} }
...@@ -69,13 +71,20 @@ void main() { ...@@ -69,13 +71,20 @@ void main() {
).cleanupAtFinish(); ).cleanupAtFinish();
verify(mockDevice1.dispose()); verify(mockDevice1.dispose());
verify(mockFlutterDevice1.stopEchoingDeviceLog()); expect(mockFlutterDevice1.stopEchoingDeviceLogCount, 1);
verify(mockDevice2.dispose()); verify(mockDevice2.dispose());
verify(mockFlutterDevice2.stopEchoingDeviceLog()); expect(mockFlutterDevice2.stopEchoingDeviceLogCount, 1);
}); });
}); });
group('cold run', () { group('cold run', () {
MemoryFileSystem memoryFileSystem;
FakePlatform fakePlatform;
setUp(() {
memoryFileSystem = MemoryFileSystem();
fakePlatform = FakePlatform(environment: <String, String>{});
});
testUsingContext('calls runCold on attached device', () async { testUsingContext('calls runCold on attached device', () async {
final MockDevice mockDevice = MockDevice(); final MockDevice mockDevice = MockDevice();
final MockFlutterDevice mockFlutterDevice = MockFlutterDevice(); final MockFlutterDevice mockFlutterDevice = MockFlutterDevice();
...@@ -101,10 +110,78 @@ void main() { ...@@ -101,10 +110,78 @@ void main() {
route: anyNamed('route'), 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 { class MockDevice extends Mock implements Device {
MockDevice() { MockDevice() {
when(isSupported()).thenReturn(true); when(isSupported()).thenReturn(true);
...@@ -141,3 +218,51 @@ class TestFlutterDevice extends FlutterDevice { ...@@ -141,3 +218,51 @@ class TestFlutterDevice extends FlutterDevice {
} }
class FakeResidentCompiler extends Fake implements ResidentCompiler {} 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