/
githubmirror
/
galahad
Обзор
Документация
Войти
/
githubmirror
/
galahad
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
test/jdk/java/lang/LazyConstant/LazyMapTest.java
574 строки
21 KB
Per Minborg
8372146: Follow-up changes to the Lazy Constants JEP
21 ноя 2025, 12:55
21 ноя 2025, 12:55
8a5c9e0
Код
Авторство
О чём код?
/* * 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 lazy map methods * @enablePreview * @modules java.base/java.util:+open * @run junit LazyMapTest */ 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.util.AbstractMap; import java.util.Arrays; import java.util.Comparator; import java.util.EnumSet; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; 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.stream.Collectors; import java.util.stream.Stream; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.mapping; import static org.junit.jupiter.api.Assertions.*; final class LazyMapTest { 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 Function<Value, Integer> MAPPER = Value::asInt; private static final Value KEY = Value.FORTY_TWO; private static final Integer VALUE = MAPPER.apply(KEY); @ParameterizedTest @MethodSource("allSets") void factoryInvariants(Set<Value> set) { assertThrows(NullPointerException.class, () -> Map.ofLazy(set, null), set.getClass().getSimpleName()); assertThrows(NullPointerException.class, () -> Map.ofLazy(null, MAPPER)); Set<Value> setWithNull = new HashSet<>(); setWithNull.add(KEY); setWithNull.add(null); assertThrows(NullPointerException.class, () -> Map.ofLazy(setWithNull, MAPPER)); } @ParameterizedTest @MethodSource("emptySets") void empty(Set<Value> set) { var lazy = newLazyMap(set); assertTrue(lazy.isEmpty()); assertEquals("{}", lazy.toString()); assertThrows(NullPointerException.class, () -> lazy.get(null)); assertNotEquals(null, lazy); } @ParameterizedTest @MethodSource("allSets") void size(Set<Value> set) { assertEquals(newRegularMap(set).size(), newLazyMap(set).size()); } @ParameterizedTest @MethodSource("nonEmptySets") void get(Set<Value> set) { LazyConstantTestUtil.CountingFunction<Value, Integer> cf = new LazyConstantTestUtil.CountingFunction<>(MAPPER); var lazy = Map.ofLazy(set, cf); int cnt = 1; for (Value v : set) { assertEquals(MAPPER.apply(v), lazy.get(v)); assertEquals(cnt, cf.cnt()); assertEquals(MAPPER.apply(v), lazy.get(v)); assertEquals(cnt++, cf.cnt()); } assertNull(lazy.get(Value.ILLEGAL_BETWEEN)); } @ParameterizedTest @MethodSource("nonEmptySets") void exception(Set<Value> set) { LazyConstantTestUtil.CountingFunction<Value, Integer> cif = new LazyConstantTestUtil.CountingFunction<>(_ -> { throw new UnsupportedOperationException(); }); var lazy = Map.ofLazy(set, cif); assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); assertEquals(2, cif.cnt()); assertThrows(UnsupportedOperationException.class, lazy::toString); assertEquals(3, cif.cnt()); } @ParameterizedTest @MethodSource("allSets") void containsKey(Set<Value> set) { var lazy = newLazyMap(set); for (Value v : set) { assertTrue(lazy.containsKey(v)); } assertFalse(lazy.containsKey(Value.ILLEGAL_BETWEEN)); } @ParameterizedTest @MethodSource("allSets") void containsValue(Set<Value> set) { var lazy = newLazyMap(set); for (Value v : set) { assertTrue(lazy.containsValue(MAPPER.apply(v))); } assertFalse(lazy.containsValue(MAPPER.apply(Value.ILLEGAL_BETWEEN))); } @ParameterizedTest @MethodSource("allSets") void forEach(Set<Value> set) { var lazy = newLazyMap(set); var ref = newRegularMap(set); Set<Map.Entry<Value, Integer>> expected = ref.entrySet(); Set<Map.Entry<Value, Integer>> actual = new HashSet<>(); lazy.forEach((k, v) -> actual.add(new AbstractMap.SimpleImmutableEntry<>(k , v))); assertEquals(expected, actual); } @ParameterizedTest @MethodSource("emptySets") void toStringTestEmpty(Set<Value> set) { var lazy = newLazyMap(set); assertEquals("{}", lazy.toString()); } @ParameterizedTest @MethodSource("nonEmptySets") void toStringTest(Set<Value> set) { var lazy = newLazyMap(set); var toString = lazy.toString(); assertTrue(toString.startsWith("{")); assertTrue(toString.endsWith("}")); // Key order is unspecified for (Value key : set) { toString = lazy.toString(); assertTrue(toString.contains(key + "=" + MAPPER.apply(key)), toString); } // One between the values assertEquals(set.size() - 1, toString.chars().filter(ch -> ch == ',').count()); } @ParameterizedTest @MethodSource("allSets") void hashCodeTest(Set<Value> set) { var lazy = newLazyMap(set); var regular = newRegularMap(set); assertEquals(regular.hashCode(), lazy.hashCode()); } @ParameterizedTest @MethodSource("allSets") void equality(Set<Value> set) { var lazy = newLazyMap(set); var regular = newRegularMap(set); assertEquals(regular, lazy); assertEquals(lazy, regular); assertNotEquals("A", lazy); } @ParameterizedTest @MethodSource("nonEmptySets") void circular(Set<Value> set) { final AtomicReference<Map<?, ?>> ref = new AtomicReference<>(); Map<Value, Map<?, ?>> lazy = Map.ofLazy(set, _ -> ref.get()); ref.set(lazy); lazy.get(KEY); var toString = lazy.toString(); assertTrue(toString.contains("FORTY_TWO=(this Map)"), toString); assertDoesNotThrow((() -> lazy.equals(lazy))); } @ParameterizedTest @MethodSource("nonEmptySets") void recursiveCall(Set<Value> set) { final AtomicReference<Map<Value, ?>> ref = new AtomicReference<>(); @SuppressWarnings("unchecked") Map<Value, Map<Value, Object>> lazy = Map.ofLazy(set, k -> (Map<Value, Object>) ref.get().get(k)); ref.set(lazy); var x = assertThrows(IllegalStateException.class, () -> lazy.get(KEY)); assertEquals("Recursive initialization of a lazy collection is illegal", x.getMessage()); } @ParameterizedTest @MethodSource("nonEmptySets") void entrySet(Set<Value> set) { var lazy = newLazyMap(set).entrySet(); var regular = newRegularMap(set).entrySet(); assertTrue(regular.equals(lazy)); assertTrue(lazy.equals(regular)); assertTrue(regular.equals(lazy)); } @ParameterizedTest @MethodSource("nonEmptySets") void entrySetToString(Set<Value> set) { var lazy = newLazyMap(set); var lazyEntrySet = lazy.entrySet(); var toString = lazyEntrySet.toString(); for (var key : set) { assertTrue(toString.contains(key + "=" + MAPPER.apply(key))); } assertTrue(toString.startsWith("[")); assertTrue(toString.endsWith("]")); } @ParameterizedTest @MethodSource("nonEmptySets") void values(Set<Value> set) { var lazy = newLazyMap(set); var lazyValues = lazy.values(); // Look at one of the elements var val = lazyValues.stream().iterator().next(); assertEquals(lazy.size() - 1, functionCounter(lazy)); // Mod ops assertThrows(UnsupportedOperationException.class, () -> lazyValues.remove(val)); assertThrows(UnsupportedOperationException.class, () -> lazyValues.add(val)); assertThrows(UnsupportedOperationException.class, lazyValues::clear); assertThrows(UnsupportedOperationException.class, () -> lazyValues.addAll(Set.of(VALUE))); assertThrows(UnsupportedOperationException.class, () -> lazyValues.removeIf(i -> true)); assertThrows(UnsupportedOperationException.class, () -> lazyValues.retainAll(Set.of(VALUE))); } @ParameterizedTest @MethodSource("allSets") void valuesToString(Set<Value> set) { var lazy = newLazyMap(set); var lazyValues = lazy.values(); var toString = lazyValues.toString(); // Key order is unspecified for (Value key : set) { assertTrue(toString.contains(MAPPER.apply(key).toString()), toString); } assertTrue(toString.startsWith("["), toString); assertTrue(toString.endsWith("]"), toString); } @ParameterizedTest @MethodSource("allSets") void iteratorNext(Set<Value> set) { Set<Value> encountered = new HashSet<>(); var iterator = newLazyMap(set).entrySet().iterator(); while (iterator.hasNext()) { var entry = iterator.next(); assertEquals(MAPPER.apply(entry.getKey()), entry.getValue()); encountered.add(entry.getKey()); } assertEquals(set, encountered); } @ParameterizedTest @MethodSource("nonEmptySets") void iteratorForEachRemaining(Set<Value> set) { Set<Value> encountered = new HashSet<>(); var iterator = newLazyMap(set).entrySet().iterator(); var entry = iterator.next(); assertEquals(MAPPER.apply(entry.getKey()), entry.getValue()); encountered.add(entry.getKey()); iterator.forEachRemaining(e -> { assertEquals(MAPPER.apply(e.getKey()), e.getValue()); encountered.add(e.getKey()); }); assertEquals(set, encountered); } @ParameterizedTest @MethodSource("nonEmptySets") void lazyEntry(Set<Value> set) { var lazy = newLazyMap(set); var entry = lazy.entrySet().stream() .filter(e -> e.getKey().equals(KEY)) .findAny() .orElseThrow(); assertEquals(lazy.size(), functionCounter(lazy)); var otherDifferent = Map.entry(Value.ZERO, -1); assertNotEquals(entry, otherDifferent); assertEquals(lazy.size(), functionCounter(lazy)); var otherEqual = Map.entry(entry.getKey(), entry.getValue()); assertEquals(entry, otherEqual); assertEquals(lazy.size() - 1, functionCounter(lazy)); assertEquals(entry.hashCode(), otherEqual.hashCode()); } @ParameterizedTest @MethodSource("nonEmptySets") void lazyForEachEntry(Set<Value> set) { var lazy = newLazyMap(set); // Only touch the key. lazy.entrySet().iterator().forEachRemaining(Map.Entry::getKey); assertEquals(lazy.size(), functionCounter(lazy)); // No evaluation // Only touch the value. lazy.entrySet().iterator().forEachRemaining(Map.Entry::getValue); assertEquals(0, functionCounter(lazy)); } // 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 = newLazyMap(set); assertThrows(expectedType, () -> operation.accept(lazy), set.getClass().getSimpleName() + " " + operation); } } // Implementing interfaces @ParameterizedTest @MethodSource("allSets") void serializable(Set<Value> set) { var lazy = newLazyMap(set); assertFalse(lazy instanceof Serializable); assertFalse(lazy.entrySet() instanceof Serializable); assertFalse(lazy.values() instanceof Serializable); } @Test void nullResult() { var lazy = Map.ofLazy(Set.of(0), _ -> null); assertThrows(NullPointerException.class, () -> lazy.getOrDefault(0, 1));; assertTrue(lazy.containsKey(0)); } @ParameterizedTest @MethodSource("allSets") void functionHolder(Set<Value> set) { LazyConstantTestUtil.CountingFunction<Value, Integer> cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER); Map<Value, Integer> lazy = Map.ofLazy(set, cif); Object holder = LazyConstantTestUtil.functionHolder(lazy); int i = 0; for (Value key : set) { assertEquals(set.size() - i, LazyConstantTestUtil.functionHolderCounter(holder)); assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder)); int v = lazy.get(key); int v2 = lazy.get(key); i++; } assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder)); assertNull(LazyConstantTestUtil.functionHolderFunction(holder)); } @ParameterizedTest @MethodSource("allSets") void functionHolderViaEntrySet(Set<Value> set) { LazyConstantTestUtil.CountingFunction<Value, Integer> cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER); Map<Value, Integer> lazy = Map.ofLazy(set, cif); Object holder = LazyConstantTestUtil.functionHolder(lazy); int i = 0; for (Map.Entry<Value, Integer> e : lazy.entrySet()) { assertEquals(set.size() - i, LazyConstantTestUtil.functionHolderCounter(holder)); assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder)); int v = e.getValue(); int v2 = e.getValue(); i++; } assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder)); assertNull(LazyConstantTestUtil.functionHolderFunction(holder)); } @ParameterizedTest @MethodSource("allSets") void underlyingRefViaEntrySetForEach(Set<Value> set) { LazyConstantTestUtil.CountingFunction<Value, Integer> cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER); Map<Value, Integer> lazy = Map.ofLazy(set, cif); Object holder = LazyConstantTestUtil.functionHolder(lazy); final AtomicInteger i = new AtomicInteger(); lazy.entrySet().forEach(e -> { assertEquals(set.size() - i.get(), LazyConstantTestUtil.functionHolderCounter(holder)); assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder)); Integer val = e.getValue(); Integer val2 = e.getValue(); i.incrementAndGet(); }); assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder)); assertNull(LazyConstantTestUtil.functionHolderFunction(holder)); } @Test void usesOptimizedVersion() { Map<Value, Integer> enumMap = Map.ofLazy(EnumSet.of(KEY), Value::asInt); assertTrue(enumMap.getClass().getName().contains("Enum"), enumMap.getClass().getName()); Map<Value, Integer> emptyMap = Map.ofLazy(EnumSet.noneOf(Value.class), Value::asInt); assertFalse(emptyMap.getClass().getName().contains("Enum"), emptyMap.getClass().getName()); Map<Value, Integer> regularMap = Map.ofLazy(Set.of(KEY), Value::asInt); assertFalse(regularMap.getClass().getName().contains("Enum"), regularMap.getClass().getName()); } @Test void overriddenEnum() { final var overridden = Value.THIRTEEN; Map<Value, Integer> enumMap = Map.ofLazy(EnumSet.of(overridden), MAPPER); assertEquals(MAPPER.apply(overridden), enumMap.get(overridden), enumMap.toString()); } @Test void enumAliasing() { enum MyEnum {FOO, BAR} enum MySecondEnum{BAZ, QUX} Map<MyEnum, Integer> mapEnum = Map.ofLazy(EnumSet.allOf(MyEnum.class), MyEnum::ordinal); assertEquals(MyEnum.BAR.ordinal(), mapEnum.get(MyEnum.BAR)); // Make sure class is checked, not just `ordinal()` assertNull(mapEnum.get(MySecondEnum.QUX)); } // Support constructs record Operation(String name, Consumer<Map<Value, Integer>> consumer) implements Consumer<Map<Value, Integer>> { @java.lang.Override public void accept(Map<Value, Integer> map) { consumer.accept(map); } @java.lang.Override public String toString() { return name; } } static Stream<Operation> nullAverseOperations() { return Stream.of( new Operation("forEach", m -> m.forEach(null)) ); } static Stream<Operation> unsupportedOperations() { return Stream.of( new Operation("clear", Map::clear), new Operation("compute", m -> m.compute(KEY, (_, _) -> 1)), new Operation("computeIfAbsent", m -> m.computeIfAbsent(KEY, _ -> 1)), new Operation("computeIfPresent", m -> m.computeIfPresent(KEY, (_, _) -> 1)), new Operation("merge", m -> m.merge(KEY, VALUE, (a, _) -> a)), new Operation("put", m -> m.put(KEY, 0)), new Operation("putAll", m -> m.putAll(Map.of())), new Operation("remove1", m -> m.remove(KEY)), new Operation("remove2", m -> m.remove(KEY, VALUE)), new Operation("replace2", m -> m.replace(KEY, 1)), new Operation("replace3", m -> m.replace(KEY, VALUE, 1)), new Operation("replaceAll", m -> m.replaceAll((a, _) -> MAPPER.apply(a))) ); } static Map<Value, Integer> newLazyMap(Set<Value> set) { return Map.ofLazy(set, MAPPER); } static Map<Value, Integer> newRegularMap(Set<Value> set) { return set.stream() .collect(Collectors.toMap(Function.identity(), MAPPER)); } private static Stream<Set<Value>> nonEmptySets() { return Stream.of( Set.of(KEY, Value.THIRTEEN), linkedHashSet(Value.THIRTEEN, KEY), treeSet(KEY, Value.THIRTEEN), EnumSet.of(KEY, Value.THIRTEEN) ); } 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; } private static int functionCounter(Map<?, ?> lazy) { final Object holder = LazyConstantTestUtil.functionHolder(lazy); return LazyConstantTestUtil.functionHolderCounter(holder); } }