mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
390 lines
12 KiB
Java
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);
|
|
}
|
|
}
|
|
|
|
}
|