ios_device_logger_test.dart 12.8 KB
Newer Older
1 2 3 4
// 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.

5 6
import 'dart:async';

7
import 'package:flutter_tools/src/artifacts.dart';
8
import 'package:flutter_tools/src/base/async_guard.dart';
9
import 'package:flutter_tools/src/base/logger.dart';
10
import 'package:flutter_tools/src/cache.dart';
11
import 'package:flutter_tools/src/convert.dart';
12 13
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/devices.dart';
14
import 'package:flutter_tools/src/ios/ios_deploy.dart';
15
import 'package:flutter_tools/src/ios/mac.dart';
16
import 'package:flutter_tools/src/vmservice.dart';
17
import 'package:test/fake.dart';
18
import 'package:vm_service/vm_service.dart';
19 20

import '../../src/common.dart';
21
import '../../src/fake_process_manager.dart';
22
import '../../src/fake_vm_services.dart';
23 24

void main() {
25 26 27 28 29
  late FakeProcessManager processManager;
  late Artifacts artifacts;
  late Cache fakeCache;
  late BufferLogger logger;
  late String ideviceSyslogPath;
30 31

  setUp(() {
32
    processManager = FakeProcessManager.empty();
33
    fakeCache = Cache.test(processManager: FakeProcessManager.any());
34
    artifacts = Artifacts.test();
35
    logger = BufferLogger.test();
36
    ideviceSyslogPath = artifacts.getHostArtifact(HostArtifact.idevicesyslog).path;
37 38
  });

39 40 41
  group('syslog stream', () {
    testWithoutContext('decodeSyslog decodes a syslog-encoded line', () {
      final String decoded = decodeSyslog(
42 43
          r'I \M-b\M^]\M-$\M-o\M-8\M^O syslog '
          r'\M-B\M-/\134_(\M-c\M^C\M^D)_/\M-B\M-/ \M-l\M^F\240!');
44

45 46
      expect(decoded, r'I ❤️ syslog ¯\_(ツ)_/¯ 솠!');
    });
47

48 49
    testWithoutContext('decodeSyslog passes through un-decodeable lines as-is', () {
      final String decoded = decodeSyslog(r'I \M-b\M^O syslog!');
50

51 52
      expect(decoded, r'I \M-b\M^O syslog!');
    });
53

54 55
    testWithoutContext('IOSDeviceLogReader suppresses non-Flutter lines from output with syslog', () async {
      processManager.addCommand(
56
        FakeCommand(
57
            command: <String>[
58
              ideviceSyslogPath, '-u', '1234',
59 60
            ],
            stdout: '''
61 62 63 64 65 66
Runner(Flutter)[297] <Notice>: A is for ari
Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt MobileGestaltSupport.m:153: pid 123 (Runner) does not have sandbox access for frZQaeyWLUvLjeuEK43hmg and IS NOT appropriately entitled
Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt MobileGestalt.c:550: no access to InverseDeviceID (see <rdar://problem/11744455>)
Runner(Flutter)[297] <Notice>: I is for ichigo
Runner(UIKit)[297] <Notice>: E is for enpitsu"
'''
67 68 69 70 71 72 73 74 75 76 77 78 79 80
        ),
      );
      final DeviceLogReader logReader = IOSDeviceLogReader.test(
        iMobileDevice: IMobileDevice(
          artifacts: artifacts,
          processManager: processManager,
          cache: fakeCache,
          logger: logger,
        ),
      );
      final List<String> lines = await logReader.logLines.toList();

      expect(lines, <String>['A is for ari', 'I is for ichigo']);
    });
81

82 83
    testWithoutContext('IOSDeviceLogReader includes multi-line Flutter logs in the output with syslog', () async {
      processManager.addCommand(
84
        FakeCommand(
85
            command: <String>[
86
              ideviceSyslogPath, '-u', '1234',
87 88
            ],
            stdout: '''
89 90 91 92 93 94
Runner(Flutter)[297] <Notice>: This is a multi-line message,
  with another Flutter message following it.
Runner(Flutter)[297] <Notice>: This is a multi-line message,
  with a non-Flutter log message following it.
Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
'''
95 96 97 98 99 100 101 102 103 104 105
        ),
      );
      final DeviceLogReader logReader = IOSDeviceLogReader.test(
        iMobileDevice: IMobileDevice(
          artifacts: artifacts,
          processManager: processManager,
          cache: fakeCache,
          logger: logger,
        ),
      );
      final List<String> lines = await logReader.logLines.toList();
106

107 108 109 110 111 112 113 114 115 116
      expect(lines, <String>[
        'This is a multi-line message,',
        '  with another Flutter message following it.',
        'This is a multi-line message,',
        '  with a non-Flutter log message following it.',
      ]);
    });

    testWithoutContext('includes multi-line Flutter logs in the output', () async {
      processManager.addCommand(
117
        FakeCommand(
118
          command: <String>[
119
            ideviceSyslogPath, '-u', '1234',
120 121
          ],
          stdout: '''
122 123 124 125 126 127
Runner(Flutter)[297] <Notice>: This is a multi-line message,
  with another Flutter message following it.
Runner(Flutter)[297] <Notice>: This is a multi-line message,
  with a non-Flutter log message following it.
Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
''',
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
        ),
      );

      final DeviceLogReader logReader = IOSDeviceLogReader.test(
        iMobileDevice: IMobileDevice(
          artifacts: artifacts,
          processManager: processManager,
          cache: fakeCache,
          logger: logger,
        ),
      );
      final List<String> lines = await logReader.logLines.toList();

      expect(lines, <String>[
        'This is a multi-line message,',
        '  with another Flutter message following it.',
        'This is a multi-line message,',
        '  with a non-Flutter log message following it.',
      ]);
    });
148
  });
149

150 151
  group('VM service', () {
    testWithoutContext('IOSDeviceLogReader can listen to VM Service logs', () async {
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
      final Event stdoutEvent = Event(
        kind: 'Stdout',
        timestamp: 0,
        bytes: base64.encode(utf8.encode('  This is a message ')),
      );
      final Event stderrEvent = Event(
        kind: 'Stderr',
        timestamp: 0,
        bytes: base64.encode(utf8.encode('  And this is an error ')),
      );
      final FlutterVmService vmService = FakeVmServiceHost(requests: <VmServiceExpectation>[
        const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{
          'streamId': 'Debug',
        }),
        const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{
          'streamId': 'Stdout',
        }),
        const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{
          'streamId': 'Stderr',
        }),
        FakeVmServiceStreamResponse(event: stdoutEvent, streamId: 'Stdout'),
        FakeVmServiceStreamResponse(event: stderrEvent, streamId: 'Stderr'),
      ]).vmService;
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
      final DeviceLogReader logReader = IOSDeviceLogReader.test(
        useSyslog: false,
        iMobileDevice: IMobileDevice(
          artifacts: artifacts,
          processManager: processManager,
          cache: fakeCache,
          logger: logger,
        ),
      );
      logReader.connectedVMService = vmService;

      // Wait for stream listeners to fire.
      await expectLater(logReader.logLines, emitsInAnyOrder(<Matcher>[
        equals('  This is a message '),
        equals('  And this is an error '),
      ]));
191
    });
192 193

    testWithoutContext('IOSDeviceLogReader ignores VM Service logs when attached to debugger', () async {
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
      final Event stdoutEvent = Event(
        kind: 'Stdout',
        timestamp: 0,
        bytes: base64.encode(utf8.encode('  This is a message ')),
      );
      final Event stderrEvent = Event(
        kind: 'Stderr',
        timestamp: 0,
        bytes: base64.encode(utf8.encode('  And this is an error ')),
      );
      final FlutterVmService vmService = FakeVmServiceHost(requests: <VmServiceExpectation>[
        const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{
          'streamId': 'Debug',
        }),
        const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{
          'streamId': 'Stdout',
        }),
        const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{
          'streamId': 'Stderr',
        }),
        FakeVmServiceStreamResponse(event: stdoutEvent, streamId: 'Stdout'),
        FakeVmServiceStreamResponse(event: stderrEvent, streamId: 'Stderr'),
      ]).vmService;
217 218 219 220 221 222 223 224 225 226 227
      final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
        useSyslog: false,
        iMobileDevice: IMobileDevice(
          artifacts: artifacts,
          processManager: processManager,
          cache: fakeCache,
          logger: logger,
        ),
      );
      logReader.connectedVMService = vmService;

228 229
      final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger();
      iosDeployDebugger.debuggerAttached = true;
230 231

      final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[
232
        'Message from debugger',
233
      ]);
234
      iosDeployDebugger.logLines = debuggingLogs;
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
      logReader.debuggerStream = iosDeployDebugger;

      // Wait for stream listeners to fire.
      await expectLater(logReader.logLines, emitsInAnyOrder(<Matcher>[
        equals('Message from debugger'),
      ]));
    });
  });

  group('debugger stream', () {
    testWithoutContext('IOSDeviceLogReader removes metadata prefix from lldb output', () async {
      final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[
        '2020-09-15 19:15:10.931434-0700 Runner[541:226276] Did finish launching.',
        '2020-09-15 19:15:10.931434-0700 Runner[541:226276] [Category] Did finish launching from logging category.',
        'stderr from dart',
        '',
      ]);

      final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
        iMobileDevice: IMobileDevice(
          artifacts: artifacts,
          processManager: processManager,
          cache: fakeCache,
          logger: logger,
        ),
        useSyslog: false,
      );
262 263
      final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger();
      iosDeployDebugger.logLines = debuggingLogs;
264 265 266 267 268 269 270 271 272
      logReader.debuggerStream = iosDeployDebugger;
      final Future<List<String>> logLines = logReader.logLines.toList();

      expect(await logLines, <String>[
        'Did finish launching.',
        '[Category] Did finish launching from logging category.',
        'stderr from dart',
        '',
      ]);
273
    });
274 275 276 277 278 279 280 281 282 283 284 285 286

    testWithoutContext('errors on debugger stream closes log stream', () async {
      final Stream<String> debuggingLogs = Stream<String>.error('ios-deploy error');
      final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
        iMobileDevice: IMobileDevice(
          artifacts: artifacts,
          processManager: processManager,
          cache: fakeCache,
          logger: logger,
        ),
        useSyslog: false,
      );
      final Completer<void> streamComplete = Completer<void>();
287 288
      final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger();
      iosDeployDebugger.logLines = debuggingLogs;
289 290 291 292
      logReader.logLines.listen(null, onError: (Object error) => streamComplete.complete());
      logReader.debuggerStream = iosDeployDebugger;

      await streamComplete.future;
293
    });
294 295 296 297 298 299 300 301 302 303 304

    testWithoutContext('detaches debugger', () async {
      final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
        iMobileDevice: IMobileDevice(
          artifacts: artifacts,
          processManager: processManager,
          cache: fakeCache,
          logger: logger,
        ),
        useSyslog: false,
      );
305
      final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger();
306 307 308
      logReader.debuggerStream = iosDeployDebugger;

      logReader.dispose();
309
      expect(iosDeployDebugger.detached, true);
310
    });
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328

    testWithoutContext('Does not throw if debuggerStream set after logReader closed', () async {
      final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[
        '2020-09-15 19:15:10.931434-0700 Runner[541:226276] Did finish launching.',
        '2020-09-15 19:15:10.931434-0700 Runner[541:226276] [Category] Did finish launching from logging category.',
        'stderr from dart',
        '',
      ]);

      final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
        iMobileDevice: IMobileDevice(
          artifacts: artifacts,
          processManager: processManager,
          cache: fakeCache,
          logger: logger,
        ),
        useSyslog: false,
      );
329 330
      Object? exception;
      StackTrace? trace;
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
      await asyncGuard(
          () async {
            await logReader.linesController.close();
            final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger();
            iosDeployDebugger.logLines = debuggingLogs;
            logReader.debuggerStream = iosDeployDebugger;
            await logReader.logLines.drain<void>();
          },
          onError: (Object err, StackTrace stackTrace) {
            exception = err;
            trace = stackTrace;
          }
      );
      expect(
        exception,
        isNull,
        reason: trace.toString(),
      );
    });
350
  });
351 352
}

353 354 355 356 357 358 359 360 361 362 363 364 365 366
class FakeIOSDeployDebugger extends Fake implements IOSDeployDebugger {
  bool detached = false;

  @override
  bool debuggerAttached = false;

  @override
  Stream<String> logLines = const Stream<String>.empty();

  @override
  void detach() {
    detached = true;
  }
}