flutter_platform.dart 5.29 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:io';
8
import 'dart:math' as math;
9 10 11 12 13

import 'package:async/async.dart';
import 'package:path/path.dart' as path;
import 'package:stream_channel/stream_channel.dart';

Ian Hickson's avatar
Ian Hickson committed
14 15 16
import 'package:test/src/backend/test_platform.dart'; // ignore: implementation_imports
import 'package:test/src/runner/plugin/platform.dart'; // ignore: implementation_imports
import 'package:test/src/runner/plugin/hack_register_platform.dart' as hack; // ignore: implementation_imports
17

18
import '../dart/package_map.dart';
19
import '../globals.dart';
20
import 'coverage_collector.dart';
21 22 23 24 25 26 27 28

final String _kSkyShell = Platform.environment['SKY_SHELL'];
const String _kHost = '127.0.0.1';
const String _kPath = '/runner';

String shellPath;

void installHook() {
29
  hack.registerPlatformPlugin(<TestPlatform>[TestPlatform.vm], () => new FlutterPlatform());
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
}

class _ServerInfo {
  final String url;
  final Future<WebSocket> socket;
  final HttpServer server;

  _ServerInfo(this.server, this.url, this.socket);
}

Future<_ServerInfo> _startServer() async {
  HttpServer server = await HttpServer.bind(_kHost, 0);
  Completer<WebSocket> socket = new Completer<WebSocket>();
  server.listen((HttpRequest request) {
    if (request.uri.path == _kPath)
      socket.complete(WebSocketTransformer.upgrade(request));
  });
  return new _ServerInfo(server, 'ws://$_kHost:${server.port}$_kPath', socket.future);
}

50
Future<Process> _startProcess(String mainPath, { String packages, int observatoryPort }) {
51
  assert(shellPath != null || _kSkyShell != null); // Please provide the path to the shell in the SKY_SHELL environment variable.
52
  String executable = shellPath ?? _kSkyShell;
53 54 55 56 57 58 59
  List<String> arguments = <String>[];
  if (observatoryPort != null) {
    arguments.add('--observatory-port=$observatoryPort');
  } else {
    arguments.add('--non-interactive');
  }
  arguments.addAll(<String>[
60
    '--enable-checked-mode',
61
    '--packages=$packages',
62
    mainPath
63
  ]);
64 65
  printTrace('$executable ${arguments.join(' ')}');
  return Process.start(executable, arguments, environment: <String, String>{ 'FLUTTER_TEST': 'true' });
66 67
}

68 69 70 71 72 73 74 75 76 77 78 79
void _attachStandardStreams(Process process) {
  for (Stream<List<int>> stream in
      <Stream<List<int>>>[process.stderr, process.stdout]) {
    stream.transform(UTF8.decoder)
      .transform(const LineSplitter())
      .listen((String line) {
        if (line != null)
          print('Shell: $line');
      });
  }
}

80
class FlutterPlatform extends PlatformPlugin {
81
  @override
82
  StreamChannel<dynamic> loadChannel(String mainPath, TestPlatform platform) {
83 84 85
    return StreamChannelCompleter.fromFuture(_startTest(mainPath));
  }

86
  Future<StreamChannel<dynamic>> _startTest(String mainPath) async {
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
    _ServerInfo info = await _startServer();
    Directory tempDir = Directory.systemTemp.createTempSync(
        'dart_test_listener');
    File listenerFile = new File('${tempDir.path}/listener.dart');
    listenerFile.createSync();
    listenerFile.writeAsStringSync('''
import 'dart:convert';
import 'dart:io';

import 'package:stream_channel/stream_channel.dart';
import 'package:test/src/runner/plugin/remote_platform_helpers.dart';
import 'package:test/src/runner/vm/catch_isolate_errors.dart';

import '${path.toUri(path.absolute(mainPath))}' as test;

void main() {
  String server = Uri.decodeComponent('${Uri.encodeComponent(info.url)}');
  StreamChannel channel = serializeSuite(() {
    catchIsolateErrors();
    return test.main;
  });
  WebSocket.connect(server).then((WebSocket socket) {
    socket.map(JSON.decode).pipe(channel.sink);
    socket.addStream(channel.stream.map(JSON.encode));
  });
}
''');

115 116 117 118 119 120
    int observatoryPort;
    if (CoverageCollector.instance.enabled) {
      observatoryPort = new math.Random().nextInt(30000) + 2000;
      await CoverageCollector.instance.finishPendingJobs();
    }

121
    Process process = await _startProcess(
122 123 124
      listenerFile.path,
      packages: PackageMap.globalPackagesPath,
      observatoryPort: observatoryPort
125 126
    );

127 128
    _attachStandardStreams(process);

129 130 131 132
    void finalize() {
      if (process != null) {
        Process processToKill = process;
        process = null;
133 134 135 136 137
        CoverageCollector.instance.collectCoverage(
          host: _kHost,
          port: observatoryPort,
          processToKill: processToKill
        );
138 139 140 141 142 143 144 145 146 147
      }
      if (tempDir != null) {
        Directory dirToDelete = tempDir;
        tempDir = null;
        dirToDelete.deleteSync(recursive: true);
      }
    }

    try {
      WebSocket socket = await info.socket;
148
      StreamChannel<dynamic> channel = new StreamChannel<dynamic>(socket.map(JSON.decode), socket);
149
      return channel.transformStream(
150 151
        new StreamTransformer<dynamic, dynamic>.fromHandlers(
          handleDone: (EventSink<dynamic> sink) {
152 153 154 155
            finalize();
            sink.close();
          }
        )
156 157
      ).transformSink(new StreamSinkTransformer<dynamic, String>.fromHandlers(
        handleData: (dynamic data, StreamSink<String> sink) {
158 159
          sink.add(JSON.encode(data));
        },
Ian Hickson's avatar
Ian Hickson committed
160
        handleDone: (EventSink<String> sink) {
161 162 163 164 165 166 167 168 169 170
          finalize();
          sink.close();
        }
      ));
    } catch(e) {
      finalize();
      rethrow;
    }
  }
}