/
githubmirror
/
panama-vector
Обзор
Документация
Войти
/
githubmirror
/
panama-vector
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
test/jdk/java/lang/LazyConstant/LazySetTest.java
591 строка
21 KB
Per Minborg
8376811: Implement JEP 531: Lazy Constants (Third Preview)
08 май 2026, 11:03
08 май 2026, 11:03
a6bd64b
Код
Авторство
О чём код?
/* * Copyright (c) 2026, 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 lazy set methods * @enablePreview * @modules java.base/java.util:+open * @run junit LazySetTest */ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import java.io.Serializable; import java.lang.Class; import java.lang.Override; import java.util.*; import java.util.Arrays; import java.util.Comparator; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; final class LazySetTest { enum Value { // Zero is here so that we have enums with ordinals before the first one // actually used in input sets (i.e. ZERO is not in the input set) ZERO(0), ILLEGAL_BEFORE(-1), // Valid values THIRTEEN(13) { @Override public String toString() { // getEnumConstants will be `null` for this enum as it is overridden return super.toString()+" (Overridden)"; } }, ILLEGAL_BETWEEN(-2), FORTY_TWO(42), // Illegal values (not in the input set) ILLEGAL_AFTER(-3); final int intValue; Value(int intValue) { this.intValue = intValue; } int asInt() { return intValue; } } private static final Value MEMBER = Value.FORTY_TWO; private static final Value NON_MEMBER = Value.THIRTEEN; private static final Set<Value> SET = Set.of(NON_MEMBER, MEMBER); private static final Predicate<Value> PREDICATE = c -> c == MEMBER; ; private static final long TIME_OUT_S = 5; private static final long OVERLAP_TIME_MS = 100; @ParameterizedTest @MethodSource("allSets") void factoryInvariants(Set<Value> set) { assertThrows(NullPointerException.class, () -> Set.ofLazy(set, null), set.getClass().getSimpleName()); assertThrows(NullPointerException.class, () -> Set.ofLazy(null, PREDICATE)); Set<Value> setWithNull = new HashSet<>(); setWithNull.add(MEMBER); setWithNull.add(null); assertThrows(NullPointerException.class, () -> Set.ofLazy(setWithNull, PREDICATE)); } @ParameterizedTest @MethodSource("emptySets") void empty(Set<Value> set) { var lazy = newLazySet(set); assertTrue(lazy.isEmpty()); assertEquals("[]", lazy.toString()); } @ParameterizedTest @MethodSource("allSets") void size(Set<Value> set) { assertEquals(newRegularSet(set).size(), newLazySet(set).size()); } @ParameterizedTest @MethodSource("nonEmptySets") void exception(Set<Value> set) { LazyConstantTestUtil.CountingPredicate<Value> cif = new LazyConstantTestUtil.CountingPredicate<>(_ -> { throw new UnsupportedOperationException("Initial exception"); }); var lazy = Set.ofLazy(set, cif); var x = assertThrows(NoSuchElementException.class, () -> lazy.contains(MEMBER)); assertEquals(LazyConstantTestUtil.expectedMessage(UnsupportedOperationException.class, MEMBER), x.getMessage()); assertEquals(UnsupportedOperationException.class, x.getCause().getClass()); assertEquals(1, cif.cnt()); var x2 = assertThrows(NoSuchElementException.class, () -> lazy.contains(MEMBER)); assertEquals(LazyConstantTestUtil.expectedMessage(UnsupportedOperationException.class, MEMBER), x2.getMessage()); // The initial cause should only be present on the _first_ unchecked exception assertNull(x2.getCause()); for (Value v : set) { // Make sure all values are touched assertThrows(Exception.class, () -> lazy.contains(v)); } var xToString = assertThrows(NoSuchElementException.class, lazy::toString); var xMessage = xToString.getMessage(); assertTrue(xMessage.startsWith(LazyConstantTestUtil.expectedMessage(UnsupportedOperationException.class, 0).substring(0, xMessage.indexOf("'")))); assertEquals(set.size(), cif.cnt()); } @ParameterizedTest @MethodSource("allSets") void contains(Set<Value> set) { var lazy = newLazySet(set); var expected = newRegularSet(set); for (Value v : set) { assertEquals(expected.contains(v), lazy.contains(v)); } assertFalse(lazy.contains(Value.ILLEGAL_BETWEEN)); } @ParameterizedTest @MethodSource("allSets") void forEach(Set<Value> set) { var lazy = newLazySet(set); var expected = newRegularSet(set); Set<Value> actual = new HashSet<>(); lazy.forEach(actual::add); assertEquals(expected, actual); } @ParameterizedTest @MethodSource("emptySets") void toStringTestEmpty(Set<Value> set) { var lazy = newLazySet(set); assertEquals("[]", lazy.toString()); } @ParameterizedTest @MethodSource("nonEmptySets") void toStringTest(Set<Value> set) { var lazy = newLazySet(set); var expected = newRegularSet(set); var toString = lazy.toString(); assertTrue(toString.startsWith("[")); assertTrue(toString.endsWith("]")); // Key order is unspecified for (Value key : expected) { assertTrue(toString.contains(key.toString()), key + " is not in" + toString); } // One between the values assertEquals(expected.size() - 1, toString.chars().filter(ch -> ch == ',').count()); } @ParameterizedTest @MethodSource("allSets") void hashCodeTest(Set<Value> set) { var lazy = newLazySet(set); var regular = newRegularSet(set); assertEquals(regular.hashCode(), lazy.hashCode()); } @ParameterizedTest @MethodSource("allSets") void zeroHashCodeTest() { specificHashCodeTest(0); } @ParameterizedTest @MethodSource("allSets") void negativeHashCodeTest() { specificHashCodeTest(-1); specificHashCodeTest(-42); specificHashCodeTest(Integer.MIN_VALUE); } @ParameterizedTest @MethodSource("allSets") void positiveHashCodeTest() { specificHashCodeTest(1); specificHashCodeTest(42); specificHashCodeTest(Integer.MAX_VALUE); } void specificHashCodeTest(int hc) { final class ZeroHashCode { @Override public int hashCode() { return hc; } } var lazy = Set.ofLazy(Set.of(new ZeroHashCode()), e -> true); assertEquals(hc, lazy.hashCode()); } @ParameterizedTest @MethodSource("allSets") void equality(Set<Value> set) { var lazy = newLazySet(set); var regular = newRegularSet(set); assertEquals(regular, lazy); assertEquals(lazy, regular); assertNotEquals("A", lazy); } @ParameterizedTest @MethodSource("nonEmptySets") void recursiveCall(Set<Value> set) { final AtomicReference<Set<Value>> ref = new AtomicReference<>(); @SuppressWarnings("unchecked") Set<Value> lazy = Set.ofLazy(set, k -> ref.get().contains(k)); ref.set(lazy); var x = assertThrows(NoSuchElementException.class, () -> lazy.contains(MEMBER)); assertEquals(LazyConstantTestUtil.expectedMessage(java.lang.IllegalStateException.class, MEMBER), x.getMessage()); assertEquals("Recursive initialization of a lazy collection is illegal: " + MEMBER, x.getCause().getMessage()); assertEquals(IllegalStateException.class, x.getCause().getClass()); } @ParameterizedTest @MethodSource("allSets") void iteratorNext(Set<Value> set) { Set<Value> encountered = new HashSet<>(); var expected = newRegularSet(set); var iterator = newLazySet(set).iterator(); while (iterator.hasNext()) { var entry = iterator.next(); encountered.add(entry); } assertEquals(expected, encountered); } @ParameterizedTest @MethodSource("nonEmptySets") void iteratorForEachRemaining(Set<Value> set) { Set<Value> encountered = new HashSet<>(); var expected = newRegularSet(set); var iterator = newLazySet(set).iterator(); var value = iterator.next(); encountered.add(value); iterator.forEachRemaining(encountered::add); assertEquals(expected, encountered); } @ParameterizedTest @MethodSource("nonEmptySets") void atMostOnceComputationUnderContention(Set<Value> set) throws Exception { // Make sure to exercise both member and non-member statuses for (Value candidate : set) { // Mitigate thread starvation via a dedicated thread pool != FJP try (var testExecutor = Executors.newFixedThreadPool(3)) { AtomicInteger calls = new AtomicInteger(); CountDownLatch entered = new CountDownLatch(1); CountDownLatch release = new CountDownLatch(1); CountDownLatch competing = new CountDownLatch(2); Set<Value> constant = Set.ofLazy(set, i -> { calls.incrementAndGet(); entered.countDown(); try { assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); } catch (InterruptedException e) { throw new AssertionError(e); } return PREDICATE.test(i); }); var f1 = CompletableFuture.supplyAsync(() -> constant.contains(candidate), testExecutor); assertTrue(entered.await(5, TimeUnit.SECONDS)); var f2 = CompletableFuture.supplyAsync(() -> { competing.countDown(); return constant.contains(candidate); }, testExecutor); var f3 = CompletableFuture.supplyAsync(() -> { competing.countDown(); return constant.contains(candidate); }, testExecutor); assertTrue(competing.await(TIME_OUT_S, TimeUnit.SECONDS)); // While computation is blocked, only one thread should have entered supplier Thread.sleep(OVERLAP_TIME_MS); assertEquals(1, calls.get()); release.countDown(); assertEquals(PREDICATE.test(candidate), f1.get(TIME_OUT_S, TimeUnit.SECONDS)); assertEquals(PREDICATE.test(candidate), f2.get(TIME_OUT_S, TimeUnit.SECONDS)); assertEquals(PREDICATE.test(candidate), f3.get(TIME_OUT_S, TimeUnit.SECONDS)); assertEquals(1, calls.get()); } } } @ParameterizedTest @MethodSource("nonEmptySets") void competingThreadsBlockUntilInitializationCompletes(Set<Value> set) throws Exception { // Make sure to exercise both member and non-member statuses for (Value candidate : set) { // Mitigate thread starvation via a dedicated thread pool != FJP try (var testExecutor = Executors.newFixedThreadPool(2)) { CountDownLatch entered = new CountDownLatch(1); CountDownLatch release = new CountDownLatch(1); CountDownLatch waiting = new CountDownLatch(1); Set<Value> constant = Set.ofLazy(set, i -> { entered.countDown(); try { assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); } catch (InterruptedException e) { throw new AssertionError(e); } return PREDICATE.test(i); }); var computingThread = CompletableFuture.supplyAsync(() -> constant.contains(candidate), testExecutor); assertTrue(entered.await(TIME_OUT_S, TimeUnit.SECONDS)); var waitingThread = CompletableFuture.supplyAsync(() -> { waiting.countDown(); return constant.contains(candidate); }, testExecutor); assertTrue(waiting.await(TIME_OUT_S, TimeUnit.SECONDS)); Thread.sleep(OVERLAP_TIME_MS); assertFalse(waitingThread.isDone(), "contending thread should be be blocked"); release.countDown(); assertEquals(PREDICATE.test(candidate), computingThread.get(TIME_OUT_S, TimeUnit.SECONDS)); assertEquals(PREDICATE.test(candidate), waitingThread.get(TIME_OUT_S, TimeUnit.SECONDS)); } } } @ParameterizedTest @MethodSource("nonEmptySets") void interruptStatusIsPreservedForComputingThread(Set<Value> set) throws Exception { // Make sure to exercise both member and non-member statuses for (Value candidate : set) { int unset = -1; int notInterrupted = 0; int interrupted = 1; AtomicInteger observedInterrupted = new AtomicInteger(unset); CountDownLatch supplierRunning = new CountDownLatch(1); CountDownLatch release = new CountDownLatch(1); Set<Value> constant = Set.ofLazy(set, i -> { supplierRunning.countDown(); try { assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); } catch (InterruptedException e) { observedInterrupted.set(Thread.currentThread().isInterrupted() ? interrupted : notInterrupted); Thread.currentThread().interrupt(); // restore if await cleared it } return PREDICATE.test(i); }); AtomicInteger interruptedAfterGet = new AtomicInteger(unset); Thread t = Thread.ofPlatform().start(() -> { assertEquals(PREDICATE.test(candidate), constant.contains(candidate)); interruptedAfterGet.set(Thread.currentThread().isInterrupted() ? interrupted : notInterrupted); }); assertTrue(supplierRunning.await(TIME_OUT_S, TimeUnit.SECONDS)); Thread.sleep(OVERLAP_TIME_MS); t.interrupt(); release.countDown(); t.join(); assertEquals(notInterrupted, observedInterrupted.get()); // Observed before restoration of the status assertEquals(interrupted, interruptedAfterGet.get(), "get() cleared interrupt status"); } } // Immutability @ParameterizedTest @MethodSource("unsupportedOperations") void unsupported(Operation operation) { assertThrowsForOperation(UnsupportedOperationException.class, operation); } // Method parameter invariant checking @ParameterizedTest @MethodSource("nullAverseOperations") void nullAverse(Operation operation) { assertThrowsForOperation(NullPointerException.class, operation); } static <T extends Throwable> void assertThrowsForOperation(Class<T> expectedType, Operation operation) { for (Set<Value> set : allSets().toList()) { var lazy = newLazySet(set); assertThrows(expectedType, () -> operation.accept(lazy), set.getClass().getSimpleName() + " " + operation); } } // Implementing interfaces @ParameterizedTest @MethodSource("allSets") void serializable(Set<Value> set) { var lazy = newLazySet(set); assertFalse(lazy instanceof Serializable); } @Test void overriddenEnum() { final var overridden = Value.THIRTEEN; Set<Value> enumMap = Set.ofLazy(EnumSet.of(overridden), PREDICATE); assertEquals(PREDICATE.test(overridden), enumMap.contains(overridden), enumMap.toString()); } // Support constructs record Operation(String name, Consumer<Set<Value>> consumer) implements Consumer<Set<Value>> { @Override public void accept(Set<Value> set) { consumer.accept(set); } @Override public String toString() { return name; } } static Stream<Operation> nullAverseOperations() { return Stream.of( new Operation("forEach", m -> m.forEach(null)), new Operation("containsAll", m -> m.containsAll(null)), new Operation("contains", m -> m.contains(null)) ); } static Stream<Operation> unsupportedOperations() { return Stream.of( new Operation("clear", Set::clear), new Operation("add", m -> m.add(MEMBER)), new Operation("addAll", m -> m.addAll(Set.of(MEMBER))), new Operation("remove", m -> m.remove(MEMBER)), new Operation("removeAll",m -> m.removeAll(Set.of(MEMBER))), new Operation("retainAll",m -> m.retainAll(Set.of(MEMBER))), new Operation("iter.rm", m -> m.iterator().remove()) ); } static Set<Value> newLazySet(Set<Value> set) { return Set.ofLazy(set, PREDICATE); } static Set<Value> newRegularSet(Set<Value> set) { return set.stream() .filter(PREDICATE) .collect(Collectors.toSet()); } private static Stream<Set<Value>> nonEmptySets() { return Stream.of( Set.of(MEMBER, NON_MEMBER), linkedHashSet(NON_MEMBER, MEMBER), treeSet(MEMBER, NON_MEMBER), EnumSet.of(MEMBER, NON_MEMBER) ); } private static Stream<Set<Value>> emptySets() { return Stream.of( Set.of(), linkedHashSet(), treeSet(), EnumSet.noneOf(Value.class) ); } private static Stream<Set<Value>> allSets() { return Stream.concat( nonEmptySets(), emptySets() ); } static Set<Value> treeSet(Value... values) { return populate(new TreeSet<>(Comparator.comparingInt(Value::asInt).reversed()),values); } static Set<Value> linkedHashSet(Value... values) { return populate(new LinkedHashSet<>(), values); } static Set<Value> populate(Set<Value> set, Value... values) { set.addAll(Arrays.asList(values)); return set; } // JEP Example class Application { enum Option { VERBOSE, DRY_RUN, STRICT } // Return true when the given Option is enabled private static boolean isEnabled(Option option) { // Parse command line, read configuration file, load database return true; } // Lazily initialized Set of Options static final Set<Option> OPTIONS = Set.ofLazy(EnumSet.allOf(Option.class), Application::isEnabled); public static void process() { if (OPTIONS.contains(Option.DRY_RUN)) { // Skip processing in DRY_RUN mode return; } // Actual Processing logic } } // Javadoc equivalent class LazySet<E> extends AbstractCollection<E> implements Set<E> { private final Map<E, LazyConstant<Boolean>> backingMap; public LazySet(Set<E> elementCandidates, Predicate<E> computingFunction) { this.backingMap = elementCandidates.stream() .collect(Collectors.toUnmodifiableMap( Function.identity(), k -> LazyConstant.of(() -> computingFunction.test(k)))); } @Override public boolean contains(Object o) { var lazyConstant = backingMap.get(o); return lazyConstant == null ? false : lazyConstant.get(); } @Override public Iterator<E> iterator() { return null; } @Override public int size() { return 0; } } }