| // Copyright (c) 2022, 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. |
| |
| library late_field_checks.common; |
| |
| import 'package:expect/expect.dart'; |
| |
| // True for DDC and VM. |
| final bool isAlwaysChecked = !const bool.fromEnvironment('dart2js'); |
| |
| /// Interface for test object classes that have a field. All the test objects |
| /// implement this interface. |
| abstract class Field { |
| abstract int field; |
| } |
| |
| /// Tag interface for a class where the `late` field is also `final`. |
| class Final {} |
| |
| /// Tag interface for a class where late field is checked. |
| class Checked {} |
| |
| /// Tag interface for a class where the late field checks are not performed for |
| /// the late field. |
| class Trusted {} |
| |
| /// The library 'name' e.g. 'LibraryCheck' |
| /// Nullable so it can be cleared to ensure the library's main() sets it. |
| String? libraryName; |
| |
| String describeClass(Field object) { |
| final name = '${libraryName!}.${object.runtimeType}'; |
| final interfaces = [ |
| if (object is Final) 'Final', |
| 'Field', |
| if (object is Checked) 'Checked', |
| if (object is Trusted) 'Trusted' |
| ]; |
| |
| return '$name implements ${interfaces.join(", ")}'; |
| } |
| |
| void test(Field Function() factory) { |
| // Consistency checks. |
| final o1 = factory(); |
| // Get the class description to ensure library name is set. |
| final description = describeClass(o1); |
| print('-- $description'); |
| Expect.isTrue(o1 is Checked || o1 is Trusted, |
| 'Test class must implement one of Checked or Trusted: $description'); |
| Expect.isFalse(o1 is Checked && o1 is Trusted, |
| 'Test class must not implement both of Checked or Trusted: $description'); |
| |
| // Setter then Getter should not throw. |
| final o2 = factory(); |
| o2.field = 100; |
| Expect.equals(100, o2.field); |
| |
| testGetterBeforeSetter(factory()); |
| testDoubleSetter(factory()); |
| } |
| |
| void testGetterBeforeSetter(Field object) { |
| final isChecked = isAlwaysChecked || object is Checked; |
| |
| bool threw = false; |
| try { |
| _sink = object.field; |
| } catch (e, s) { |
| threw = true; |
| } |
| |
| if (threw == isChecked) return; |
| _fail(object, threw, 'getter before setter'); |
| } |
| |
| void testDoubleSetter(Field object) { |
| final isChecked = isAlwaysChecked || object is Checked; |
| |
| object.field = 101; |
| bool threw = false; |
| try { |
| object.field = 102; |
| } catch (e, s) { |
| threw = true; |
| } |
| |
| if (object is Final) { |
| if (threw == isChecked) return; |
| _fail(object, threw, 'double setter'); |
| } |
| |
| Expect.equals(102, object.field); |
| } |
| |
| int _sink = 0; |
| |
| void _fail(Field object, bool threw, String testDescription) { |
| final classDescription = describeClass(object); |
| if (threw) { |
| Expect.fail('Should not throw for $testDescription: $classDescription'); |
| } else { |
| Expect.fail('Failed to throw for $testDescription: $classDescription'); |
| } |
| } |