001/* 002 * Copyright (C) 2011 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 017package com.google.common.collect.testing.google; 018 019import static com.google.common.collect.testing.features.CollectionFeature.KNOWN_ORDER; 020import static com.google.common.collect.testing.features.CollectionFeature.RESTRICTS_ELEMENTS; 021import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE; 022import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS; 023 024import com.google.common.annotations.GwtIncompatible; 025import com.google.common.collect.BoundType; 026import com.google.common.collect.ImmutableList; 027import com.google.common.collect.Lists; 028import com.google.common.collect.Multiset; 029import com.google.common.collect.SortedMultiset; 030import com.google.common.collect.testing.AbstractTester; 031import com.google.common.collect.testing.FeatureSpecificTestSuiteBuilder; 032import com.google.common.collect.testing.Helpers; 033import com.google.common.collect.testing.OneSizeTestContainerGenerator; 034import com.google.common.collect.testing.SampleElements; 035import com.google.common.collect.testing.SetTestSuiteBuilder; 036import com.google.common.collect.testing.features.Feature; 037import com.google.common.testing.SerializableTester; 038import java.util.ArrayList; 039import java.util.Arrays; 040import java.util.Collection; 041import java.util.Collections; 042import java.util.Comparator; 043import java.util.HashSet; 044import java.util.List; 045import java.util.Set; 046import junit.framework.TestSuite; 047 048/** 049 * Creates, based on your criteria, a JUnit test suite that exhaustively tests a {@code 050 * SortedMultiset} implementation. 051 * 052 * <p><b>Warning:</b> expects that {@code E} is a String. 053 * 054 * @author Louis Wasserman 055 */ 056@GwtIncompatible 057public class SortedMultisetTestSuiteBuilder<E> extends MultisetTestSuiteBuilder<E> { 058 public static <E> SortedMultisetTestSuiteBuilder<E> using(TestMultisetGenerator<E> generator) { 059 SortedMultisetTestSuiteBuilder<E> result = new SortedMultisetTestSuiteBuilder<E>(); 060 result.usingGenerator(generator); 061 return result; 062 } 063 064 @Override 065 public TestSuite createTestSuite() { 066 withFeatures(KNOWN_ORDER); 067 TestSuite suite = super.createTestSuite(); 068 for (TestSuite subSuite : createDerivedSuites(this)) { 069 suite.addTest(subSuite); 070 } 071 return suite; 072 } 073 074 @Override 075 protected List<Class<? extends AbstractTester>> getTesters() { 076 List<Class<? extends AbstractTester>> testers = Helpers.copyToList(super.getTesters()); 077 testers.add(MultisetNavigationTester.class); 078 return testers; 079 } 080 081 @Override 082 TestSuite createElementSetTestSuite( 083 FeatureSpecificTestSuiteBuilder<?, ? extends OneSizeTestContainerGenerator<Collection<E>, E>> 084 parentBuilder) { 085 // TODO(lowasser): make a SortedElementSetGenerator 086 return SetTestSuiteBuilder.using( 087 new ElementSetGenerator<E>(parentBuilder.getSubjectGenerator())) 088 .named(getName() + ".elementSet") 089 .withFeatures(computeElementSetFeatures(parentBuilder.getFeatures())) 090 .suppressing(parentBuilder.getSuppressedTests()) 091 .createTestSuite(); 092 } 093 094 /** 095 * To avoid infinite recursion, test suites with these marker features won't have derived suites 096 * created for them. 097 */ 098 enum NoRecurse implements Feature<Void> { 099 SUBMULTISET, 100 DESCENDING; 101 102 @Override 103 public Set<Feature<? super Void>> getImpliedFeatures() { 104 return Collections.emptySet(); 105 } 106 } 107 108 /** Two bounds (from and to) define how to build a subMultiset. */ 109 enum Bound { 110 INCLUSIVE, 111 EXCLUSIVE, 112 NO_BOUND; 113 } 114 115 List<TestSuite> createDerivedSuites(SortedMultisetTestSuiteBuilder<E> parentBuilder) { 116 List<TestSuite> derivedSuites = Lists.newArrayList(); 117 118 if (!parentBuilder.getFeatures().contains(NoRecurse.DESCENDING)) { 119 derivedSuites.add(createDescendingSuite(parentBuilder)); 120 } 121 122 if (parentBuilder.getFeatures().contains(SERIALIZABLE)) { 123 derivedSuites.add(createReserializedSuite(parentBuilder)); 124 } 125 126 if (!parentBuilder.getFeatures().contains(NoRecurse.SUBMULTISET)) { 127 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.NO_BOUND, Bound.EXCLUSIVE)); 128 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.NO_BOUND, Bound.INCLUSIVE)); 129 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.EXCLUSIVE, Bound.NO_BOUND)); 130 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.EXCLUSIVE, Bound.EXCLUSIVE)); 131 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.EXCLUSIVE, Bound.INCLUSIVE)); 132 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.INCLUSIVE, Bound.NO_BOUND)); 133 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.INCLUSIVE, Bound.EXCLUSIVE)); 134 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.INCLUSIVE, Bound.INCLUSIVE)); 135 } 136 137 return derivedSuites; 138 } 139 140 private TestSuite createSubMultisetSuite( 141 SortedMultisetTestSuiteBuilder<E> parentBuilder, final Bound from, final Bound to) { 142 final TestMultisetGenerator<E> delegate = 143 (TestMultisetGenerator<E>) parentBuilder.getSubjectGenerator(); 144 145 Set<Feature<?>> features = new HashSet<>(); 146 features.add(NoRecurse.SUBMULTISET); 147 features.add(RESTRICTS_ELEMENTS); 148 features.addAll(parentBuilder.getFeatures()); 149 150 if (!features.remove(SERIALIZABLE_INCLUDING_VIEWS)) { 151 features.remove(SERIALIZABLE); 152 } 153 154 SortedMultiset<E> emptyMultiset = (SortedMultiset<E>) delegate.create(); 155 final Comparator<? super E> comparator = emptyMultiset.comparator(); 156 SampleElements<E> samples = delegate.samples(); 157 @SuppressWarnings("unchecked") 158 List<E> samplesList = 159 Arrays.asList(samples.e0(), samples.e1(), samples.e2(), samples.e3(), samples.e4()); 160 161 Collections.sort(samplesList, comparator); 162 final E firstInclusive = samplesList.get(0); 163 final E lastInclusive = samplesList.get(samplesList.size() - 1); 164 165 return SortedMultisetTestSuiteBuilder.using( 166 new ForwardingTestMultisetGenerator<E>(delegate) { 167 @Override 168 public SortedMultiset<E> create(Object... entries) { 169 @SuppressWarnings("unchecked") 170 // we dangerously assume E is a string 171 List<E> extremeValues = (List) getExtremeValues(); 172 @SuppressWarnings("unchecked") 173 // map generators must past entry objects 174 List<E> normalValues = (List) Arrays.asList(entries); 175 176 // prepare extreme values to be filtered out of view 177 Collections.sort(extremeValues, comparator); 178 E firstExclusive = extremeValues.get(1); 179 E lastExclusive = extremeValues.get(2); 180 if (from == Bound.NO_BOUND) { 181 extremeValues.remove(0); 182 extremeValues.remove(0); 183 } 184 if (to == Bound.NO_BOUND) { 185 extremeValues.remove(extremeValues.size() - 1); 186 extremeValues.remove(extremeValues.size() - 1); 187 } 188 189 // the regular values should be visible after filtering 190 List<E> allEntries = new ArrayList<E>(); 191 allEntries.addAll(extremeValues); 192 allEntries.addAll(normalValues); 193 SortedMultiset<E> multiset = 194 (SortedMultiset<E>) delegate.create(allEntries.toArray()); 195 196 // call the smallest subMap overload that filters out the extreme 197 // values 198 if (from == Bound.INCLUSIVE) { 199 multiset = multiset.tailMultiset(firstInclusive, BoundType.CLOSED); 200 } else if (from == Bound.EXCLUSIVE) { 201 multiset = multiset.tailMultiset(firstExclusive, BoundType.OPEN); 202 } 203 204 if (to == Bound.INCLUSIVE) { 205 multiset = multiset.headMultiset(lastInclusive, BoundType.CLOSED); 206 } else if (to == Bound.EXCLUSIVE) { 207 multiset = multiset.headMultiset(lastExclusive, BoundType.OPEN); 208 } 209 210 return multiset; 211 } 212 }) 213 .named(parentBuilder.getName() + " subMultiset " + from + "-" + to) 214 .withFeatures(features) 215 .suppressing(parentBuilder.getSuppressedTests()) 216 .createTestSuite(); 217 } 218 219 /** 220 * Returns an array of four bogus elements that will always be too high or too low for the 221 * display. This includes two values for each extreme. 222 * 223 * <p>This method (dangerously) assume that the strings {@code "!! a"} and {@code "~~ z"} will 224 * work for this purpose, which may cause problems for navigable maps with non-string or unicode 225 * generators. 226 */ 227 private List<String> getExtremeValues() { 228 List<String> result = new ArrayList<>(); 229 result.add("!! a"); 230 result.add("!! b"); 231 result.add("~~ y"); 232 result.add("~~ z"); 233 return result; 234 } 235 236 private TestSuite createDescendingSuite(SortedMultisetTestSuiteBuilder<E> parentBuilder) { 237 final TestMultisetGenerator<E> delegate = 238 (TestMultisetGenerator<E>) parentBuilder.getSubjectGenerator(); 239 240 Set<Feature<?>> features = new HashSet<>(); 241 features.add(NoRecurse.DESCENDING); 242 features.addAll(parentBuilder.getFeatures()); 243 if (!features.remove(SERIALIZABLE_INCLUDING_VIEWS)) { 244 features.remove(SERIALIZABLE); 245 } 246 247 return SortedMultisetTestSuiteBuilder.using( 248 new ForwardingTestMultisetGenerator<E>(delegate) { 249 @Override 250 public SortedMultiset<E> create(Object... entries) { 251 return ((SortedMultiset<E>) super.create(entries)).descendingMultiset(); 252 } 253 254 @Override 255 public Iterable<E> order(List<E> insertionOrder) { 256 return ImmutableList.copyOf(super.order(insertionOrder)).reverse(); 257 } 258 }) 259 .named(parentBuilder.getName() + " descending") 260 .withFeatures(features) 261 .suppressing(parentBuilder.getSuppressedTests()) 262 .createTestSuite(); 263 } 264 265 private TestSuite createReserializedSuite(SortedMultisetTestSuiteBuilder<E> parentBuilder) { 266 final TestMultisetGenerator<E> delegate = 267 (TestMultisetGenerator<E>) parentBuilder.getSubjectGenerator(); 268 269 Set<Feature<?>> features = new HashSet<>(); 270 features.addAll(parentBuilder.getFeatures()); 271 features.remove(SERIALIZABLE); 272 features.remove(SERIALIZABLE_INCLUDING_VIEWS); 273 274 return SortedMultisetTestSuiteBuilder.using( 275 new ForwardingTestMultisetGenerator<E>(delegate) { 276 @Override 277 public SortedMultiset<E> create(Object... entries) { 278 return SerializableTester.reserialize(((SortedMultiset<E>) super.create(entries))); 279 } 280 }) 281 .named(parentBuilder.getName() + " reserialized") 282 .withFeatures(features) 283 .suppressing(parentBuilder.getSuppressedTests()) 284 .createTestSuite(); 285 } 286 287 private static class ForwardingTestMultisetGenerator<E> implements TestMultisetGenerator<E> { 288 private final TestMultisetGenerator<E> delegate; 289 290 ForwardingTestMultisetGenerator(TestMultisetGenerator<E> delegate) { 291 this.delegate = delegate; 292 } 293 294 @Override 295 public SampleElements<E> samples() { 296 return delegate.samples(); 297 } 298 299 @Override 300 public E[] createArray(int length) { 301 return delegate.createArray(length); 302 } 303 304 @Override 305 public Iterable<E> order(List<E> insertionOrder) { 306 return delegate.order(insertionOrder); 307 } 308 309 @Override 310 public Multiset<E> create(Object... elements) { 311 return delegate.create(elements); 312 } 313 } 314}