Migration: Write unit tests for NullabilityGraph.

Change-Id: I7d3ab1bde98ff28203dc73d61ecdc77e8fed25f3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105682
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/nnbd_migration/lib/src/nullability_node.dart b/pkg/nnbd_migration/lib/src/nullability_node.dart
index 9779caf..b18fd8e 100644
--- a/pkg/nnbd_migration/lib/src/nullability_node.dart
+++ b/pkg/nnbd_migration/lib/src/nullability_node.dart
@@ -277,7 +277,7 @@
   /// [joinNullabilities] callback.  TODO(paulberry): this should become
   /// unnecessary once constraint solving is performed directly using
   /// [NullabilityNode] objects.
-  factory NullabilityNode.forLUB(NullabilityNode a, NullabilityNode b) =
+  factory NullabilityNode.forLUB(NullabilityNode left, NullabilityNode right) =
       NullabilityNodeForLUB._;
 
   /// Creates a [NullabilityNode] representing the nullability of a type
diff --git a/pkg/nnbd_migration/test/migration_visitor_test.dart b/pkg/nnbd_migration/test/migration_visitor_test.dart
index c347349..f34c48c 100644
--- a/pkg/nnbd_migration/test/migration_visitor_test.dart
+++ b/pkg/nnbd_migration/test/migration_visitor_test.dart
@@ -92,12 +92,6 @@
     return _variables.decoratedExpressionType(findNode.expression(text));
   }
 
-  test_always() async {
-    await analyze('');
-
-    expect(NullabilityNode.always.isNullable, isTrue);
-  }
-
   test_assert_demonstrates_non_null_intent() async {
     await analyze('''
 void f(int i) {
diff --git a/pkg/nnbd_migration/test/nullability_node_test.dart b/pkg/nnbd_migration/test/nullability_node_test.dart
new file mode 100644
index 0000000..57f07e6
--- /dev/null
+++ b/pkg/nnbd_migration/test/nullability_node_test.dart
@@ -0,0 +1,306 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:nnbd_migration/src/nullability_node.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(NullabilityNodeTest);
+  });
+}
+
+@reflectiveTest
+class NullabilityNodeTest {
+  final graph = NullabilityGraph();
+
+  NullabilityNode get always => NullabilityNode.always;
+
+  NullabilityNode get never => NullabilityNode.never;
+
+  void connect(NullabilityNode source, NullabilityNode destination,
+      {bool hard = false, List<NullabilityNode> guards = const []}) {
+    graph.connect(source, destination, hard: hard, guards: guards);
+  }
+
+  NullabilityNode lub(NullabilityNode left, NullabilityNode right) {
+    return NullabilityNode.forLUB(left, right);
+  }
+
+  NullabilityNode newNode(int offset) =>
+      NullabilityNode.forTypeAnnotation(offset);
+
+  NullabilityNode subst(NullabilityNode inner, NullabilityNode outer) {
+    return NullabilityNode.forSubstitution(inner, outer);
+  }
+
+  test_always_and_never_state() {
+    graph.propagate();
+    expect(always.isNullable, isTrue);
+    expect(never.isNullable, isFalse);
+  }
+
+  test_always_and_never_unaffected_by_hard_edges() {
+    connect(always, never, hard: true);
+    graph.propagate();
+    expect(always.isNullable, isTrue);
+    expect(never.isNullable, isFalse);
+  }
+
+  test_always_and_never_unaffected_by_soft_edges() {
+    connect(always, never);
+    graph.propagate();
+    expect(always.isNullable, isTrue);
+    expect(never.isNullable, isFalse);
+  }
+
+  test_always_destination() {
+    // always -> 1 -(hard)-> always
+    var n1 = newNode(1);
+    connect(always, n1);
+    connect(n1, always, hard: true);
+    graph.propagate();
+    // Upstream propagation of non-nullability ignores edges terminating at
+    // `always`, so n1 should be nullable.
+    expect(n1.isNullable, true);
+  }
+
+  test_never_source() {
+    // never -> 1
+    var n1 = newNode(1);
+    connect(never, n1);
+    graph.propagate();
+    // Downstream propagation of nullability ignores edges originating at
+    // `never`, so n1 should be non-nullable.
+    expect(n1.isNullable, false);
+  }
+
+  test_propagation_downstream_guarded_multiple_guards_all_satisfied() {
+    // always -> 1
+    // always -> 2
+    // always -> 3
+    // 1 -(2,3)-> 4
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    var n4 = newNode(3);
+    connect(always, n1);
+    connect(always, n2);
+    connect(always, n3);
+    connect(n1, n4, guards: [n2, n3]);
+    graph.propagate();
+    expect(n4.isNullable, true);
+  }
+
+  test_propagation_downstream_guarded_multiple_guards_not_all_satisfied() {
+    // always -> 1
+    // always -> 2
+    // 1 -(2,3)-> 4
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    var n4 = newNode(3);
+    connect(always, n1);
+    connect(always, n2);
+    connect(n1, n4, guards: [n2, n3]);
+    graph.propagate();
+    expect(n4.isNullable, false);
+  }
+
+  test_propagation_downstream_guarded_satisfy_guard_first() {
+    // always -> 1
+    // always -> 2
+    // 2 -(1)-> 3
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(always, n1);
+    connect(always, n2);
+    connect(n2, n3, guards: [n1]);
+    graph.propagate();
+    expect(n3.isNullable, true);
+  }
+
+  test_propagation_downstream_guarded_satisfy_source_first() {
+    // always -> 1
+    // always -> 2
+    // 1 -(2)-> 3
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(always, n1);
+    connect(always, n2);
+    connect(n1, n3, guards: [n2]);
+    graph.propagate();
+    expect(n3.isNullable, true);
+  }
+
+  test_propagation_downstream_guarded_unsatisfied_guard() {
+    // always -> 1
+    // 1 -(2)-> 3
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(always, n1);
+    connect(n1, n3, guards: [n2]);
+    graph.propagate();
+    expect(n3.isNullable, false);
+  }
+
+  test_propagation_downstream_guarded_unsatisfied_source() {
+    // always -> 1
+    // 2 -(1)-> 3
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(always, n1);
+    connect(n2, n3, guards: [n1]);
+    graph.propagate();
+    expect(n3.isNullable, false);
+  }
+
+  test_propagation_downstream_through_lub_both() {
+    // always -> 1
+    // always -> 2
+    // LUB(1, 2) -> 3
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(always, n1);
+    connect(always, n2);
+    connect(lub(n1, n2), n3);
+    graph.propagate();
+    expect(n3.isNullable, true);
+  }
+
+  test_propagation_downstream_through_lub_cascaded() {
+    // always -> 1
+    // LUB(LUB(1, 2), 3) -> 4
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    var n4 = newNode(3);
+    connect(always, n1);
+    connect(lub(lub(n1, n2), n3), n4);
+    graph.propagate();
+    expect(n4.isNullable, true);
+  }
+
+  test_propagation_downstream_through_lub_left() {
+    // always -> 1
+    // LUB(1, 2) -> 3
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(always, n1);
+    connect(lub(n1, n2), n3);
+    graph.propagate();
+    expect(n3.isNullable, true);
+  }
+
+  test_propagation_downstream_through_lub_neither() {
+    // LUB(1, 2) -> 3
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(lub(n1, n2), n3);
+    graph.propagate();
+    expect(n3.isNullable, false);
+  }
+
+  test_propagation_downstream_through_lub_right() {
+    // always -> 2
+    // LUB(1, 2) -> 3
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(always, n2);
+    connect(lub(n1, n2), n3);
+    graph.propagate();
+    expect(n3.isNullable, true);
+  }
+
+  test_propagation_downstream_through_substitution_both() {
+    // always -> 1
+    // always -> 2
+    // subst(1, 2) -> 3
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(always, n1);
+    connect(always, n2);
+    connect(subst(n1, n2), n3);
+    graph.propagate();
+    expect(n3.isNullable, true);
+  }
+
+  test_propagation_downstream_through_substitution_cascaded() {
+    // always -> 1
+    // LUB(LUB(1, 2), 3) -> 4
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    var n4 = newNode(3);
+    connect(always, n1);
+    connect(subst(subst(n1, n2), n3), n4);
+    graph.propagate();
+    expect(n4.isNullable, true);
+  }
+
+  test_propagation_downstream_through_substitution_inner() {
+    // always -> 1
+    // LUB(1, 2) -> 3
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(always, n1);
+    connect(subst(n1, n2), n3);
+    graph.propagate();
+    expect(n3.isNullable, true);
+  }
+
+  test_propagation_downstream_through_substitution_neither() {
+    // LUB(1, 2) -> 3
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(subst(n1, n2), n3);
+    graph.propagate();
+    expect(n3.isNullable, false);
+  }
+
+  test_propagation_downstream_through_substitution_outer() {
+    // always -> 2
+    // LUB(1, 2) -> 3
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(always, n2);
+    connect(subst(n1, n2), n3);
+    graph.propagate();
+    expect(n3.isNullable, true);
+  }
+
+  test_propagation_simple() {
+    // always -(soft)-> 1 -(soft)-> 2 -(hard) -> 3 -(hard)-> never
+    var n1 = newNode(1);
+    var n2 = newNode(2);
+    var n3 = newNode(3);
+    connect(always, n1);
+    connect(n1, n2);
+    connect(n2, n3, hard: true);
+    connect(n3, never, hard: true);
+    graph.propagate();
+    expect(n1.isNullable, true);
+    expect(n2.isNullable, false);
+    expect(n3.isNullable, false);
+  }
+
+  test_unconstrainted_node_non_nullable() {
+    var n1 = newNode(1);
+    graph.propagate();
+    expect(n1.isNullable, false);
+  }
+}
diff --git a/pkg/nnbd_migration/test/test_all.dart b/pkg/nnbd_migration/test/test_all.dart
index 8f19fb9..af35c52 100644
--- a/pkg/nnbd_migration/test/test_all.dart
+++ b/pkg/nnbd_migration/test/test_all.dart
@@ -6,10 +6,12 @@
 
 import 'api_test.dart' as api_test;
 import 'migration_visitor_test.dart' as migration_visitor_test;
+import 'nullability_node_test.dart' as nullability_node_test;
 
 main() {
   defineReflectiveSuite(() {
-    migration_visitor_test.main();
     api_test.main();
+    migration_visitor_test.main();
+    nullability_node_test.main();
   });
 }