Java
359 строк · 11.9 Кб
1/*
2* Copyright (C) 2008 The Guava Authors
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8* http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16
17package com.google.common.collect.testing;18
19import static com.google.common.base.Preconditions.checkNotNull;20import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_KEYS;21import static com.google.common.collect.testing.features.MapFeature.ALLOWS_NULL_VALUES;22
23import com.google.common.collect.Lists;24import com.google.common.collect.Maps;25import com.google.common.collect.testing.features.CollectionFeature;26import com.google.common.collect.testing.features.CollectionSize;27import com.google.common.collect.testing.features.Feature;28import com.google.common.collect.testing.features.MapFeature;29import com.google.common.reflect.Reflection;30import java.io.Serializable;31import java.lang.reflect.InvocationHandler;32import java.lang.reflect.InvocationTargetException;33import java.lang.reflect.Method;34import java.util.AbstractMap;35import java.util.AbstractSet;36import java.util.Collection;37import java.util.Collections;38import java.util.HashMap;39import java.util.Iterator;40import java.util.List;41import java.util.Map;42import java.util.Map.Entry;43import java.util.Set;44import java.util.concurrent.atomic.AtomicBoolean;45import java.util.function.Predicate;46import junit.framework.Test;47import junit.framework.TestCase;48import junit.framework.TestSuite;49import org.checkerframework.checker.nullness.qual.Nullable;50
51/**
52* Tests {@link MapTestSuiteBuilder} by using it against maps that have various negative behaviors.
53*
54* @author George van den Driessche
55*/
56public final class MapTestSuiteBuilderTests extends TestCase {57private MapTestSuiteBuilderTests() {}58
59public static Test suite() {60TestSuite suite = new TestSuite(MapTestSuiteBuilderTests.class.getSimpleName());61suite.addTest(testsForHashMapNullKeysForbidden());62suite.addTest(testsForHashMapNullValuesForbidden());63suite.addTest(testsForSetUpTearDown());64return suite;65}66
67private abstract static class WrappedHashMapGenerator extends TestStringMapGenerator {68@Override69protected final Map<String, String> create(Entry<String, String>[] entries) {70HashMap<String, String> map = Maps.newHashMap();71for (Entry<String, String> entry : entries) {72map.put(entry.getKey(), entry.getValue());73}74return wrap(map);75}76
77abstract Map<String, String> wrap(HashMap<String, String> map);78}79
80private static TestSuite wrappedHashMapTests(81WrappedHashMapGenerator generator, String name, Feature<?>... features) {82List<Feature<?>> featuresList = Lists.newArrayList(features);83Collections.addAll(84featuresList,85MapFeature.GENERAL_PURPOSE,86CollectionFeature.SUPPORTS_ITERATOR_REMOVE,87CollectionSize.ANY);88return MapTestSuiteBuilder.using(generator)89.named(name)90.withFeatures(featuresList)91.createTestSuite();92}93
94// TODO: consider being null-hostile in these tests95
96private static Test testsForHashMapNullKeysForbidden() {97return wrappedHashMapTests(98new WrappedHashMapGenerator() {99@Override100Map<String, String> wrap(final HashMap<String, String> map) {101if (map.containsKey(null)) {102throw new NullPointerException();103}104return new AbstractMap<String, String>() {105@Override106public Set<Entry<String, String>> entrySet() {107return map.entrySet();108}109
110@Override111public @Nullable String put(String key, String value) {112checkNotNull(key);113return map.put(key, value);114}115};116}117},118"HashMap w/out null keys",119ALLOWS_NULL_VALUES);120}121
122private static Test testsForHashMapNullValuesForbidden() {123return wrappedHashMapTests(124new WrappedHashMapGenerator() {125@Override126Map<String, String> wrap(final HashMap<String, String> map) {127if (map.containsValue(null)) {128throw new NullPointerException();129}130
131return new AbstractMap<String, String>() {132@Override133public Set<Entry<String, String>> entrySet() {134return new EntrySet();135}136
137@Override138public int hashCode() {139return map.hashCode();140}141
142@Override143public boolean equals(@Nullable Object o) {144return map.equals(o);145}146
147@Override148public String toString() {149return map.toString();150}151
152@Override153public @Nullable String remove(Object key) {154return map.remove(key);155}156
157@Override158public boolean remove(Object key, Object value) {159return map.remove(key, value);160}161
162class EntrySet extends AbstractSet<Map.Entry<String, String>> {163@Override164public Iterator<Entry<String, String>> iterator() {165return new Iterator<Entry<String, String>>() {166
167final Iterator<Entry<String, String>> iterator = map.entrySet().iterator();168
169@Override170public void remove() {171iterator.remove();172}173
174@Override175public boolean hasNext() {176return iterator.hasNext();177}178
179@Override180public Entry<String, String> next() {181return transform(iterator.next());182}183
184private Entry<String, String> transform(final Entry<String, String> next) {185return new Entry<String, String>() {186
187@Override188public String setValue(String value) {189checkNotNull(value);190return next.setValue(value);191}192
193@Override194public String getValue() {195return next.getValue();196}197
198@Override199public String getKey() {200return next.getKey();201}202
203@Override204public boolean equals(@Nullable Object obj) {205return next.equals(obj);206}207
208@Override209public int hashCode() {210return next.hashCode();211}212};213}214};215}216
217@Override218public int size() {219return map.size();220}221
222@Override223public boolean remove(Object o) {224return map.entrySet().remove(o);225}226
227@Override228public boolean removeIf(Predicate<? super Entry<String, String>> filter) {229return map.entrySet().removeIf(filter);230}231
232@Override233public boolean containsAll(Collection<?> c) {234return map.entrySet().containsAll(c);235}236
237@Override238public boolean removeAll(Collection<?> c) {239return map.entrySet().removeAll(c);240}241
242@Override243public boolean retainAll(Collection<?> c) {244return map.entrySet().retainAll(c);245}246
247@Override248public int hashCode() {249return map.entrySet().hashCode();250}251
252@Override253public boolean equals(@Nullable Object o) {254return map.entrySet().equals(o);255}256
257@Override258public String toString() {259return map.entrySet().toString();260}261}262
263@Override264public @Nullable String put(String key, String value) {265checkNotNull(value);266return map.put(key, value);267}268};269}270},271"HashMap w/out null values",272ALLOWS_NULL_KEYS);273}274
275/**276* Map generator that verifies that {@code setUp()} methods are called in all the test cases. The
277* {@code setUpRan} parameter is set true by the {@code setUp} that every test case is supposed to
278* have registered, and set false by the {@code tearDown}. We use a dynamic proxy to intercept all
279* of the {@code Map} method calls and check that {@code setUpRan} is true.
280*/
281private static class CheckSetUpHashMapGenerator extends WrappedHashMapGenerator {282private final AtomicBoolean setUpRan;283
284CheckSetUpHashMapGenerator(AtomicBoolean setUpRan) {285this.setUpRan = setUpRan;286}287
288@Override289Map<String, String> wrap(HashMap<String, String> map) {290@SuppressWarnings("unchecked")291Map<String, String> proxy =292Reflection.newProxy(Map.class, new CheckSetUpInvocationHandler(map, setUpRan));293return proxy;294}295}296
297/**298* Intercepts calls to a {@code Map} to check that {@code setUpRan} is true when they happen. Then
299* forwards the calls to the underlying {@code Map}.
300*/
301private static class CheckSetUpInvocationHandler implements InvocationHandler, Serializable {302private final Map<String, String> map;303private final AtomicBoolean setUpRan;304
305CheckSetUpInvocationHandler(Map<String, String> map, AtomicBoolean setUpRan) {306this.map = map;307this.setUpRan = setUpRan;308}309
310@Override311public Object invoke(Object target, Method method, Object[] args) throws Throwable {312assertTrue("setUp should have run", setUpRan.get());313try {314return method.invoke(map, args);315} catch (InvocationTargetException e) {316throw e.getCause();317} catch (IllegalAccessException e) {318throw newLinkageError(e);319}320}321}322
323/** Verifies that {@code setUp} and {@code tearDown} are called in all map test cases. */324private static Test testsForSetUpTearDown() {325final AtomicBoolean setUpRan = new AtomicBoolean();326Runnable setUp =327new Runnable() {328@Override329public void run() {330assertFalse("previous tearDown should have run before setUp", setUpRan.getAndSet(true));331}332};333Runnable tearDown =334new Runnable() {335@Override336public void run() {337assertTrue("setUp should have run", setUpRan.getAndSet(false));338}339};340return MapTestSuiteBuilder.using(new CheckSetUpHashMapGenerator(setUpRan))341.named("setUpTearDown")342.withFeatures(343MapFeature.GENERAL_PURPOSE,344MapFeature.ALLOWS_NULL_KEYS,345MapFeature.ALLOWS_NULL_VALUES,346CollectionFeature.SERIALIZABLE,347CollectionFeature.SUPPORTS_ITERATOR_REMOVE,348CollectionSize.ANY)349.withSetUp(setUp)350.withTearDown(tearDown)351.createTestSuite();352}353
354private static LinkageError newLinkageError(Throwable cause) {355LinkageError error = new LinkageError(cause.toString());356error.initCause(cause);357return error;358}359}
360