diff --git a/packages/flutter/lib/src/services/message_codecs.dart b/packages/flutter/lib/src/services/message_codecs.dart index 8a658a44a0..cac9267d1c 100644 --- a/packages/flutter/lib/src/services/message_codecs.dart +++ b/packages/flutter/lib/src/services/message_codecs.dart @@ -338,6 +338,14 @@ class StandardMessageCodec implements MessageCodec { buffer.putUint8(_valueNull); } else if (value is bool) { buffer.putUint8(value ? _valueTrue : _valueFalse); + } else if (value is double) { // Double precedes int because in JS everything is a double. + // Therefore in JS, both `is int` and `is double` always + // return `true`. If we check int first, we'll end up treating + // all numbers as ints and attempt the int32/int64 conversion, + // which is wrong. This precedence rule is irrelevant when + // decoding because we use tags to detect the type of value. + buffer.putUint8(_valueFloat64); + buffer.putFloat64(value); } else if (value is int) { if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) { buffer.putUint8(_valueInt32); @@ -346,9 +354,6 @@ class StandardMessageCodec implements MessageCodec { buffer.putUint8(_valueInt64); buffer.putInt64(value); } - } else if (value is double) { - buffer.putUint8(_valueFloat64); - buffer.putFloat64(value); } else if (value is String) { buffer.putUint8(_valueString); final List bytes = utf8.encoder.convert(value); diff --git a/packages/flutter/test/services/message_codecs_test.dart b/packages/flutter/test/services/message_codecs_test.dart index c69d0d6896..b1aa34c36c 100644 --- a/packages/flutter/test/services/message_codecs_test.dart +++ b/packages/flutter/test/services/message_codecs_test.dart @@ -2,28 +2,31 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -@TestOn('!chrome') +// This files contains message codec tests that are supported both on the Web +// and in the VM. For VM-only tests see message_codecs_vm_test.dart. + import 'dart:typed_data'; import 'package:flutter/services.dart'; import '../flutter_test_alternative.dart'; +import 'message_codecs_testing.dart'; void main() { group('Binary codec', () { const MessageCodec binary = BinaryCodec(); test('should encode and decode simple messages', () { - _checkEncodeDecode(binary, null); - _checkEncodeDecode(binary, ByteData(0)); - _checkEncodeDecode(binary, ByteData(4)..setInt32(0, -7)); + checkEncodeDecode(binary, null); + checkEncodeDecode(binary, ByteData(0)); + checkEncodeDecode(binary, ByteData(4)..setInt32(0, -7)); }); }); group('String codec', () { const MessageCodec string = StringCodec(); test('should encode and decode simple messages', () { - _checkEncodeDecode(string, null); - _checkEncodeDecode(string, ''); - _checkEncodeDecode(string, 'hello'); - _checkEncodeDecode(string, 'special chars >\u263A\u{1F602}<'); + checkEncodeDecode(string, null); + checkEncodeDecode(string, ''); + checkEncodeDecode(string, 'hello'); + checkEncodeDecode(string, 'special chars >\u263A\u{1F602}<'); }); test('ByteData with offset', () { const MessageCodec string = StringCodec(); @@ -42,19 +45,17 @@ void main() { group('JSON message codec', () { const MessageCodec json = JSONMessageCodec(); test('should encode and decode simple messages', () { - _checkEncodeDecode(json, null); - _checkEncodeDecode(json, true); - _checkEncodeDecode(json, false); - _checkEncodeDecode(json, 7); - _checkEncodeDecode(json, -7); - _checkEncodeDecode(json, 98742923489); - _checkEncodeDecode(json, -98742923489); - _checkEncodeDecode(json, 9223372036854775807); - _checkEncodeDecode(json, -9223372036854775807); - _checkEncodeDecode(json, 3.14); - _checkEncodeDecode(json, ''); - _checkEncodeDecode(json, 'hello'); - _checkEncodeDecode(json, 'special chars >\u263A\u{1F602}<'); + checkEncodeDecode(json, null); + checkEncodeDecode(json, true); + checkEncodeDecode(json, false); + checkEncodeDecode(json, 7); + checkEncodeDecode(json, -7); + checkEncodeDecode(json, 98742923489); + checkEncodeDecode(json, -98742923489); + checkEncodeDecode(json, 3.14); + checkEncodeDecode(json, ''); + checkEncodeDecode(json, 'hello'); + checkEncodeDecode(json, 'special chars >\u263A\u{1F602}<'); }); test('should encode and decode composite message', () { final List message = [ @@ -63,7 +64,6 @@ void main() { false, -707, -7000000007, - -7000000000000000007, -3.14, '', 'hello', @@ -71,91 +71,47 @@ void main() { {'a': 'nested', 'b': {}}, 'world', ]; - _checkEncodeDecode(json, message); + checkEncodeDecode(json, message); }); }); group('Standard message codec', () { const MessageCodec standard = StandardMessageCodec(); - test('should encode integers correctly at boundary cases', () { - _checkEncoding( - standard, - -0x7fffffff - 1, - [3, 0x00, 0x00, 0x00, 0x80], - ); - _checkEncoding( - standard, - -0x7fffffff - 2, - [4, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff], - ); - _checkEncoding( - standard, - 0x7fffffff, - [3, 0xff, 0xff, 0xff, 0x7f], - ); - _checkEncoding( - standard, - 0x7fffffff + 1, - [4, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00], - ); - _checkEncoding( - standard, - -0x7fffffffffffffff - 1, - [4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80], - ); - _checkEncoding( - standard, - -0x7fffffffffffffff - 2, - [4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f], - ); - _checkEncoding( - standard, - 0x7fffffffffffffff, - [4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f], - ); - _checkEncoding( - standard, - 0x7fffffffffffffff + 1, - [4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80], - ); - }); test('should encode sizes correctly at boundary cases', () { - _checkEncoding( + checkEncoding( standard, Uint8List(253), [8, 253, ...List.filled(253, 0)], ); - _checkEncoding( + checkEncoding( standard, Uint8List(254), [8, 254, 254, 0, ...List.filled(254, 0)], ); - _checkEncoding( + checkEncoding( standard, Uint8List(0xffff), [8, 254, 0xff, 0xff, ...List.filled(0xffff, 0)], ); - _checkEncoding( + checkEncoding( standard, Uint8List(0xffff + 1), [8, 255, 0, 0, 1, 0, ...List.filled(0xffff + 1, 0)], ); }); test('should encode and decode simple messages', () { - _checkEncodeDecode(standard, null); - _checkEncodeDecode(standard, true); - _checkEncodeDecode(standard, false); - _checkEncodeDecode(standard, 7); - _checkEncodeDecode(standard, -7); - _checkEncodeDecode(standard, 98742923489); - _checkEncodeDecode(standard, -98742923489); - _checkEncodeDecode(standard, 9223372036854775807); - _checkEncodeDecode(standard, -9223372036854775807); - _checkEncodeDecode(standard, 3.14); - _checkEncodeDecode(standard, double.infinity); - _checkEncodeDecode(standard, double.nan); - _checkEncodeDecode(standard, ''); - _checkEncodeDecode(standard, 'hello'); - _checkEncodeDecode(standard, 'special chars >\u263A\u{1F602}<'); + checkEncodeDecode(standard, null); + checkEncodeDecode(standard, true); + checkEncodeDecode(standard, false); + checkEncodeDecode(standard, 7); + checkEncodeDecode(standard, -7); + checkEncodeDecode(standard, 98742923489); + checkEncodeDecode(standard, -98742923489); + checkEncodeDecode(standard, 3.14); + checkEncodeDecode(standard, double.infinity); + checkEncodeDecode(standard, double.nan); + checkEncodeDecode(standard, ''); + checkEncodeDecode(standard, 'hello'); + checkEncodeDecode(standard, 'special chars >\u263A\u{1F602}<'); }); test('should encode and decode composite message', () { final List message = [ @@ -164,15 +120,12 @@ void main() { false, -707, -7000000007, - -7000000000000000007, -3.14, '', 'hello', Uint8List.fromList([0xBA, 0x5E, 0xBA, 0x11]), Int32List.fromList([-0x7fffffff - 1, 0, 0x7fffffff]), null, // ensures the offset of the following list is unaligned. - Int64List.fromList( - [-0x7fffffffffffffff - 1, 0, 0x7fffffffffffffff]), null, // ensures the offset of the following list is unaligned. Float64List.fromList([ double.negativeInfinity, @@ -189,10 +142,10 @@ void main() { {'a': 'nested', null: {}}, 'world', ]; - _checkEncodeDecode(standard, message); + checkEncodeDecode(standard, message); }); test('should align doubles to 8 bytes', () { - _checkEncoding( + checkEncoding( standard, 1.0, [6, 0, 0, 0, 0, 0, 0, 0, @@ -201,76 +154,3 @@ void main() { }); }); } - -void _checkEncoding(MessageCodec codec, T message, List expectedBytes) { - final ByteData encoded = codec.encodeMessage(message); - expect( - encoded.buffer.asUint8List(0, encoded.lengthInBytes), - orderedEquals(expectedBytes), - ); -} - -void _checkEncodeDecode(MessageCodec codec, T message) { - final ByteData encoded = codec.encodeMessage(message); - final T decoded = codec.decodeMessage(encoded); - if (message == null) { - expect(encoded, isNull); - expect(decoded, isNull); - } else { - expect(_deepEquals(message, decoded), isTrue); - final ByteData encodedAgain = codec.encodeMessage(decoded); - expect( - encodedAgain.buffer.asUint8List(), - orderedEquals(encoded.buffer.asUint8List()), - ); - } -} - -bool _deepEquals(dynamic valueA, dynamic valueB) { - if (valueA is TypedData) - return valueB is TypedData && _deepEqualsTypedData(valueA, valueB); - if (valueA is List) - return valueB is List && _deepEqualsList(valueA, valueB); - if (valueA is Map) - return valueB is Map && _deepEqualsMap(valueA, valueB); - if (valueA is double && valueA.isNaN) - return valueB is double && valueB.isNaN; - return valueA == valueB; -} - -bool _deepEqualsTypedData(TypedData valueA, TypedData valueB) { - if (valueA is ByteData) { - return valueB is ByteData - && _deepEqualsList( - valueA.buffer.asUint8List(), valueB.buffer.asUint8List()); - } - if (valueA is Uint8List) - return valueB is Uint8List && _deepEqualsList(valueA, valueB); - if (valueA is Int32List) - return valueB is Int32List && _deepEqualsList(valueA, valueB); - if (valueA is Int64List) - return valueB is Int64List && _deepEqualsList(valueA, valueB); - if (valueA is Float64List) - return valueB is Float64List && _deepEqualsList(valueA, valueB); - throw 'Unexpected typed data: $valueA'; -} - -bool _deepEqualsList(List valueA, List valueB) { - if (valueA.length != valueB.length) - return false; - for (int i = 0; i < valueA.length; i++) { - if (!_deepEquals(valueA[i], valueB[i])) - return false; - } - return true; -} - -bool _deepEqualsMap(Map valueA, Map valueB) { - if (valueA.length != valueB.length) - return false; - for (final dynamic key in valueA.keys) { - if (!valueB.containsKey(key) || !_deepEquals(valueA[key], valueB[key])) - return false; - } - return true; -} diff --git a/packages/flutter/test/services/message_codecs_testing.dart b/packages/flutter/test/services/message_codecs_testing.dart new file mode 100644 index 0000000000..adf3ffc711 --- /dev/null +++ b/packages/flutter/test/services/message_codecs_testing.dart @@ -0,0 +1,81 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; +import '../flutter_test_alternative.dart'; + +void checkEncoding(MessageCodec codec, T message, List expectedBytes) { + final ByteData encoded = codec.encodeMessage(message); + expect( + encoded.buffer.asUint8List(0, encoded.lengthInBytes), + orderedEquals(expectedBytes), + ); +} + +void checkEncodeDecode(MessageCodec codec, T message) { + final ByteData encoded = codec.encodeMessage(message); + final T decoded = codec.decodeMessage(encoded); + if (message == null) { + expect(encoded, isNull); + expect(decoded, isNull); + } else { + expect(deepEquals(message, decoded), isTrue); + final ByteData encodedAgain = codec.encodeMessage(decoded); + expect( + encodedAgain.buffer.asUint8List(), + orderedEquals(encoded.buffer.asUint8List()), + ); + } +} + +bool deepEquals(dynamic valueA, dynamic valueB) { + if (valueA is TypedData) + return valueB is TypedData && deepEqualsTypedData(valueA, valueB); + if (valueA is List) + return valueB is List && deepEqualsList(valueA, valueB); + if (valueA is Map) + return valueB is Map && deepEqualsMap(valueA, valueB); + if (valueA is double && valueA.isNaN) + return valueB is double && valueB.isNaN; + return valueA == valueB; +} + +bool deepEqualsTypedData(TypedData valueA, TypedData valueB) { + if (valueA is ByteData) { + return valueB is ByteData + && deepEqualsList( + valueA.buffer.asUint8List(), valueB.buffer.asUint8List()); + } + if (valueA is Uint8List) + return valueB is Uint8List && deepEqualsList(valueA, valueB); + if (valueA is Int32List) + return valueB is Int32List && deepEqualsList(valueA, valueB); + if (valueA is Int64List) + return valueB is Int64List && deepEqualsList(valueA, valueB); + if (valueA is Float64List) + return valueB is Float64List && deepEqualsList(valueA, valueB); + throw 'Unexpected typed data: $valueA'; +} + +bool deepEqualsList(List valueA, List valueB) { + if (valueA.length != valueB.length) + return false; + for (int i = 0; i < valueA.length; i++) { + if (!deepEquals(valueA[i], valueB[i])) + return false; + } + return true; +} + +bool deepEqualsMap(Map valueA, Map valueB) { + if (valueA.length != valueB.length) + return false; + for (final dynamic key in valueA.keys) { + if (!valueB.containsKey(key) || !deepEquals(valueA[key], valueB[key])) + return false; + } + return true; +} diff --git a/packages/flutter/test/services/message_codecs_vm_test.dart b/packages/flutter/test/services/message_codecs_vm_test.dart new file mode 100644 index 0000000000..6c20a71386 --- /dev/null +++ b/packages/flutter/test/services/message_codecs_vm_test.dart @@ -0,0 +1,84 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains tests that are only supported by the Dart VM. For +// example, on the Web there's no way to express large integers. +@TestOn('!chrome') + +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; +import '../flutter_test_alternative.dart'; +import 'message_codecs_testing.dart'; + +void main() { + group('JSON message codec', () { + const MessageCodec json = JSONMessageCodec(); + test('should encode and decode big numbers', () { + checkEncodeDecode(json, 9223372036854775807); + checkEncodeDecode(json, -9223372036854775807); + }); + test('should encode and decode list with a big number', () { + final List message = [-7000000000000000007]; + checkEncodeDecode(json, message); + }); + }); + group('Standard message codec', () { + const MessageCodec standard = StandardMessageCodec(); + test('should encode integers correctly at boundary cases', () { + checkEncoding( + standard, + -0x7fffffff - 1, + [3, 0x00, 0x00, 0x00, 0x80], + ); + checkEncoding( + standard, + -0x7fffffff - 2, + [4, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff], + ); + checkEncoding( + standard, + 0x7fffffff, + [3, 0xff, 0xff, 0xff, 0x7f], + ); + checkEncoding( + standard, + 0x7fffffff + 1, + [4, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00], + ); + checkEncoding( + standard, + -0x7fffffffffffffff - 1, + [4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80], + ); + checkEncoding( + standard, + -0x7fffffffffffffff - 2, + [4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f], + ); + checkEncoding( + standard, + 0x7fffffffffffffff, + [4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f], + ); + checkEncoding( + standard, + 0x7fffffffffffffff + 1, + [4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80], + ); + }); + test('should encode and decode big numbers', () { + checkEncodeDecode(standard, 9223372036854775807); + checkEncodeDecode(standard, -9223372036854775807); + }); + test('should encode and decode a list containing big numbers', () { + final List message = [ + -7000000000000000007, + Int64List.fromList( + [-0x7fffffffffffffff - 1, 0, 0x7fffffffffffffff]), + ]; + checkEncodeDecode(standard, message); + }); + }); +}