LCOV - code coverage report
Current view: top level - test_api-0.4.8/lib/src/backend - remote_listener.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 79 115 68.7 %
Date: 2021-11-28 14:37:50 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
       2             : // for details. All rights reserved. Use of this source code is governed by a
       3             : // BSD-style license that can be found in the LICENSE file.
       4             : 
       5             : import 'dart:async';
       6             : 
       7             : import 'package:async/async.dart';
       8             : import 'package:stream_channel/stream_channel.dart';
       9             : import 'package:term_glyph/term_glyph.dart' as glyph;
      10             : 
      11             : import 'declarer.dart';
      12             : import 'group.dart';
      13             : import 'invoker.dart';
      14             : import 'live_test.dart';
      15             : import 'metadata.dart';
      16             : import 'remote_exception.dart';
      17             : import 'stack_trace_formatter.dart';
      18             : import 'suite.dart';
      19             : import 'suite_channel_manager.dart';
      20             : import 'suite_platform.dart';
      21             : import 'test.dart';
      22             : 
      23             : class RemoteListener {
      24             :   /// The test suite to run.
      25             :   final Suite _suite;
      26             : 
      27             :   /// The zone to forward prints to, or `null` if prints shouldn't be forwarded.
      28             :   final Zone? _printZone;
      29             : 
      30             :   /// Extracts metadata about all the tests in the function returned by
      31             :   /// [getMain] and returns a channel that will send information about them.
      32             :   ///
      33             :   /// The main function is wrapped in a closure so that we can handle it being
      34             :   /// undefined here rather than in the generated code.
      35             :   ///
      36             :   /// Once that's done, this starts listening for commands about which tests to
      37             :   /// run.
      38             :   ///
      39             :   /// If [hidePrints] is `true` (the default), calls to `print()` within this
      40             :   /// suite will not be forwarded to the parent zone's print handler. However,
      41             :   /// the caller may want them to be forwarded in (for example) a browser
      42             :   /// context where they'll be visible in the development console.
      43             :   ///
      44             :   /// If [beforeLoad] is passed, it's called before the tests have been declared
      45             :   /// for this worker.
      46          11 :   static StreamChannel<Object?> start(Function Function() getMain,
      47             :       {bool hidePrints = true,
      48             :       Future Function(
      49             :               StreamChannel<Object?> Function(String name) suiteChannel)?
      50             :           beforeLoad}) {
      51             :     // Synchronous in order to allow `print` output to show up immediately, even
      52             :     // if they are followed by long running synchronous work.
      53             :     var controller =
      54          11 :         StreamChannelController<Object?>(allowForeignErrors: false, sync: true);
      55          22 :     var channel = MultiChannel<Object?>(controller.local);
      56             : 
      57             :     var verboseChain = true;
      58             : 
      59           0 :     var printZone = hidePrints ? null : Zone.current;
      60          11 :     var spec = ZoneSpecification(print: (_, __, ___, line) {
      61           0 :       if (printZone != null) printZone.print(line);
      62           0 :       channel.sink.add({'type': 'print', 'line': line});
      63             :     });
      64             : 
      65          11 :     final suiteChannelManager = SuiteChannelManager();
      66          33 :     StackTraceFormatter().asCurrent(() {
      67          22 :       runZonedGuarded(() async {
      68             :         Function? main;
      69             :         try {
      70             :           main = getMain();
      71           0 :         } on NoSuchMethodError catch (_) {
      72           0 :           _sendLoadException(channel, 'No top-level main() function defined.');
      73             :           return;
      74             :         } catch (error, stackTrace) {
      75           0 :           _sendError(channel, error, stackTrace, verboseChain);
      76             :           return;
      77             :         }
      78             : 
      79          11 :         if (main is! Function()) {
      80           0 :           _sendLoadException(
      81             :               channel, 'Top-level main() function takes arguments.');
      82             :           return;
      83             :         }
      84             : 
      85          22 :         var queue = StreamQueue(channel.stream);
      86          22 :         var message = await queue.next as Map;
      87          22 :         assert(message['type'] == 'initial');
      88             : 
      89          33 :         queue.rest.cast<Map>().listen((message) {
      90           0 :           if (message['type'] == 'close') {
      91           0 :             controller.local.sink.close();
      92             :             return;
      93             :           }
      94             : 
      95           0 :           assert(message['type'] == 'suiteChannel');
      96           0 :           suiteChannelManager.connectIn(message['name'] as String,
      97           0 :               channel.virtualChannel(message['id'] as int));
      98             :         });
      99             : 
     100          11 :         if ((message['asciiGlyphs'] as bool?) ?? false) glyph.ascii = true;
     101          22 :         var metadata = Metadata.deserialize(message['metadata']);
     102          11 :         verboseChain = metadata.verboseTrace;
     103          11 :         var declarer = Declarer(
     104             :           metadata: metadata,
     105          22 :           platformVariables: Set.from(message['platformVariables'] as Iterable),
     106          11 :           collectTraces: message['collectTraces'] as bool,
     107          11 :           noRetry: message['noRetry'] as bool,
     108             :           // TODO: Change to non-nullable https://github.com/dart-lang/test/issues/1591
     109             :           allowDuplicateTestNames:
     110          11 :               message['allowDuplicateTestNames'] as bool? ?? true,
     111             :         );
     112          22 :         StackTraceFormatter.current!.configure(
     113          22 :             except: _deserializeSet(message['foldTraceExcept'] as List),
     114          22 :             only: _deserializeSet(message['foldTraceOnly'] as List));
     115             : 
     116             :         if (beforeLoad != null) {
     117           0 :           await beforeLoad(suiteChannelManager.connectOut);
     118             :         }
     119             : 
     120          22 :         await declarer.declare(main);
     121             : 
     122          22 :         var suite = Suite(declarer.build(),
     123          22 :             SuitePlatform.deserialize(message['platform'] as Object),
     124          11 :             path: message['path'] as String);
     125             : 
     126          22 :         runZoned(() {
     127          11 :           Invoker.guard(
     128          33 :               () => RemoteListener._(suite, printZone)._listen(channel));
     129             :         },
     130             :             // Make the declarer visible to running tests so that they'll throw
     131             :             // useful errors when calling `test()` and `group()` within a test,
     132             :             // and so they can add to the declarer's `tearDownAll()` list.
     133          11 :             zoneValues: {#test.declarer: declarer});
     134           0 :       }, (error, stackTrace) {
     135           0 :         _sendError(channel, error, stackTrace, verboseChain);
     136             :       }, zoneSpecification: spec);
     137             :     });
     138             : 
     139          11 :     return controller.foreign;
     140             :   }
     141             : 
     142             :   /// Returns a [Set] from a JSON serialized list of strings, or `null` if the
     143             :   /// list is empty or `null`.
     144          11 :   static Set<String>? _deserializeSet(List? list) {
     145             :     if (list == null) return null;
     146          11 :     if (list.isEmpty) return null;
     147           0 :     return Set.from(list);
     148             :   }
     149             : 
     150             :   /// Sends a message over [channel] indicating that the tests failed to load.
     151             :   ///
     152             :   /// [message] should describe the failure.
     153           0 :   static void _sendLoadException(StreamChannel channel, String message) {
     154           0 :     channel.sink.add({'type': 'loadException', 'message': message});
     155             :   }
     156             : 
     157             :   /// Sends a message over [channel] indicating an error from user code.
     158           0 :   static void _sendError(StreamChannel channel, Object error,
     159             :       StackTrace stackTrace, bool verboseChain) {
     160           0 :     channel.sink.add({
     161             :       'type': 'error',
     162           0 :       'error': RemoteException.serialize(
     163             :           error,
     164           0 :           StackTraceFormatter.current!
     165           0 :               .formatStackTrace(stackTrace, verbose: verboseChain))
     166             :     });
     167             :   }
     168             : 
     169          11 :   RemoteListener._(this._suite, this._printZone);
     170             : 
     171             :   /// Send information about [_suite] across [channel] and start listening for
     172             :   /// commands to run the tests.
     173          11 :   void _listen(MultiChannel channel) {
     174          33 :     channel.sink.add({
     175             :       'type': 'success',
     176          44 :       'root': _serializeGroup(channel, _suite.group, [])
     177             :     });
     178             :   }
     179             : 
     180             :   /// Serializes [group] into a JSON-safe map.
     181             :   ///
     182             :   /// [parents] lists the groups that contain [group].
     183          11 :   Map _serializeGroup(
     184             :       MultiChannel channel, Group group, Iterable<Group> parents) {
     185          22 :     parents = parents.toList()..add(group);
     186          11 :     return {
     187             :       'type': 'group',
     188          11 :       'name': group.name,
     189          22 :       'metadata': group.metadata.serialize(),
     190          11 :       'trace': group.trace == null
     191             :           ? null
     192           2 :           : StackTraceFormatter.current
     193           4 :                   ?.formatStackTrace(group.trace!)
     194           2 :                   .toString() ??
     195           0 :               group.trace?.toString(),
     196          22 :       'setUpAll': _serializeTest(channel, group.setUpAll, parents),
     197          22 :       'tearDownAll': _serializeTest(channel, group.tearDownAll, parents),
     198          33 :       'entries': group.entries.map((entry) {
     199          11 :         return entry is Group
     200           2 :             ? _serializeGroup(channel, entry, parents)
     201          11 :             : _serializeTest(channel, entry as Test, parents);
     202          11 :       }).toList()
     203             :     };
     204             :   }
     205             : 
     206             :   /// Serializes [test] into a JSON-safe map.
     207             :   ///
     208             :   /// [groups] lists the groups that contain [test]. Returns `null` if [test]
     209             :   /// is `null`.
     210          11 :   Map? _serializeTest(
     211             :       MultiChannel channel, Test? test, Iterable<Group>? groups) {
     212             :     if (test == null) return null;
     213             : 
     214          11 :     var testChannel = channel.virtualChannel();
     215          33 :     testChannel.stream.listen((message) {
     216          22 :       assert(message['command'] == 'run');
     217          33 :       _runLiveTest(test.load(_suite, groups: groups),
     218          22 :           channel.virtualChannel(message['channel'] as int));
     219             :     });
     220             : 
     221          11 :     return {
     222             :       'type': 'test',
     223          11 :       'name': test.name,
     224          22 :       'metadata': test.metadata.serialize(),
     225          11 :       'trace': test.trace == null
     226             :           ? null
     227          11 :           : StackTraceFormatter.current
     228          22 :                   ?.formatStackTrace(test.trace!)
     229          11 :                   .toString() ??
     230           0 :               test.trace?.toString(),
     231          11 :       'channel': testChannel.id
     232             :     };
     233             :   }
     234             : 
     235             :   /// Runs [liveTest] and sends the results across [channel].
     236          11 :   void _runLiveTest(LiveTest liveTest, MultiChannel channel) {
     237          22 :     channel.stream.listen((message) {
     238           0 :       assert(message['command'] == 'close');
     239           0 :       liveTest.close();
     240             :     });
     241             : 
     242          33 :     liveTest.onStateChange.listen((state) {
     243          33 :       channel.sink.add({
     244             :         'type': 'state-change',
     245          22 :         'status': state.status.name,
     246          22 :         'result': state.result.name
     247             :       });
     248             :     });
     249             : 
     250          22 :     liveTest.onError.listen((asyncError) {
     251           0 :       channel.sink.add({
     252             :         'type': 'error',
     253           0 :         'error': RemoteException.serialize(
     254           0 :             asyncError.error,
     255           0 :             StackTraceFormatter.current!.formatStackTrace(asyncError.stackTrace,
     256           0 :                 verbose: liveTest.test.metadata.verboseTrace))
     257             :       });
     258             :     });
     259             : 
     260          22 :     liveTest.onMessage.listen((message) {
     261           0 :       if (_printZone != null) _printZone!.print(message.text);
     262           0 :       channel.sink.add({
     263             :         'type': 'message',
     264           0 :         'message-type': message.type.name,
     265           0 :         'text': message.text
     266             :       });
     267             :     });
     268             : 
     269          22 :     runZoned(() {
     270          66 :       liveTest.run().then((_) => channel.sink.add({'type': 'complete'}));
     271          11 :     }, zoneValues: {#test.runner.test_channel: channel});
     272             :   }
     273             : }

Generated by: LCOV version 1.14