From 074c27c911def4f40453f0381f332b2d7e52538f Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Sun, 8 Nov 2026 18:14:11 -0300 Subject: [PATCH 1/2] fix: convert JS callback results that the primitive return type can hold A callback result for a primitive return type only reached Java when the boxed conversion happened to produce exactly the declared wrapper, so a double callback returning a whole number, a float callback returning a negative or whole number, or a byte/short callback returning any plain number threw ClassCastException in the generated binding. A result now converts whenever the declared type can hold it: whole numbers in range for byte/short/int/long, a value within float range (or 0, NaN, +-Infinity) for float, any number for double, a one-character string for char, and a Boolean object by its value. Values the type cannot hold (non-whole or out-of-range numbers for integer types, float underflow or overflow, strings for numbers) still throw, as numericConversionTests expects. --- .../app/tests/testPrimitiveCallbackReturns.js | 38 ++++---- test-app/runtime/src/main/cpp/Runtime.cpp | 90 ++++++++++++------- .../src/main/java/com/tns/Runtime.java | 17 ++-- 3 files changed, 90 insertions(+), 55 deletions(-) diff --git a/test-app/app/src/main/assets/app/tests/testPrimitiveCallbackReturns.js b/test-app/app/src/main/assets/app/tests/testPrimitiveCallbackReturns.js index d206de91f..e7ceac7d1 100644 --- a/test-app/app/src/main/assets/app/tests/testPrimitiveCallbackReturns.js +++ b/test-app/app/src/main/assets/app/tests/testPrimitiveCallbackReturns.js @@ -49,34 +49,36 @@ describe("Primitive return values of JS callbacks", function () { "{}": function () { return {}; } }; - // Captured from the boxed conversion; columns are z b c s i j f d. + // Columns are z b c s i j f d. A value converts only when the return type + // can hold it (whole numbers in range for integer types, float range for + // float, a one-character string for char); otherwise the binding throws. var expected = { - "0": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:0 ok:0 ex:ClassCastException ex:ClassCastException", - "1": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1 ok:1 ex:ClassCastException ex:ClassCastException", - "-1": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-1 ok:-1 ex:ClassCastException ex:ClassCastException", - "-0": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:0 ok:0 ex:ClassCastException ex:ClassCastException", - "2^31": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:2147483648 ex:ClassCastException ex:ClassCastException", - "-2^31-1": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-2147483649 ex:ClassCastException ex:ClassCastException", - "2^40": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1099511627776 ex:ClassCastException ex:ClassCastException", - "2^60": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1152921504606846976 ex:ClassCastException ex:ClassCastException", + "0": "ex:ClassCastException ok:0 ex:ClassCastException ok:0 ok:0 ok:0 ok:0.0 ok:0.0", + "1": "ex:ClassCastException ok:1 ex:ClassCastException ok:1 ok:1 ok:1 ok:1.0 ok:1.0", + "-1": "ex:ClassCastException ok:-1 ex:ClassCastException ok:-1 ok:-1 ok:-1 ok:-1.0 ok:-1.0", + "-0": "ex:ClassCastException ok:0 ex:ClassCastException ok:0 ok:0 ok:0 ok:-0.0 ok:-0.0", + "2^31": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:2147483648 ok:2.1474836E9 ok:2.147483648E9", + "-2^31-1": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-2147483649 ok:-2.1474836E9 ok:-2.147483649E9", + "2^40": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1099511627776 ok:1.0995116E12 ok:1.099511627776E12", + "2^60": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1152921504606846976 ok:1.1529215E18 ok:1.152921504606847E18", "1.5": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1.5 ok:1.5", - "-1.5": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-1.5", + "-1.5": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-1.5 ok:-1.5", "1e-40": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1.0E-40", "3.5e38": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:3.5E38", - "NaN": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:NaN", - "Infinity": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:Infinity", - "-Infinity": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-Infinity", - "-2.5": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-2.5", + "NaN": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:NaN ok:NaN", + "Infinity": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:Infinity ok:Infinity", + "-Infinity": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-Infinity ok:-Infinity", + "-2.5": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-2.5 ok:-2.5", "true": "ok:true ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", "false": "ok:false ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", - "'1'": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", - "'a'": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", + "'1'": "ex:ClassCastException ex:ClassCastException ok:49 ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", + "'a'": "ex:ClassCastException ex:ClassCastException ok:97 ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", "null": "ok:false ok:0 ok:0 ok:0 ok:0 ok:0 ok:0.0 ok:0.0", "undefined": "ok:false ok:0 ok:0 ok:0 ok:0 ok:0 ok:0.0 ok:0.0", - "Number(5)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:5 ok:5 ex:ClassCastException ex:ClassCastException", + "Number(5)": "ex:ClassCastException ok:5 ex:ClassCastException ok:5 ok:5 ok:5 ok:5.0 ok:5.0", "Number(2.5)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:2.5 ok:2.5", "Boolean(true)": "ok:true ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", - "Boolean(false)": "ok:true ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", + "Boolean(false)": "ok:false ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", "long(5)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:5 ex:ClassCastException ex:ClassCastException", "byte(2)": "ex:ClassCastException ok:2 ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", "short(3)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:3 ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", diff --git a/test-app/runtime/src/main/cpp/Runtime.cpp b/test-app/runtime/src/main/cpp/Runtime.cpp index 7dca92805..dd45ea3d4 100644 --- a/test-app/runtime/src/main/cpp/Runtime.cpp +++ b/test-app/runtime/src/main/cpp/Runtime.cpp @@ -56,6 +56,7 @@ #include #include +#include #include #include @@ -503,10 +504,12 @@ jobject Runtime::RunScript(JNIEnv* _env, jobject obj, jstring scriptFile) { } /* - * The primitive-returning cases of JsArgToArrayConverter::ConvertArg that box - * into exactly the declared wrapper type, which is all the generated binding's - * cast accepts. Everything else keeps the boxed conversion, including the - * values it turns into a ClassCastException or a defaulted null. + * A JS callback result for a primitive Java return type, converted only when + * the declared type can hold it: whole numbers in range for the integer types, + * a value within float range (or 0, NaN, +-Infinity) for float, any number for + * double, a one-character string for char. Anything else keeps the boxed + * conversion, whose result the generated binding rejects with a + * ClassCastException. */ static bool TryGetPrimitiveReturnBits(Isolate* isolate, const Local& value, Type type, @@ -516,49 +519,76 @@ static bool TryGetPrimitiveReturnBits(Isolate* isolate, } if (type == Type::Boolean) { - if (!value->IsBoolean() && !value->IsBooleanObject()) { - return false; + if (value->IsBoolean()) { + bits = value->IsTrue() ? 1 : 0; + return true; + } + if (value->IsBooleanObject()) { + bits = value.As()->ValueOf() ? 1 : 0; + return true; } - // ToBoolean, as the boxed path does: a Boolean object is always true. - bits = value->BooleanValue(isolate) ? 1 : 0; - return true; - } - - // Number objects are left to the boxed path: reading one calls valueOf, - // which must not run twice if this path then declines. - if (!value->IsNumber()) { return false; } - if (type == Type::Int && value->IsInt32()) { - bits = value.As()->Value(); + if (type == Type::Char) { + Local str; + if (value->IsString()) { + str = value.As(); + } else if (value->IsStringObject()) { + str = value.As()->ValueOf(); + } else { + return false; + } + if (str->Length() != 1) { + return false; + } + uint16_t unit; + str->WriteV2(isolate, 0, 1, &unit); + bits = unit; return true; } - double d = value.As()->Value(); - // Beyond int64 the boxed path's double -> int64 cast is undefined. - constexpr double kTwoPow63 = 9223372036854775808.0; - if (std::isfinite(d) && std::fabs(d) >= kTwoPow63) { + double d; + if (value->IsNumber()) { + d = value.As()->Value(); + } else if (value->IsNumberObject()) { + d = value.As()->ValueOf(); + } else { return false; } - bool isWholeNumber = - std::isfinite(d) && d == static_cast(static_cast(d)); + bool isWhole = std::isfinite(d) && std::trunc(d) == d; switch (type) { + case Type::Byte: + if (isWhole && d >= INT8_MIN && d <= INT8_MAX) { + bits = static_cast(d); + return true; + } + return false; + case Type::Short: + if (isWhole && d >= INT16_MIN && d <= INT16_MAX) { + bits = static_cast(d); + return true; + } + return false; case Type::Int: - if (isWholeNumber && INT_MIN <= d && d <= INT_MAX) { + if (isWhole && d >= INT32_MIN && d <= INT32_MAX) { bits = static_cast(d); return true; } return false; case Type::Long: - if (isWholeNumber) { + // 2^63 itself is not representable; the boxed path's handling of it is + // undefined, so it is left there. + if (isWhole && std::fabs(d) < 9223372036854775808.0) { bits = static_cast(d); return true; } return false; - case Type::Float: - if (!isWholeNumber && FLT_MIN <= d && d <= FLT_MAX) { + case Type::Float: { + double magnitude = std::fabs(d); + if (!std::isfinite(d) || d == 0 || + (magnitude >= FLT_MIN && magnitude <= FLT_MAX)) { float f = static_cast(d); int32_t raw; memcpy(&raw, &f, sizeof(raw)); @@ -566,12 +596,10 @@ static bool TryGetPrimitiveReturnBits(Isolate* isolate, return true; } return false; + } case Type::Double: - if (!isWholeNumber) { - memcpy(&bits, &d, sizeof(bits)); - return true; - } - return false; + memcpy(&bits, &d, sizeof(bits)); + return true; default: return false; } diff --git a/test-app/runtime/src/main/java/com/tns/Runtime.java b/test-app/runtime/src/main/java/com/tns/Runtime.java index 5e1d5e302..3aa1709e4 100644 --- a/test-app/runtime/src/main/java/com/tns/Runtime.java +++ b/test-app/runtime/src/main/java/com/tns/Runtime.java @@ -1537,14 +1537,13 @@ private static Object discardCall(String methodName, Class retType, String re } /* - * For return types the native side can hand back as raw bits instead of a - * boxed object: it writes {bits, 1} here and returns null, or leaves the - * array untouched and returns the boxed value as before. + * For primitive return types the native side hands the converted value back + * as raw bits: it writes {bits, 1} here and returns null, or leaves the + * array untouched and returns a boxed value from the generic conversion. */ private static long[] newPrimitiveResult(int returnType) { - boolean raw = returnType == TypeIDs.Boolean || returnType == TypeIDs.Int || returnType == TypeIDs.Long - || returnType == TypeIDs.Float || returnType == TypeIDs.Double; - return raw ? new long[2] : null; + boolean primitive = returnType >= TypeIDs.Boolean && returnType <= TypeIDs.Double; + return primitive ? new long[2] : null; } private static Object takePrimitiveResult(Object nativeResult, int returnType, long[] primitiveResult) { @@ -1555,6 +1554,12 @@ private static Object takePrimitiveResult(Object nativeResult, int returnType, l long bits = primitiveResult[0]; if (returnType == TypeIDs.Boolean) { return bits != 0; + } else if (returnType == TypeIDs.Char) { + return (char) bits; + } else if (returnType == TypeIDs.Byte) { + return (byte) bits; + } else if (returnType == TypeIDs.Short) { + return (short) bits; } else if (returnType == TypeIDs.Int) { return (int) bits; } else if (returnType == TypeIDs.Long) { From bf0f0b6b0649675bd13602b066b6252198056da4 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Sun, 8 Nov 2026 18:43:07 -0300 Subject: [PATCH 2/2] test: format float/double callback results the same on every Android version Float.toString and Double.toString changed their shortest-digits algorithm across Android releases, so the conversion spec's expected strings depended on the emulator image (2.1474836E9 vs 2.14748365E9). Values are now printed through BigDecimal rounded to 9 (float) or 17 (double) significant digits. --- .../app/tests/testPrimitiveCallbackReturns.js | 30 +++++++++---------- .../com/tns/tests/PrimitiveReturnTest.java | 16 ++++++++-- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/test-app/app/src/main/assets/app/tests/testPrimitiveCallbackReturns.js b/test-app/app/src/main/assets/app/tests/testPrimitiveCallbackReturns.js index e7ceac7d1..5fec0b5d4 100644 --- a/test-app/app/src/main/assets/app/tests/testPrimitiveCallbackReturns.js +++ b/test-app/app/src/main/assets/app/tests/testPrimitiveCallbackReturns.js @@ -53,18 +53,18 @@ describe("Primitive return values of JS callbacks", function () { // can hold it (whole numbers in range for integer types, float range for // float, a one-character string for char); otherwise the binding throws. var expected = { - "0": "ex:ClassCastException ok:0 ex:ClassCastException ok:0 ok:0 ok:0 ok:0.0 ok:0.0", - "1": "ex:ClassCastException ok:1 ex:ClassCastException ok:1 ok:1 ok:1 ok:1.0 ok:1.0", - "-1": "ex:ClassCastException ok:-1 ex:ClassCastException ok:-1 ok:-1 ok:-1 ok:-1.0 ok:-1.0", - "-0": "ex:ClassCastException ok:0 ex:ClassCastException ok:0 ok:0 ok:0 ok:-0.0 ok:-0.0", - "2^31": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:2147483648 ok:2.1474836E9 ok:2.147483648E9", - "-2^31-1": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-2147483649 ok:-2.1474836E9 ok:-2.147483649E9", - "2^40": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1099511627776 ok:1.0995116E12 ok:1.099511627776E12", - "2^60": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1152921504606846976 ok:1.1529215E18 ok:1.152921504606847E18", + "0": "ex:ClassCastException ok:0 ex:ClassCastException ok:0 ok:0 ok:0 ok:0 ok:0", + "1": "ex:ClassCastException ok:1 ex:ClassCastException ok:1 ok:1 ok:1 ok:1 ok:1", + "-1": "ex:ClassCastException ok:-1 ex:ClassCastException ok:-1 ok:-1 ok:-1 ok:-1 ok:-1", + "-0": "ex:ClassCastException ok:0 ex:ClassCastException ok:0 ok:0 ok:0 ok:0 ok:0", + "2^31": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:2147483648 ok:2.14748365E+9 ok:2147483648", + "-2^31-1": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-2147483649 ok:-2.14748365E+9 ok:-2147483649", + "2^40": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1099511627776 ok:1.09951163E+12 ok:1099511627776", + "2^60": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1152921504606846976 ok:1.1529215E+18 ok:1.152921504606847E+18", "1.5": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1.5 ok:1.5", "-1.5": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-1.5 ok:-1.5", - "1e-40": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1.0E-40", - "3.5e38": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:3.5E38", + "1e-40": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:9.9999999999999993E-41", + "3.5e38": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:3.5E+38", "NaN": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:NaN ok:NaN", "Infinity": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:Infinity ok:Infinity", "-Infinity": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:-Infinity ok:-Infinity", @@ -73,9 +73,9 @@ describe("Primitive return values of JS callbacks", function () { "false": "ok:false ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", "'1'": "ex:ClassCastException ex:ClassCastException ok:49 ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", "'a'": "ex:ClassCastException ex:ClassCastException ok:97 ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", - "null": "ok:false ok:0 ok:0 ok:0 ok:0 ok:0 ok:0.0 ok:0.0", - "undefined": "ok:false ok:0 ok:0 ok:0 ok:0 ok:0 ok:0.0 ok:0.0", - "Number(5)": "ex:ClassCastException ok:5 ex:ClassCastException ok:5 ok:5 ok:5 ok:5.0 ok:5.0", + "null": "ok:false ok:0 ok:0 ok:0 ok:0 ok:0 ok:0 ok:0", + "undefined": "ok:false ok:0 ok:0 ok:0 ok:0 ok:0 ok:0 ok:0", + "Number(5)": "ex:ClassCastException ok:5 ex:ClassCastException ok:5 ok:5 ok:5 ok:5 ok:5", "Number(2.5)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:2.5 ok:2.5", "Boolean(true)": "ok:true ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", "Boolean(false)": "ok:false ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", @@ -84,8 +84,8 @@ describe("Primitive return values of JS callbacks", function () { "short(3)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:3 ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", "char('a')": "ex:ClassCastException ex:ClassCastException ok:97 ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException", "float(1.5)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:1.5 ex:ClassCastException", - "double(2)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:2.0", - "{}": "ok:false ok:0 ok:0 ok:0 ok:0 ok:0 ok:0.0 ok:0.0" + "double(2)": "ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ex:ClassCastException ok:2", + "{}": "ok:false ok:0 ok:0 ok:0 ok:0 ok:0 ok:0 ok:0" }; var kinds = ["z", "b", "c", "s", "i", "j", "f", "d"]; diff --git a/test-app/app/src/main/java/com/tns/tests/PrimitiveReturnTest.java b/test-app/app/src/main/java/com/tns/tests/PrimitiveReturnTest.java index 284084e14..96aac8ce7 100644 --- a/test-app/app/src/main/java/com/tns/tests/PrimitiveReturnTest.java +++ b/test-app/app/src/main/java/com/tns/tests/PrimitiveReturnTest.java @@ -1,5 +1,8 @@ package com.tns.tests; +import java.math.BigDecimal; +import java.math.MathContext; + /** * Calls each primitive-returning method of a JS implementation and reports either the * returned value or the class of what was thrown, so a spec can pin how JS return values @@ -24,6 +27,15 @@ public interface Returns { double d(); } + // Float.toString/Double.toString changed their shortest-digits algorithm + // across Android versions; BigDecimal rounding prints the same everywhere. + private static String format(double value, int digits) { + if (Double.isNaN(value) || Double.isInfinite(value)) { + return Double.toString(value); + } + return new BigDecimal(value).round(new MathContext(digits)).stripTrailingZeros().toString(); + } + public static String call(Returns r, String kind) { try { switch (kind) { @@ -40,9 +52,9 @@ public static String call(Returns r, String kind) { case "j": return "ok:" + r.j(); case "f": - return "ok:" + r.f(); + return "ok:" + format(r.f(), 9); case "d": - return "ok:" + r.d(); + return "ok:" + format(r.d(), 17); default: throw new IllegalArgumentException(kind); }