diff --git a/lib/ui/lerp.dart b/lib/ui/lerp.dart index 91a04c2cbf94a..c4841e2ae37b6 100644 --- a/lib/ui/lerp.dart +++ b/lib/ui/lerp.dart @@ -6,14 +6,20 @@ part of dart.ui; -/// Linearly interpolate between two numbers. -// TODO(cbracken): Consider making a and b non-nullable. -// https://github.com/flutter/flutter/issues/64617 +/// Linearly interpolate between two numbers, `a` and `b`, by an extrapolation +/// factor `t`. +/// +/// When `a` and `b` are equal or both NaN, `a` is returned. Otherwise, if +/// `a`, `b`, and `t` are required to be finite or null, and the result of `a + +/// (b - a) * t` is returned, where nulls are defaulted to 0.0. double? lerpDouble(num? a, num? b, double t) { - if (a == null && b == null) - return null; + if (a == b || (a?.isNaN == true) && (b?.isNaN == true)) + return a?.toDouble(); a ??= 0.0; b ??= 0.0; + assert(a.isFinite, 'Cannot interpolate between finite and non-finite values'); + assert(b.isFinite, 'Cannot interpolate between finite and non-finite values'); + assert(t.isFinite, 't must be finite when interpolating between values'); return a + (b - a) * t as double; } diff --git a/testing/dart/canvas_test.dart b/testing/dart/canvas_test.dart index 8640cc86bf63d..39a21a540f678 100644 --- a/testing/dart/canvas_test.dart +++ b/testing/dart/canvas_test.dart @@ -12,6 +12,8 @@ import 'package:image/image.dart' as dart_image; import 'package:path/path.dart' as path; import 'package:test/test.dart'; +import 'test_util.dart'; + typedef CanvasCallback = void Function(Canvas canvas); Future createImage(int width, int height) { @@ -38,35 +40,6 @@ void testCanvas(CanvasCallback callback) { } catch (error) { } // ignore: empty_catches } -void expectAssertion(Function callback) { - bool assertsEnabled = false; - assert(() { - assertsEnabled = true; - return true; - }()); - if (assertsEnabled) { - bool threw = false; - try { - callback(); - } catch (e) { - expect(e is AssertionError, true); - threw = true; - } - expect(threw, true); - } -} - -void expectArgumentError(Function callback) { - bool threw = false; - try { - callback(); - } catch (e) { - expect(e is ArgumentError, true); - threw = true; - } - expect(threw, true); -} - void testNoCrashes() { test('canvas APIs should not crash', () async { final Paint paint = Paint(); diff --git a/testing/dart/lerp_test.dart b/testing/dart/lerp_test.dart index 26ad7b89b4031..4395e974747f7 100644 --- a/testing/dart/lerp_test.dart +++ b/testing/dart/lerp_test.dart @@ -7,6 +7,8 @@ import 'dart:ui'; import 'package:test/test.dart'; +import 'test_util.dart'; + void main() { test('lerpDouble should return null if and only if both inputs are null', () { expect(lerpDouble(null, null, 1.0), isNull); @@ -65,47 +67,53 @@ void main() { expect(lerpDouble(10, 0, 5), -40); }); - test('lerpDouble should return NaN if any input is NaN', () { - expect(lerpDouble(0.0, 10.0, double.nan), isNaN); - expect(lerpDouble(0.0, double.infinity, double.nan), isNaN); - expect(lerpDouble(0.0, double.nan, 5.0), isNaN); - expect(lerpDouble(0.0, double.nan, double.infinity), isNaN); - expect(lerpDouble(0.0, double.nan, double.nan), isNaN); - expect(lerpDouble(double.infinity, 10.0, double.nan), isNaN); - expect(lerpDouble(double.infinity, double.infinity, double.nan), isNaN); - expect(lerpDouble(double.infinity, double.nan, 5.0), isNaN); - expect(lerpDouble(double.infinity, double.nan, double.infinity), isNaN); - expect(lerpDouble(double.infinity, double.nan, double.nan), isNaN); - expect(lerpDouble(double.nan, 10.0, 5.0), isNaN); - expect(lerpDouble(double.nan, 10.0, double.infinity), isNaN); - expect(lerpDouble(double.nan, 10.0, double.nan), isNaN); - expect(lerpDouble(double.nan, double.infinity, 5.0), isNaN); - expect(lerpDouble(double.nan, double.infinity, double.infinity), isNaN); - expect(lerpDouble(double.nan, double.infinity, double.nan), isNaN); + test('lerpDouble should return input value in all cases if begin/end are equal', () { + expect(lerpDouble(10.0, 10.0, 5.0), 10.0); + expect(lerpDouble(10.0, 10.0, double.nan), 10.0); + expect(lerpDouble(10.0, 10.0, double.infinity), 10.0); + expect(lerpDouble(10.0, 10.0, -double.infinity), 10.0); + + expect(lerpDouble(10, 10, 5.0), 10.0); + expect(lerpDouble(10, 10, double.nan), 10.0); + expect(lerpDouble(10, 10, double.infinity), 10.0); + expect(lerpDouble(10, 10, -double.infinity), 10.0); + expect(lerpDouble(double.nan, double.nan, 5.0), isNaN); - expect(lerpDouble(double.nan, double.nan, double.infinity), isNaN); expect(lerpDouble(double.nan, double.nan, double.nan), isNaN); + expect(lerpDouble(double.nan, double.nan, double.infinity), isNaN); + expect(lerpDouble(double.nan, double.nan, -double.infinity), isNaN); + + expect(lerpDouble(double.infinity, double.infinity, 5.0), double.infinity); + expect(lerpDouble(double.infinity, double.infinity, double.nan), double.infinity); + expect(lerpDouble(double.infinity, double.infinity, double.infinity), double.infinity); + expect(lerpDouble(double.infinity, double.infinity, -double.infinity), double.infinity); + + expect(lerpDouble(-double.infinity, -double.infinity, 5.0), -double.infinity); + expect(lerpDouble(-double.infinity, -double.infinity, double.nan), -double.infinity); + expect(lerpDouble(-double.infinity, -double.infinity, double.infinity), -double.infinity); + expect(lerpDouble(-double.infinity, -double.infinity, -double.infinity), -double.infinity); }); - test('lerpDouble returns NaN if interpolation results in Infinity - Infinity', () { - expect(lerpDouble(double.infinity, 10.0, 5.0), isNaN); - expect(lerpDouble(double.infinity, 10.0, double.infinity), isNaN); - expect(lerpDouble(-double.infinity, 10.0, 5.0), isNaN); - expect(lerpDouble(-double.infinity, 10.0, double.infinity), isNaN); + test('lerpDouble should throw AssertionError if interpolation value is NaN and a != b', () { + expectAssertion(() => lerpDouble(0.0, 10.0, double.nan)); }); - test('lerpDouble returns +/- infinity if interpolating towards an infinity', () { - expect(lerpDouble(double.infinity, 10.0, -5.0)?.isInfinite, isTrue); - expect(lerpDouble(double.infinity, 10.0, -double.infinity)?.isInfinite, isTrue); - expect(lerpDouble(-double.infinity, 10.0, -5.0)?.isInfinite, isTrue); - expect(lerpDouble(-double.infinity, 10.0, -double.infinity)?.isInfinite, isTrue); - expect(lerpDouble(0.0, double.infinity, 5.0)?.isInfinite, isTrue); - expect(lerpDouble(0.0, double.infinity, -5.0)?.isInfinite, isTrue); - expect(lerpDouble(0.0, 10.0, double.infinity)?.isInfinite, isTrue); - expect(lerpDouble(0.0, double.infinity, double.infinity)?.isInfinite, isTrue); + test('lerpDouble should throw AssertionError if interpolation value is +/- infinity and a != b', () { + expectAssertion(() => lerpDouble(0.0, 10.0, double.infinity)); + expectAssertion(() => lerpDouble(0.0, 10.0, -double.infinity)); }); - test('lerpDouble returns NaN if start/end and interpolation value are infinity', () { - expect(lerpDouble(double.infinity, double.infinity, double.infinity), isNaN); + test('lerpDouble should throw AssertionError if either start or end are NaN', () { + expectAssertion(() => lerpDouble(double.nan, 10.0, 5.0)); + expectAssertion(() => lerpDouble(0.0, double.nan, 5.0)); + }); + + test('lerpDouble should throw AssertionError if either start or end are +/- infinity', () { + expectAssertion(() => lerpDouble(double.infinity, 10.0, 5.0)); + expectAssertion(() => lerpDouble(-double.infinity, 10.0, 5.0)); + expectAssertion(() => lerpDouble(0.0, double.infinity, 5.0)); + expectAssertion(() => lerpDouble(0.0, -double.infinity, 5.0)); }); } + +final Matcher throwsAssertionError = throwsA(const TypeMatcher()); diff --git a/testing/dart/test_util.dart b/testing/dart/test_util.dart new file mode 100644 index 0000000000000..e990b138e7f3e --- /dev/null +++ b/testing/dart/test_util.dart @@ -0,0 +1,44 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.6 + +import 'package:test/test.dart'; + +/// Asserts that `callback` throws an [AssertionError]. +/// +/// When running in a VM in which assertions are enabled, asserts that the +/// specified callback throws an [AssertionError]. When asserts are not +/// enabled, such as when running using a release-mode VM with default +/// settings, this acts as a no-op. +void expectAssertion(Function callback) { + bool assertsEnabled = false; + assert(() { + assertsEnabled = true; + return true; + }()); + if (assertsEnabled) { + bool threw = false; + try { + callback(); + } catch (e) { + expect(e is AssertionError, true); + threw = true; + } + expect(threw, true); + } +} + +/// Asserts that `callback` throws an [ArgumentError]. +void expectArgumentError(Function callback) { + bool threw = false; + try { + callback(); + } catch (e) { + expect(e is ArgumentError, true); + threw = true; + } + expect(threw, true); +} + diff --git a/testing/run_tests.py b/testing/run_tests.py index a5c7d833fb279..3330cb985db15 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -395,7 +395,7 @@ def RunDartTests(build_dir, filter, verbose_dart_snapshot): # Now that we have the Sky packages at the hardcoded location, run `pub get`. RunEngineExecutable(build_dir, os.path.join('dart-sdk', 'bin', 'pub'), None, flags=['get'], cwd=dart_tests_dir) - dart_tests = glob.glob('%s/*.dart' % dart_tests_dir) + dart_tests = glob.glob('%s/*_test.dart' % dart_tests_dir) for dart_test_file in dart_tests: if filter is not None and os.path.basename(dart_test_file) not in filter: