blob: 5ec9a414910ca53e4ca004f6c558f7d6aac6a1e4 [file] [log] [blame]
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.wear.protolayout.expression.pipeline;
import static androidx.wear.protolayout.expression.DynamicBuilders.DynamicString.constant;
import static com.google.common.truth.Truth.assertThat;
import static java.lang.Integer.MAX_VALUE;
import android.icu.util.ULocale;
import androidx.annotation.NonNull;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicDuration;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat.FloatFormatter;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32.IntFormatter;
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString;
import androidx.wear.protolayout.expression.proto.FixedProto.FixedBool;
import androidx.wear.protolayout.expression.proto.FixedProto.FixedFloat;
import androidx.wear.protolayout.expression.proto.FixedProto.FixedInt32;
import androidx.wear.protolayout.expression.proto.FixedProto.FixedString;
import androidx.wear.protolayout.expression.proto.StateEntryProto.StateEntryValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.ParameterizedRobolectricTestRunner;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
@RunWith(ParameterizedRobolectricTestRunner.class)
public class DynamicTypeEvaluatorTest {
@ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
public static ImmutableList<Object[]> params() {
DynamicTypeEvaluatorTest.TestCase<?>[] testCases = {
test(constant("hello"), "hello"),
test(DynamicString.fromState("state_hello_world"), "hello_world"),
test(DynamicInt32.constant(5).format(), "5"),
test(DynamicInt32.constant(10), 10),
test(DynamicInt32.fromState("state_int_15"), 15),
test(DynamicInt32.fromState("state_int_15").plus(DynamicInt32.constant(2)), 17),
test(DynamicInt32.fromState("state_int_15").minus(DynamicInt32.constant(5)), 10),
test(DynamicInt32.fromState("state_int_15").times(DynamicInt32.constant(2)), 30),
test(DynamicInt32.fromState("state_int_15").div(DynamicInt32.constant(3)), 5),
test(DynamicInt32.fromState("state_int_15").rem(DynamicInt32.constant(2)), 1),
test(DynamicInt32.fromState("state_int_15").plus(2), 17),
test(DynamicInt32.fromState("state_int_15").minus(5), 10),
test(DynamicInt32.fromState("state_int_15").times(2), 30),
test(DynamicInt32.fromState("state_int_15").div(3), 5),
test(DynamicInt32.fromState("state_int_15").rem(2), 1),
test(DynamicInt32.fromState("state_int_15").plus(2.5f), 17.5f),
test(DynamicInt32.fromState("state_int_15").minus(5.5f), 9.5f),
test(DynamicInt32.fromState("state_int_15").times(2.5f), 37.5f),
test(DynamicInt32.fromState("state_int_15").div(2.0f), 7.5f),
test(DynamicInt32.fromState("state_int_15").rem(4.5f), 1.5f),
test(DynamicInt32.fromState("state_int_15").plus(DynamicFloat.constant(2.5f)), 17.5f),
test(DynamicInt32.fromState("state_int_15").minus(DynamicFloat.constant(5.5f)), 9.5f),
test(DynamicInt32.fromState("state_int_15").times(DynamicFloat.constant(2.5f)), 37.5f),
test(DynamicInt32.fromState("state_int_15").div(DynamicFloat.constant(2.0f)), 7.5f),
test(DynamicInt32.fromState("state_int_15").rem(DynamicFloat.constant(4.5f)), 1.5f),
test(DynamicFloat.constant(5.0f), 5.0f),
test(DynamicFloat.fromState("state_float_1.5"), 1.5f),
test(DynamicFloat.constant(1234.567f).asInt(), 1234),
test(DynamicFloat.constant(0.967f).asInt(), 0),
test(DynamicFloat.constant(-1234.967f).asInt(), -1235),
test(DynamicFloat.constant(-0.967f).asInt(), -1),
test(DynamicFloat.constant(Float.MIN_VALUE).asInt(), 0),
test(DynamicFloat.constant(Float.MAX_VALUE).asInt(), (int) Float.MAX_VALUE),
test(DynamicInt32.constant(100).asFloat(), 100.0f),
test(
DynamicInt32.constant(Integer.MIN_VALUE).asFloat(),
Float.valueOf(Integer.MIN_VALUE)),
test(
DynamicInt32.constant(Integer.MAX_VALUE).asFloat(),
Float.valueOf(Integer.MAX_VALUE)),
test(DynamicFloat.constant(100f).plus(DynamicFloat.constant(2f)), 102f),
test(DynamicFloat.constant(100f).minus(DynamicFloat.constant(5.5f)), 94.5f),
test(DynamicFloat.constant(5.5f).times(DynamicFloat.constant(2f)), 11f),
test(DynamicFloat.constant(5f).div(DynamicFloat.constant(2f)), 2.5f),
test(DynamicFloat.constant(5f).rem(DynamicFloat.constant(2f)), 1f),
test(DynamicFloat.constant(100f).plus(2f), 102f),
test(DynamicFloat.constant(100f).minus(5.5f), 94.5f),
test(DynamicFloat.constant(5.5f).times(2f), 11f),
test(DynamicFloat.constant(5f).div(2f), 2.5f),
test(DynamicFloat.constant(5f).rem(2f), 1f),
test(DynamicFloat.constant(0.12345622f).eq(0.12345688f), true),
test(DynamicFloat.constant(0.123455f).ne(0.123457f), true),
test(DynamicFloat.constant(0.12345622f).ne(0.12345688f), false),
test(DynamicFloat.constant(0.123455f).eq(0.123457f), false),
test(DynamicFloat.constant(0.4f).lt(0.6f), true),
test(DynamicFloat.constant(0.4f).lt(0.2f), false),
test(DynamicFloat.constant(0.1234568f).lt(0.1234562f), false),
test(DynamicFloat.constant(0.4f).lte(0.6f), true),
test(DynamicFloat.constant(0.1234568f).lte(0.1234562f), true),
test(DynamicFloat.constant(0.6f).gt(0.4f), true),
test(DynamicFloat.constant(0.4f).gt(0.6f), false),
test(DynamicFloat.constant(0.1234568f).gt(0.1234562f), false),
test(DynamicFloat.constant(0.6f).gte(0.4f), true),
test(DynamicFloat.constant(0.1234568f).gte(0.1234562f), true),
test(DynamicBool.constant(true), true),
test(DynamicBool.constant(true).isTrue(), true),
test(DynamicBool.constant(false).isTrue(), false),
test(DynamicBool.constant(true).isFalse(), false),
test(DynamicBool.constant(false).isFalse(), true),
test(DynamicBool.constant(true).and(DynamicBool.constant(true)), true),
test(DynamicBool.constant(true).and(DynamicBool.constant(false)), false),
test(DynamicBool.constant(false).and(DynamicBool.constant(true)), false),
test(DynamicBool.constant(false).and(DynamicBool.constant(false)), false),
test(DynamicBool.constant(true).or(DynamicBool.constant(true)), true),
test(DynamicBool.constant(true).or(DynamicBool.constant(false)), true),
test(DynamicBool.constant(false).or(DynamicBool.constant(true)), true),
test(DynamicBool.constant(false).or(DynamicBool.constant(false)), false),
test(DynamicBool.fromState("state_bool_true"), true),
test(DynamicBool.constant(false), false),
test(DynamicBool.fromState("state_bool_false"), false),
test(DynamicInt32.constant(5).eq(DynamicInt32.constant(5)), true),
test(DynamicInt32.constant(5).eq(DynamicInt32.constant(6)), false),
test(DynamicInt32.constant(5).ne(DynamicInt32.constant(5)), false),
test(DynamicInt32.constant(5).ne(DynamicInt32.constant(6)), true),
test(DynamicInt32.constant(10).lt(11), true),
test(DynamicInt32.constant(10).lt(10), false),
test(DynamicInt32.constant(10).lt(5), false),
test(DynamicInt32.constant(10).lte(11), true),
test(DynamicInt32.constant(10).lte(10), true),
test(DynamicInt32.constant(10).lte(5), false),
test(DynamicInt32.constant(10).gt(11), false),
test(DynamicInt32.constant(10).gt(10), false),
test(DynamicInt32.constant(10).gt(5), true),
test(DynamicInt32.constant(10).gte(11), false),
test(DynamicInt32.constant(10).gte(10), true),
test(DynamicInt32.constant(10).gte(5), true),
// Instant maximum value
test(
DynamicInstant.withSecondsPrecision(Instant.MAX),
Instant.MAX.truncatedTo(ChronoUnit.SECONDS)),
// Duration Int overflow
test(
DynamicInstant.withSecondsPrecision(Instant.EPOCH)
.durationUntil(DynamicInstant.withSecondsPrecision(Instant.MAX))
.toIntSeconds(),
(int) Instant.MAX.getEpochSecond()),
// Positive duration
test(durationOfSeconds(123456L).toIntDays(), 1),
test(durationOfSeconds(123456L).toIntHours(), 34),
test(durationOfSeconds(123456L).toIntMinutes(), 2057),
test(durationOfSeconds(123456L).toIntSeconds(), 123456),
test(durationOfSeconds(123456L).getIntDaysPart(), 1),
test(durationOfSeconds(123456L).getHoursPart(), 10),
test(durationOfSeconds(123456L).getMinutesPart(), 17),
test(durationOfSeconds(123456L).getSecondsPart(), 36),
// Negative duration
test(durationOfSeconds(-123456L).toIntDays(), -1),
test(durationOfSeconds(-123456L).toIntHours(), -34),
test(durationOfSeconds(-123456L).toIntMinutes(), -2057),
test(durationOfSeconds(-123456L).toIntSeconds(), -123456),
test(durationOfSeconds(-123456L).getIntDaysPart(), 1),
test(durationOfSeconds(-123456L).getHoursPart(), 10),
test(durationOfSeconds(-123456L).getMinutesPart(), 17),
test(durationOfSeconds(-123456L).getSecondsPart(), 36),
test(
DynamicString.onCondition(DynamicBool.constant(true))
.use(constant("Hello"))
.elseUse(constant("World")),
"Hello"),
test(
DynamicString.onCondition(DynamicBool.constant(false))
.use(constant("Hello"))
.elseUse(constant("World")),
"World"),
test(
DynamicString.fromState("state_hello_world")
.concat(DynamicString.constant("_test")),
"hello_world_test"),
test(
DynamicString.constant("this ")
.concat(DynamicString.constant("is "))
.concat(DynamicString.constant("a test")),
"this is a test"),
test(
DynamicInt32.onCondition(DynamicBool.constant(true))
.use(DynamicInt32.constant(1))
.elseUse(DynamicInt32.constant(10)),
1),
test(
DynamicInt32.onCondition(DynamicBool.constant(false))
.use(DynamicInt32.constant(1))
.elseUse(DynamicInt32.constant(10)),
10),
test(
DynamicFloat.constant(12.345f)
.format(
FloatFormatter.with()
.maxFractionDigits(2)
.minIntegerDigits(4)
.groupingUsed(true)),
"0,012.35"),
test(
DynamicFloat.constant(12.345f)
.format(
FloatFormatter.with()
.minFractionDigits(4)
.minIntegerDigits(4)
.groupingUsed(false)),
"0012.3450"),
test(
DynamicFloat.constant(12.345f)
.format(FloatFormatter.with().maxFractionDigits(1).groupingUsed(true))
.concat(DynamicString.constant("°")),
"12.3°"),
test(
DynamicFloat.constant(12.345678f)
.format(
FloatFormatter.with()
.minFractionDigits(4)
.maxFractionDigits(2)
.groupingUsed(true)),
"12.3457"),
test(
DynamicFloat.constant(12.345678f)
.format(FloatFormatter.with().minFractionDigits(2).groupingUsed(true)),
"12.346"),
test(DynamicFloat.constant(12.3456f).format(FloatFormatter.with()), "12.346"),
test(
DynamicInt32.constant(12)
.format(IntFormatter.with().minIntegerDigits(4).groupingUsed(true)),
"0,012"),
test(DynamicInt32.constant(12).format(IntFormatter.with()), "12")
};
ImmutableList.Builder<Object[]> immutableListBuilder = new ImmutableList.Builder<>();
for (DynamicTypeEvaluatorTest.TestCase<?> testCase : testCases) {
immutableListBuilder.add(new Object[] {testCase});
}
return immutableListBuilder.build();
}
private final DynamicTypeEvaluatorTest.TestCase<?> mTestCase;
public DynamicTypeEvaluatorTest(DynamicTypeEvaluatorTest.TestCase<?> testCase) {
this.mTestCase = testCase;
}
@Test
public void runTest() {
StateStore stateStore = new StateStore(generateExampleState());
DynamicTypeEvaluator evaluator =
new DynamicTypeEvaluator(
new DynamicTypeEvaluator.Config.Builder()
.setPlatformDataSourcesInitiallyEnabled(true)
.setStateStore(stateStore)
.setAnimationQuotaManager(new FixedQuotaManagerImpl(MAX_VALUE))
.build());
mTestCase.runTest(evaluator);
}
private static DynamicTypeEvaluatorTest.TestCase<String> test(
DynamicString bindUnderTest, String expectedValue) {
return new DynamicTypeEvaluatorTest.TestCase<>(
bindUnderTest.toDynamicStringProto().toString(),
(evaluator, cb) ->
evaluator
.bind(
bindUnderTest,
ULocale.getDefault(),
new MainThreadExecutor(),
cb)
.startEvaluation(),
expectedValue);
}
private static DynamicTypeEvaluatorTest.TestCase<Integer> test(
DynamicInt32 bindUnderTest, Integer expectedValue) {
return new DynamicTypeEvaluatorTest.TestCase<>(
bindUnderTest.toDynamicInt32Proto().toString(),
(evaluator, cb) ->
evaluator
.bind(bindUnderTest, new MainThreadExecutor(), cb)
.startEvaluation(),
expectedValue);
}
private static DynamicTypeEvaluatorTest.TestCase<Instant> test(
DynamicInstant bindUnderTest, Instant instant) {
return new DynamicTypeEvaluatorTest.TestCase<>(
bindUnderTest.toDynamicInstantProto().toString(),
(evaluator, cb) -> {
evaluator.bind(bindUnderTest, new MainThreadExecutor(), cb).startEvaluation();
},
instant);
}
private static DynamicTypeEvaluatorTest.TestCase<Float> test(
DynamicFloat bindUnderTest, Float expectedValue) {
return new DynamicTypeEvaluatorTest.TestCase<>(
bindUnderTest.toDynamicFloatProto().toString(),
(evaluator, cb) ->
evaluator
.bind(bindUnderTest, new MainThreadExecutor(), cb)
.startEvaluation(),
expectedValue);
}
private static DynamicTypeEvaluatorTest.TestCase<Boolean> test(
DynamicBool bindUnderTest, Boolean expectedValue) {
return new DynamicTypeEvaluatorTest.TestCase<>(
bindUnderTest.toDynamicBoolProto().toString(),
(evaluator, cb) ->
evaluator
.bind(bindUnderTest, new MainThreadExecutor(), cb)
.startEvaluation(),
expectedValue);
}
private static class TestCase<T> {
private final String mName;
private final BiConsumer<DynamicTypeEvaluator, DynamicTypeValueReceiver<T>>
mExpressionEvaluator;
private final T mExpectedValue;
TestCase(
String name,
BiConsumer<DynamicTypeEvaluator, DynamicTypeValueReceiver<T>> expressionEvaluator,
T expectedValue) {
this.mName = name;
this.mExpressionEvaluator = expressionEvaluator;
this.mExpectedValue = expectedValue;
}
public void runTest(DynamicTypeEvaluator evaluator) {
List<T> results = new ArrayList<>();
DynamicTypeValueReceiver<T> callback =
new DynamicTypeValueReceiver<T>() {
@Override
public void onData(@NonNull T newData) {
results.add(newData);
}
@Override
public void onInvalidated() {}
};
this.mExpressionEvaluator.accept(evaluator, callback);
assertThat(results).hasSize(1);
assertThat(results).containsExactly(mExpectedValue);
}
@NonNull
@Override
public String toString() {
return mName + " = " + mExpectedValue;
}
}
private static DynamicDuration durationOfSeconds(long seconds) {
Instant now = Instant.now();
return DynamicInstant.withSecondsPrecision(now)
.durationUntil(DynamicInstant.withSecondsPrecision(now.plusSeconds(seconds)));
}
private static ImmutableMap<String, StateEntryValue> generateExampleState() {
return ImmutableMap.of(
"state_hello_world",
StateEntryValue.newBuilder()
.setStringVal(FixedString.newBuilder().setValue("hello_world"))
.build(),
"state_int_15",
StateEntryValue.newBuilder()
.setInt32Val(FixedInt32.newBuilder().setValue(15))
.build(),
"state_float_1.5",
StateEntryValue.newBuilder()
.setFloatVal(FixedFloat.newBuilder().setValue(1.5f))
.build(),
"state_bool_true",
StateEntryValue.newBuilder()
.setBoolVal(FixedBool.newBuilder().setValue(true))
.build(),
"state_bool_false",
StateEntryValue.newBuilder()
.setBoolVal(FixedBool.newBuilder().setValue(false))
.build());
}
}