为什么进程在 dart:io 的单元测试期间不退出?

Why doesn't the process exit during this unittest with dart:io?

以下代码测试客户端发送的服务器上接收到的数据。

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:unittest/unittest.dart';

main() {
  ServerSocket ss;

  setUp(() => ServerSocket.bind('localhost', 9876).then((e) => ss = e));

  tearDown(() => ss.close());

  test('test 1', () {
    final line = new Completer<String>();
    ss.listen((s) => s.map(UTF8.decode).listen((s) => line.complete(s)));
    Socket.connect(ss.address, ss.port)
        .then((s) {
          s.write('test');
          return s.close();
        })
        .then((_) => line.future)
        .then(expectAsync((s) => expect(s, equals('test'))));
  });
}

本次测试显示:

unittest-suite-wait-for-done
PASS: test 1

All 1 tests passed.
unittest-suite-success

但是,进程不会停止。

  1. 为什么在 ss.close()s.close() 的情况下进程仍然 运行?
  2. 如何找到使进程保持活动状态的原因? Observatory 是否提供调试的东西?

编辑:请参阅下面亚历克斯的回答。 Socket.drain() 再见!

根据observatory io page, the socket created by the server was still open for writing. I've updated the code below to call destroy() on both the server and client socket. destroy() closes a Socket两个方向。

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:unittest/unittest.dart';

main() {
  ServerSocket ss;

  setUp(() => ServerSocket.bind('localhost', 9876).then((e) => ss = e));

  tearDown(() => ss.close());

  test('test 1', () {
    final line = new Completer<String>();
    ss.listen((s) {
      s.map(UTF8.decode).listen((t) {
        line.complete(t);
        s.destroy();
      });
    });
    Socket.connect(ss.address, ss.port)
        .then((s) {
          s.write('test');
          return s.flush().then((_) {
            s.destroy();
            return line.future;
          });
        })
        .then(expectAsync((s) => expect(s, equals('test'))));
  });
}

根据 drain()close() 应该优先于 destroy()

这是工作版本:

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:unittest/unittest.dart';

main() {
  ServerSocket ss;

  setUp(() => ServerSocket.bind('localhost', 9876).then((e) => ss = e));

  tearDown(() => ss.close());

  test('test 1', () {
    final line = new Completer<String>();
    ss.listen((s) => UTF8.decodeStream(s)
                         .then((value) => line.complete(value))
                         .then((_)=> s.close()));
    Socket.connect(ss.address, ss.port)
        .then((s) {
          s.write('test');
          return Future.wait([s.drain(), s.close()]);
        })
        .then((_) => line.future)
        .then(expectAsync((s) => expect(s, equals('test'))));
  });
}

对此的一些评论:

  • 在服务器端,我使用 UTF8.decodeStream(s).then(...) 而不是 s.map(UTF8.decode).listen(...),因为该值可以分成几个块。在这种情况下,套接字将关闭得太早。
  • 在客户端,我使用 Future.wait([s.drain(), s.close()]) 而不是链接 Futures。如果我使用 s.drain().then((_) => s.close()) 进程阻塞,因为服务器正在等待数据流结束(由客户端的 s.close() 触发)以关闭套接字,从而触发 s.drain() 的完成在客户端。