jdk/test/jdk/java/lang/StableValue/StableValueTest.java
2025-05-13 13:40:48 +00:00

390 lines
12 KiB
Java

/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/* @test
* @summary Basic tests for StableValue implementations
* @enablePreview
* @run junit StableValueTest
*/
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.UnaryOperator;
import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
final class StableValueTest {
private static final int VALUE = 42;
private static final int VALUE2 = 13;
@Test
void trySet() {
trySet(VALUE);
trySet(null);
}
@Test
void preSet() {
StableValue<Integer> stable = StableValue.of(VALUE);
assertTrue(stable.isSet());
assertEquals(VALUE, stable.orElseThrow());
assertEquals(VALUE, stable.orElse(VALUE2));
assertEquals(VALUE, stable.orElseSet(() -> VALUE2));
assertFalse(stable.trySet(VALUE2));
var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2));
assertEquals(
"The contents is already set",
e.getMessage());
}
void trySet(Integer initial) {
StableValue<Integer> stable = StableValue.of();
assertTrue(stable.trySet(initial));
assertFalse(stable.trySet(null));
assertFalse(stable.trySet(VALUE));
assertFalse(stable.trySet(VALUE2));
assertEquals(initial, stable.orElseThrow());
}
@Test
void setOrThrowValue() {
StableValue<Integer> stable = StableValue.of();
stable.setOrThrow(VALUE);
var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2));
assertEquals("The contents is already set", e.getMessage());
}
@Test
void setOrThrowNull() {
StableValue<Integer> stable = StableValue.of();
stable.setOrThrow(null);
var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(null));
assertEquals("The contents is already set", e.getMessage());
}
@Test
void orElse() {
StableValue<Integer> stable = StableValue.of();
assertEquals(VALUE, stable.orElse(VALUE));
assertNull(stable.orElse(null));
stable.trySet(VALUE);
assertEquals(VALUE, stable.orElse(VALUE2));
}
@Test
void orElseThrow() {
StableValue<Integer> stable = StableValue.of();
var e = assertThrows(NoSuchElementException.class, stable::orElseThrow);
assertEquals("No contents set", e.getMessage());
stable.trySet(VALUE);
assertEquals(VALUE, stable.orElseThrow());
}
@Test
void isSet() {
isSet(VALUE);
isSet(null);
}
void isSet(Integer initial) {
StableValue<Integer> stable = StableValue.of();
assertFalse(stable.isSet());
stable.trySet(initial);
assertTrue(stable.isSet());
}
@Test
void testOrElseSetSupplier() {
StableTestUtil.CountingSupplier<Integer> cs = new StableTestUtil.CountingSupplier<>(() -> VALUE);
StableValue<Integer> stable = StableValue.of();
assertThrows(NullPointerException.class, () -> stable.orElseSet(null));
assertEquals(VALUE, stable.orElseSet(cs));
assertEquals(1, cs.cnt());
assertEquals(VALUE, stable.orElseSet(cs));
assertEquals(1, cs.cnt());
}
@Test
void testHashCode() {
StableValue<Integer> stableValue = StableValue.of();
// Should be Object::hashCode
assertEquals(System.identityHashCode(stableValue), stableValue.hashCode());
}
@Test
void testEquals() {
StableValue<Integer> s0 = StableValue.of();
assertNotEquals(null, s0);
StableValue<Integer> s1 = StableValue.of();
assertNotEquals(s0, s1); // Identity based
s0.setOrThrow(42);
s1.setOrThrow(42);
assertNotEquals(s0, s1);
assertNotEquals("a", s0);
StableValue<Integer> null0 = StableValue.of();
StableValue<Integer> null1 = StableValue.of();
null0.setOrThrow(null);
null1.setOrThrow(null);
assertNotEquals(null0, null1);
}
@Test
void toStringUnset() {
StableValue<Integer> stable = StableValue.of();
assertEquals(".unset", stable.toString());
}
@Test
void toStringNull() {
StableValue<Integer> stable = StableValue.of();
assertTrue(stable.trySet(null));
assertEquals("null", stable.toString());
}
@Test
void toStringNonNull() {
StableValue<Integer> stable = StableValue.of();
assertTrue(stable.trySet(VALUE));
assertEquals(Objects.toString(VALUE), stable.toString());
}
@Test
void toStringCircular() {
StableValue<StableValue<?>> stable = StableValue.of();
stable.trySet(stable);
String toString = assertDoesNotThrow(stable::toString);
assertEquals("(this StableValue)", toString);
assertDoesNotThrow(stable::hashCode);
assertDoesNotThrow((() -> stable.equals(stable)));
}
@Test
void recursiveCall() {
StableValue<Integer> stable = StableValue.of();
AtomicReference<StableValue<Integer>> ref = new AtomicReference<>(stable);
assertThrows(IllegalStateException.class, () ->
stable.orElseSet(() -> {
ref.get().trySet(1);
return 1;
})
);
assertThrows(IllegalStateException.class, () ->
stable.orElseSet(() -> {
ref.get().orElseSet(() -> 1);
return 1;
})
);
}
@Test
void intFunctionExample() {
final class SqrtUtil {
private SqrtUtil() {}
private static final int CACHED_SIZE = 10;
private static final IntFunction<Double> SQRT =
// @link substring="intFunction" target="#intFunction(int,IntFunction)" :
StableValue.intFunction(CACHED_SIZE, StrictMath::sqrt);
public static double sqrt(int a) {
return SQRT.apply(a);
}
}
double sqrt9 = SqrtUtil.sqrt(9); // May eventually constant fold to 3.0 at runtime
assertEquals(3, sqrt9);
assertThrows(IllegalArgumentException.class, () -> SqrtUtil.sqrt(16));
}
@Test
void intFunctionExample2() {
final class PowerOf2Util {
private PowerOf2Util() {}
private static final int SIZE = 6;
private static final IntFunction<Integer> ORIGINAL_POWER_OF_TWO =
v -> 1 << v;
private static final IntFunction<Integer> POWER_OF_TWO =
// @link substring="intFunction" target="#intFunction(int,IntFunction)" :
StableValue.intFunction(SIZE, ORIGINAL_POWER_OF_TWO);
public static int powerOfTwo(int a) {
return POWER_OF_TWO.apply(a);
}
}
int pwr4 = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime
assertEquals(16, pwr4);
assertEquals(1, PowerOf2Util.powerOfTwo(0));
assertEquals(8, PowerOf2Util.powerOfTwo(3));
assertEquals(32, PowerOf2Util.powerOfTwo(5));
assertThrows(IllegalArgumentException.class, () -> PowerOf2Util.powerOfTwo(10));
}
@Test
void functionExample() {
class Log2Util {
private Log2Util() {}
private static final Set<Integer> CACHED_KEYS =
Set.of(1, 2, 4, 8, 16, 32);
private static final UnaryOperator<Integer> LOG2_ORIGINAL =
i -> 31 - Integer.numberOfLeadingZeros(i);
private static final Function<Integer, Integer> LOG2_CACHED =
// @link substring="function" target="#function(Set,Function)" :
StableValue.function(CACHED_KEYS, LOG2_ORIGINAL);
public static double log2(int a) {
if (CACHED_KEYS.contains(a)) {
return LOG2_CACHED.apply(a);
} else {
return LOG2_ORIGINAL.apply(a);
}
}
}
double log16 = Log2Util.log2(16); // May eventually constant fold to 4.0 at runtime
double log256 = Log2Util.log2(256); // Will not constant fold
assertEquals(4, log16);
assertEquals(8, log256);
}
@Test
void functionExample2() {
class Log2Util {
private Log2Util() {}
private static final Set<Integer> KEYS =
Set.of(1, 2, 4, 8);
private static final UnaryOperator<Integer> LOG2_ORIGINAL =
i -> 31 - Integer.numberOfLeadingZeros(i);
private static final Function<Integer, Integer> LOG2 =
// @link substring="function" target="#function(Set,Function)" :
StableValue.function(KEYS, LOG2_ORIGINAL);
public static double log2(int a) {
return LOG2.apply(a);
}
}
double log16 = Log2Util.log2(8); // May eventually constant fold to 3.0 at runtime
assertEquals(3, log16);
assertThrows(IllegalArgumentException.class, () -> Log2Util.log2(3));
}
private static final BiPredicate<StableValue<Integer>, Integer> TRY_SET = StableValue::trySet;
private static final BiPredicate<StableValue<Integer>, Integer> SET_OR_THROW = (s, i) -> {
try {
s.setOrThrow(i);
return true;
} catch (IllegalStateException e) {
return false;
}
};
@Test
void raceTrySet() {
race(TRY_SET);
}
@Test
void raceSetOrThrow() {
race(SET_OR_THROW);
}
@Test
void raceMixed() {
race((s, i) -> switch (i % 2) {
case 0 -> TRY_SET.test(s, i);
case 1 -> SET_OR_THROW.test(s, i);
default -> fail("should not reach here");
});
}
void race(BiPredicate<StableValue<Integer>, Integer> winnerPredicate) {
int noThreads = 10;
CountDownLatch starter = new CountDownLatch(noThreads);
StableValue<Integer> stable = StableValue.of();
Map<Integer, Boolean> winners = new ConcurrentHashMap<>();
List<Thread> threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> {
try {
// Ready ...
starter.countDown();
// ... set ...
starter.await();
// Here we go!
winners.put(i, winnerPredicate.test(stable, i));
} catch (Throwable t) {
fail(t);
}
}))
.toList();
threads.forEach(Thread::start);
threads.forEach(StableValueTest::join);
// There can only be one winner
assertEquals(1, winners.values().stream().filter(b -> b).count());
}
private static void join(Thread thread) {
try {
thread.join();
} catch (InterruptedException e) {
fail(e);
}
}
}