loader.dart 4.85 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// 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';

import 'package:path/path.dart' as p;
import 'package:sky_tools/src/test/json_socket.dart';
import 'package:sky_tools/src/test/remote_test.dart';
import 'package:stack_trace/stack_trace.dart';
import 'package:test/src/backend/metadata.dart';
import 'package:test/src/backend/test_platform.dart';
import 'package:test/src/runner/configuration.dart';
import 'package:test/src/runner/load_exception.dart';
import 'package:test/src/runner/runner_suite.dart';
18
import 'package:test/src/runner/hack_load_vm_file_hook.dart' as hack;
19 20 21 22 23
import 'package:test/src/runner/vm/environment.dart';
import 'package:test/src/util/io.dart';
import 'package:test/src/util/remote_exception.dart';

void installHook() {
24
  hack.loadVMFileHook = _loadVMFile;
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
}

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

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

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

Future<_ServerInfo> _createServer() 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);
}

Future<Process> _startProcess(String path, { String packageRoot }) {
  assert(_kSkyShell != null); // Please provide the path to the shell in the SKY_SHELL environment variable.
  return Process.start(_kSkyShell, [
    '--enable-checked-mode',
    '--non-interactive',
    '--package-root=$packageRoot',
    path,
  ]);
}

Future<RunnerSuite> _loadVMFile(String path,
                                Metadata metadata,
                                Configuration config) async {
  String encodedMetadata = Uri.encodeComponent(JSON.encode(
      metadata.serialize()));
  _ServerInfo info = await _createServer();
  Directory tempDir = await Directory.systemTemp.createTemp(
      'dart_test_listener');
  File listenerFile = new File('${tempDir.path}/listener.dart');
  await listenerFile.create();
  await listenerFile.writeAsString('''
import 'dart:convert';

import 'package:test/src/backend/metadata.dart';
import 'package:sky_tools/src/test/remote_listener.dart';

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

void main() {
  String server = Uri.decodeComponent('${Uri.encodeComponent(info.url)}');
  Metadata metadata = new Metadata.deserialize(
      JSON.decode(Uri.decodeComponent('$encodedMetadata')));
  RemoteListener.start(server, metadata, () => test.main);
}
''');

85 86
  Completer completer = new Completer();

87 88 89
  Process process = await _startProcess(listenerFile.path,
      packageRoot: p.absolute(config.packageRoot));

90 91 92 93 94 95
  Future cleanupTempDirectory() async {
    if (tempDir == null)
      return;
    Directory dirToDelete = tempDir;
    tempDir = null;
    await dirToDelete.delete(recursive: true);
96 97
  }

98 99 100 101 102
  process.exitCode.then((int exitCode) async {
    info.server.close(force: true);
    await cleanupTempDirectory();
    if (!completer.isCompleted) {
      String error = await process.stderr.transform(UTF8.decoder).first;
103
      completer.completeError(
104
        new LoadException(path, error), new Trace.current());
105 106 107
    }
  });

108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
  Future<JSONSocket> socket = (() async {
    return new JSONSocket(await info.socket);
  })();

  socket.then((JSONSocket socket) async {
    await cleanupTempDirectory();

    StreamSubscription subscription;
    subscription = socket.stream.listen((response) {
      if (response["type"] == "print") {
        print(response["line"]);
      } else if (response["type"] == "loadException") {
        process.kill();
        completer.completeError(
            new LoadException(path, response["message"]),
            new Trace.current());
      } else if (response["type"] == "error") {
        process.kill();
        AsyncError asyncError = RemoteException.deserialize(response["error"]);
        completer.completeError(
            new LoadException(path, asyncError.error),
            asyncError.stackTrace);
      } else {
        assert(response["type"] == "success");
        subscription.cancel();
        completer.complete(response["tests"]);
      }
    });
  });

138 139 140 141 142 143 144 145 146
  return new RunnerSuite(const VMEnvironment(),
      (await completer.future).map((test) {
        var testMetadata = new Metadata.deserialize(test['metadata']);
        return new RemoteTest(test['name'], testMetadata, socket, test['index']);
      }),
      metadata: metadata,
      path: path,
      platform: TestPlatform.vm,
      os: currentOS,
147
      onClose: process.kill);
148
}