chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NullableValueTypesTest.java

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.mojo.bindings;

import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.mojo.MojoTestRule;
import org.chromium.mojo.bindings.test.mojom.nullable_value_types.ExtensibleEnum;
import org.chromium.mojo.bindings.test.mojom.nullable_value_types.InterfaceV2;
import org.chromium.mojo.bindings.test.mojom.nullable_value_types.RegularEnum;
import org.chromium.mojo.bindings.test.mojom.nullable_value_types.StructWithEnums;
import org.chromium.mojo.bindings.test.mojom.nullable_value_types.StructWithNumerics;
import org.chromium.mojo.bindings.test.mojom.nullable_value_types.TypemappedEnum;
import org.chromium.mojo.system.Pair;
import org.chromium.mojo.system.impl.CoreImpl;

import java.util.HashMap;
import java.util.Map;

/**
 * Testing the Java bindings implementation for nullable value types, e.g. `int32?`. Note that the
 * Java tests are not exhaustive; it is assumed that successful interop with a C++ implementation
 * over JNI transitively implies correctness from the exhaustive C++ tests.
 */
@RunWith(BaseJUnit4ClassRunner.class)
@Batch(Batch.UNIT_TESTS)
public class NullableValueTypesTest {
    @Rule public MojoTestRule mTestRule = new MojoTestRule();

    private InterfaceV2 getRemote() {
        Pair<InterfaceV2.Proxy, InterfaceRequest<InterfaceV2>> result =
                InterfaceV2.MANAGER.getInterfaceRequest(CoreImpl.getInstance());
        NullableValueTypesTestUtilJni.get()
                .bindTestInterface(result.second.passHandle().releaseNativeHandle());
        return result.first;
    }

    /** Test cases when nullable value type fields are null. */
    @Test
    @SmallTest
    public void testNull() {
        final var remote = getRemote();

        {
            final @RegularEnum.EnumType Integer inEnumValue = null;
            final @TypemappedEnum.EnumType Integer inMappedEnumValue = null;

            remote.methodWithEnums(
                    inEnumValue,
                    inMappedEnumValue,
                    (@Nullable @RegularEnum.EnumType Integer outEnumValue,
                            @Nullable @TypemappedEnum.EnumType Integer outMappedEnumValue) -> {
                        Assert.assertNull(outEnumValue);
                        Assert.assertNull(outMappedEnumValue);
                        mTestRule.quitLoop();
                    });
            mTestRule.runLoopForever();
        }

        {
            final var in = new StructWithEnums();
            Assert.assertNull(in.enumValue);
            Assert.assertNull(in.mappedEnumValue);

            remote.methodWithStructWithEnums(
                    in,
                    (StructWithEnums out) -> {
                        Assert.assertNull(out.enumValue);
                        Assert.assertNull(out.mappedEnumValue);
                        mTestRule.quitLoop();
                    });
            mTestRule.runLoopForever();
        }

        {
            final Boolean inBoolValue = null;
            final Byte inU8Value = null;
            final Short inU16Value = null;
            final Integer inU32Value = null;
            final Long inU64Value = null;
            final Byte inI8Value = null;
            final Short inI16Value = null;
            final Integer inI32Value = null;
            final Long inI64Value = null;
            final Float inFloatValue = null;
            final Double inDoubleValue = null;

            remote.methodWithNumerics(
                    inBoolValue,
                    inU8Value,
                    inU16Value,
                    inU32Value,
                    inU64Value,
                    inI8Value,
                    inI16Value,
                    inI32Value,
                    inI64Value,
                    inFloatValue,
                    inDoubleValue,
                    (@Nullable Boolean outBoolValue,
                            @Nullable Byte outU8Value,
                            @Nullable Short outU16Value,
                            @Nullable Integer outU32Value,
                            @Nullable Long outU64Value,
                            @Nullable Byte outI8Value,
                            @Nullable Short outI16Value,
                            @Nullable Integer outI32Value,
                            @Nullable Long outI64Value,
                            @Nullable Float outFloatValue,
                            @Nullable Double outDoubleValue) -> {
                        Assert.assertNull(outBoolValue);
                        Assert.assertNull(outU8Value);
                        Assert.assertNull(outU16Value);
                        Assert.assertNull(outU32Value);
                        Assert.assertNull(outU64Value);
                        Assert.assertNull(outI8Value);
                        Assert.assertNull(outI16Value);
                        Assert.assertNull(outI32Value);
                        Assert.assertNull(outI64Value);
                        Assert.assertNull(outFloatValue);
                        Assert.assertNull(outDoubleValue);
                        mTestRule.quitLoop();
                    });
            mTestRule.runLoopForever();
        }

        {
            final var in = new StructWithNumerics();
            Assert.assertNull(in.boolValue);
            Assert.assertNull(in.u8Value);
            Assert.assertNull(in.u16Value);
            Assert.assertNull(in.u32Value);
            Assert.assertNull(in.u64Value);
            Assert.assertNull(in.i8Value);
            Assert.assertNull(in.i16Value);
            Assert.assertNull(in.i32Value);
            Assert.assertNull(in.i64Value);
            Assert.assertNull(in.floatValue);
            Assert.assertNull(in.doubleValue);

            remote.methodWithStructWithNumerics(
                    in,
                    (StructWithNumerics out) -> {
                        Assert.assertNull(out.boolValue);
                        Assert.assertNull(out.u8Value);
                        Assert.assertNull(out.u16Value);
                        Assert.assertNull(out.u32Value);
                        Assert.assertNull(out.u64Value);
                        Assert.assertNull(out.i8Value);
                        Assert.assertNull(out.i16Value);
                        Assert.assertNull(out.i32Value);
                        Assert.assertNull(out.i64Value);
                        Assert.assertNull(out.floatValue);
                        Assert.assertNull(out.doubleValue);
                        mTestRule.quitLoop();
                    });
            mTestRule.runLoopForever();
        }

        // Versioned interfaces intentionally untested... for now.
    }

    /** Test cases when nullable value type fields are set. */
    @Test
    @SmallTest
    public void testSet() {
        final var remote = getRemote();

        {
            final @RegularEnum.EnumType Integer inEnumValue = RegularEnum.THIS_VALUE;
            final @TypemappedEnum.EnumType Integer inMappedEnumValue =
                    TypemappedEnum.THIS_OTHER_VALUE;

            remote.methodWithEnums(
                    inEnumValue,
                    inMappedEnumValue,
                    (@Nullable @RegularEnum.EnumType Integer outEnumValue,
                            @Nullable @TypemappedEnum.EnumType Integer outMappedEnumValue) -> {
                        Assert.assertEquals(inEnumValue, outEnumValue);
                        Assert.assertEquals(inMappedEnumValue, outMappedEnumValue);
                        mTestRule.quitLoop();
                    });
            mTestRule.runLoopForever();
        }

        {
            final var in = new StructWithEnums();
            in.enumValue = RegularEnum.THAT_VALUE;
            in.mappedEnumValue = TypemappedEnum.THAT_OTHER_VALUE;

            remote.methodWithStructWithEnums(
                    in,
                    (StructWithEnums out) -> {
                        Assert.assertEquals(in.enumValue, out.enumValue);
                        Assert.assertEquals(in.mappedEnumValue, out.mappedEnumValue);
                        mTestRule.quitLoop();
                    });
            mTestRule.runLoopForever();
        }

        {
            final Boolean inBoolValue = false;
            final Byte inU8Value = 64;
            final Short inU16Value = 256;
            final Integer inU32Value = 1024;
            final Long inU64Value = 4096L;
            final Byte inI8Value = -64;
            final Short inI16Value = -256;
            final Integer inI32Value = -1024;
            final Long inI64Value = -4096L;
            final Float inFloatValue = 2.25f;
            final Double inDoubleValue = -9.0;

            remote.methodWithNumerics(
                    inBoolValue,
                    inU8Value,
                    inU16Value,
                    inU32Value,
                    inU64Value,
                    inI8Value,
                    inI16Value,
                    inI32Value,
                    inI64Value,
                    inFloatValue,
                    inDoubleValue,
                    (@Nullable Boolean outBoolValue,
                            @Nullable Byte outU8Value,
                            @Nullable Short outU16Value,
                            @Nullable Integer outU32Value,
                            @Nullable Long outU64Value,
                            @Nullable Byte outI8Value,
                            @Nullable Short outI16Value,
                            @Nullable Integer outI32Value,
                            @Nullable Long outI64Value,
                            @Nullable Float outFloatValue,
                            @Nullable Double outDoubleValue) -> {
                        Assert.assertEquals(inBoolValue, outBoolValue);
                        Assert.assertEquals(inU8Value, outU8Value);
                        Assert.assertEquals(inU16Value, outU16Value);
                        Assert.assertEquals(inU32Value, outU32Value);
                        Assert.assertEquals(inU64Value, outU64Value);
                        Assert.assertEquals(inI8Value, outI8Value);
                        Assert.assertEquals(inI16Value, outI16Value);
                        Assert.assertEquals(inI32Value, outI32Value);
                        Assert.assertEquals(inI64Value, outI64Value);
                        Assert.assertEquals(inFloatValue, outFloatValue);
                        Assert.assertEquals(inDoubleValue, outDoubleValue);
                        mTestRule.quitLoop();
                    });
            mTestRule.runLoopForever();
        }

        {
            final var in = new StructWithNumerics();
            in.boolValue = true;
            in.u8Value = 8;
            in.u16Value = 16;
            in.u32Value = 32;
            in.u64Value = 64L;
            in.i8Value = -8;
            in.i16Value = -16;
            in.i32Value = -32;
            in.i64Value = -64L;
            in.floatValue = -1.5f;
            in.doubleValue = 3.0;

            remote.methodWithStructWithNumerics(
                    in,
                    (StructWithNumerics out) -> {
                        Assert.assertEquals(in.boolValue, out.boolValue);
                        Assert.assertEquals(in.u8Value, out.u8Value);
                        Assert.assertEquals(in.u16Value, out.u16Value);
                        Assert.assertEquals(in.u32Value, out.u32Value);
                        Assert.assertEquals(in.u64Value, out.u64Value);
                        Assert.assertEquals(in.i8Value, out.i8Value);
                        Assert.assertEquals(in.i16Value, out.i16Value);
                        Assert.assertEquals(in.i32Value, out.i32Value);
                        Assert.assertEquals(in.i64Value, out.i64Value);
                        Assert.assertEquals(in.floatValue, out.floatValue);
                        Assert.assertEquals(in.doubleValue, out.doubleValue);
                        mTestRule.quitLoop();
                    });
            mTestRule.runLoopForever();
        }

        {
            // No specific pattern to the tests. We just want a mixture of values and nulls.
            Boolean[] boolValues = {true, true, true, null, false, false, false};
            Byte[] uByteValues = {null, (byte) 8, null};
            Short[] uShortValues = {null, (short) 1, (short) 6, null};
            Integer[] uIntValues = {null, 3, 2, null};
            Long[] uLongValues = {null, 6L, 4L, null};
            Byte[] byteValues = {null, (byte) 3, (byte) 2, null};
            Short[] shortValues = {null, (short) 3, (short) 2, null};
            Integer[] intValues = {null, 3, 2, null};
            Long[] longValues = {null, 3L, 2L, null};
            Float[] floatValues = {null, 4.0f, 2.0f, null};
            Double[] doubleValues = {null, 6.0, 4.0, null};
            @RegularEnum.EnumType Integer[] enumValues = {RegularEnum.THIS_VALUE, null};
            // Explicitly test defaulting behaviour.
            @ExtensibleEnum.EnumType
            Integer[] extensibleEnumValues = {null, 555, ExtensibleEnum.UNKNOWN};
            Map<Integer, Boolean> boolMap = new HashMap();
            boolMap.put(0, true);
            boolMap.put(1, null);
            boolMap.put(2, false);
            Map<Integer, Integer> intMap = new HashMap();
            intMap.put(0, 10);
            intMap.put(1, null);
            intMap.put(2, 12);

            remote.methodWithContainers(
                    boolValues,
                    uByteValues,
                    uShortValues,
                    uIntValues,
                    uLongValues,
                    byteValues,
                    shortValues,
                    intValues,
                    longValues,
                    floatValues,
                    doubleValues,
                    enumValues,
                    extensibleEnumValues,
                    boolMap,
                    intMap,
                    (Boolean[] outBoolValues,
                            Byte[] outUByteValues,
                            Short[] outUShortValues,
                            Integer[] outUIntValues,
                            Long[] outULongValues,
                            Byte[] outByteValues,
                            Short[] outShortValues,
                            Integer[] outIntValues,
                            Long[] outLongValues,
                            Float[] outFloatValues,
                            Double[] outDoubleValues,
                            @RegularEnum.EnumType Integer[] outEnumValues,
                            @ExtensibleEnum.EnumType Integer[] outExtensibleEnumValues,
                            Map<Integer, Boolean> outBoolMap,
                            Map<Integer, Integer> outIntMap) -> {
                        Assert.assertArrayEquals(boolValues, outBoolValues);
                        Assert.assertArrayEquals(uByteValues, outUByteValues);
                        Assert.assertArrayEquals(uShortValues, outUShortValues);
                        Assert.assertArrayEquals(uIntValues, outUIntValues);
                        Assert.assertArrayEquals(uLongValues, outULongValues);
                        Assert.assertArrayEquals(byteValues, outByteValues);
                        Assert.assertArrayEquals(shortValues, outShortValues);
                        Assert.assertArrayEquals(intValues, outIntValues);
                        Assert.assertArrayEquals(longValues, outLongValues);
                        Assert.assertArrayEquals(floatValues, outFloatValues);
                        Assert.assertArrayEquals(doubleValues, outDoubleValues);
                        Assert.assertArrayEquals(enumValues, outEnumValues);
                        Assert.assertArrayEquals(
                                new Integer[] {
                                    null, ExtensibleEnum.UNKNOWN, ExtensibleEnum.UNKNOWN
                                },
                                outExtensibleEnumValues);
                        Assert.assertEquals(boolMap, outBoolMap);
                        Assert.assertEquals(intMap, outIntMap);

                        mTestRule.quitLoop();
                    });
            mTestRule.runLoopForever();
        }

        {
            // Test bitfields that extend beyond the size of the element.
            final int testSize = 1000;
            Integer[] intValues = new Integer[testSize];
            for (int i = 0; i < intValues.length; ++i) {
                // Alternate between value and null.
                intValues[i] = i % 2 == 0 ? i : null;
            }

            // Also test the case where we pass in empty containers.
            remote.methodWithContainers(
                    new Boolean[0],
                    new Byte[0],
                    new Short[0],
                    intValues,
                    new Long[0],
                    new Byte[0],
                    new Short[0],
                    new Integer[0],
                    new Long[0],
                    new Float[0],
                    new Double[0],
                    new Integer[0],
                    new Integer[0],
                    new HashMap<Integer, Boolean>(),
                    new HashMap<Integer, Integer>(),
                    (Boolean[] outBoolValues,
                            Byte[] outUByteValues,
                            Short[] outUShortValues,
                            Integer[] outUIntValues,
                            Long[] outULongValues,
                            Byte[] outByteValues,
                            Short[] outShortValues,
                            Integer[] outIntValues,
                            Long[] outLongValues,
                            Float[] outFloatValues,
                            Double[] outDoubleValues,
                            @RegularEnum.EnumType Integer[] outEnumValues,
                            @ExtensibleEnum.EnumType Integer[] outExtensibleEnumValues,
                            Map<Integer, Boolean> outBoolMap,
                            Map<Integer, Integer> outIntMap) -> {
                        Assert.assertArrayEquals(intValues, outUIntValues);

                        mTestRule.quitLoop();
                    });
            mTestRule.runLoopForever();
        }

        {
            remote.methodToSendUnknownEnum(
                    (@ExtensibleEnum.EnumType Integer[] result) -> {
                        Assert.assertArrayEquals(
                                new Integer[] {ExtensibleEnum.UNKNOWN, null}, result);

                        mTestRule.quitLoop();
                    });

            mTestRule.runLoopForever();
        }

        // Versioned interfaces intentionally untested... for now.
    }

    // TODO(dcheng): Add a test with C++ remote and Java receiver.
}