Migrate to null safety (#23)

diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
index 07e2dbf..f647b2a 100644
--- a/.github/workflows/test-package.yml
+++ b/.github/workflows/test-package.yml
@@ -59,27 +59,3 @@
       - name: Run VM tests
         run: dart test --platform vm
         if: always() && steps.install.outcome == 'success'
-
-  # Run tests on a legacy SDK on a matrix consisting of two dimensions:
-  # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
-  # 2. release: 2.1.0
-  test_legacy_sdk:
-    needs: analyze
-    runs-on: ${{ matrix.os }}
-    strategy:
-      fail-fast: false
-      matrix:
-        # Add macos-latest and/or windows-latest if relevant for this package.
-        os: [ubuntu-latest]
-        sdk: [2.1.0]
-    steps:
-      - uses: actions/checkout@v2
-      - uses: dart-lang/setup-dart@v0.3
-        with:
-          sdk: ${{ matrix.sdk }}
-      - id: install
-        name: Install dependencies
-        run: pub get
-      - name: Run VM tests
-        run: pub run test --platform vm
-        if: always() && steps.install.outcome == 'success'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 766efa6..97bf110 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.0.0-nullsafety.0
+
+* Migrate to null safety.
+* Require Dart >=2.12.
+
 ## 1.0.6
 
 * Require Dart >=2.1
diff --git a/lib/test_process.dart b/lib/test_process.dart
index 301ce9d..a42be3b 100644
--- a/lib/test_process.dart
+++ b/lib/test_process.dart
@@ -28,15 +28,13 @@
 
   /// A [StreamQueue] that emits each line of stdout from the process.
   ///
-  /// A copy of the underlying stream can be retreived using [stdoutStream].
-  StreamQueue<String> get stdout => _stdout;
-  StreamQueue<String> _stdout;
+  /// A copy of the underlying stream can be retrieved using [stdoutStream].
+  late final StreamQueue<String> stdout = StreamQueue(stdoutStream());
 
   /// A [StreamQueue] that emits each line of stderr from the process.
   ///
-  /// A copy of the underlying stream can be retreived using [stderrStream].
-  StreamQueue<String> get stderr => _stderr;
-  StreamQueue<String> _stderr;
+  /// A copy of the underlying stream can be retrieved using [stderrStream].
+  late final StreamQueue<String> stderr = StreamQueue(stderrStream());
 
   /// A splitter that can emit new copies of [stdout].
   final StreamSplitter<String> _stdoutSplitter;
@@ -48,7 +46,7 @@
   IOSink get stdin => _process.stdin;
 
   /// A buffer of mixed stdout and stderr lines.
-  final _log = <String>[];
+  final List<String> _log = <String>[];
 
   /// Whether [_log] has been passed to [printOnFailure] yet.
   bool _loggedOutput = false;
@@ -62,8 +60,9 @@
 
   /// Completes to [_process]'s exit code if it's exited, otherwise completes to
   /// `null` immediately.
-  Future<int> get _exitCodeOrNull async =>
-      await exitCode.timeout(Duration.zero, onTimeout: () => null);
+  Future<int?> get _exitCodeOrNull async => await exitCode
+      .then<int?>((value) => value)
+      .timeout(Duration.zero, onTimeout: () => null);
 
   /// Starts a process.
   ///
@@ -79,12 +78,12 @@
   /// temporarily to help when debugging test failures.
   static Future<TestProcess> start(
       String executable, Iterable<String> arguments,
-      {String workingDirectory,
-      Map<String, String> environment,
+      {String? workingDirectory,
+      Map<String, String>? environment,
       bool includeParentEnvironment = true,
       bool runInShell = false,
-      String description,
-      Encoding encoding,
+      String? description,
+      Encoding encoding = utf8,
       bool forwardStdio = false}) async {
     var process = await Process.start(executable, arguments.toList(),
         workingDirectory: workingDirectory,
@@ -99,7 +98,6 @@
       description = "$humanExecutable ${arguments.join(" ")}";
     }
 
-    encoding ??= utf8;
     return TestProcess(process, description,
         encoding: encoding, forwardStdio: forwardStdio);
   }
@@ -112,7 +110,7 @@
   /// This is protected, which means it should only be called by subclasses.
   @protected
   TestProcess(Process process, this.description,
-      {Encoding encoding, bool forwardStdio = false})
+      {Encoding encoding= utf8, bool forwardStdio = false})
       : _process = process,
         _stdoutSplitter = StreamSplitter(process.stdout
             .transform(encoding.decoder)
@@ -124,9 +122,6 @@
     expect(_process.exitCode.then((_) => _logOutput()), completes,
         reason: 'Process `$description` never exited.');
 
-    _stdout = StreamQueue(stdoutStream());
-    _stderr = StreamQueue(stderrStream());
-
     // Listen eagerly so that the lines are interleaved properly between the two
     // streams.
     //
diff --git a/pubspec.yaml b/pubspec.yaml
index 19e80f7..772b97d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,18 +1,18 @@
 name: test_process
-version: 1.0.6-dev
+version: 2.0.0
 
 description: A package for testing subprocesses.
 homepage: https://github.com/dart-lang/test_process
 
 environment:
-  sdk: '>=2.1.0 <3.0.0'
+  sdk: ">=2.12.0-0 <3.0.0"
 
 dependencies:
-  async: ^2.0.0
-  meta: ^1.0.0
-  path: ^1.0.0
-  test: ^1.0.0
+  async: ^2.5.0
+  meta: ^1.3.0
+  path: ^1.8.0
+  test: ^1.16.0
 
 dev_dependencies:
-  pedantic: ^1.0.0
-  test_descriptor: ^1.0.0
+  pedantic: ^1.10.0
+  test_descriptor: ^2.0.0