1 /* 2 * Copyright (C) 2011 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.google.common.collect.testing.google; 18 19 import com.google.common.annotations.GwtCompatible; 20 import com.google.common.collect.BoundType; 21 import com.google.common.collect.ImmutableList; 22 import com.google.common.collect.Lists; 23 import com.google.common.collect.Multiset; 24 import com.google.common.collect.SortedMultiset; 25 import com.google.common.collect.testing.AbstractTester; 26 import com.google.common.collect.testing.Helpers; 27 import com.google.common.collect.testing.SampleElements; 28 import com.google.common.collect.testing.features.CollectionFeature; 29 import com.google.common.collect.testing.features.Feature; 30 31 import junit.framework.TestSuite; 32 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Collections; 36 import java.util.Comparator; 37 import java.util.List; 38 import java.util.Set; 39 40 /** 41 * Creates, based on your criteria, a JUnit test suite that exhaustively tests a 42 * {@code SortedMultiset} implementation. 43 * 44 * <p><b>Warning</b>: expects that {@code E} is a String. 45 * 46 * @author Louis Wasserman 47 */ 48 @GwtCompatible 49 public class SortedMultisetTestSuiteBuilder<E> extends 50 MultisetTestSuiteBuilder<E> { 51 public static <E> SortedMultisetTestSuiteBuilder<E> using( 52 TestMultisetGenerator<E> generator) { 53 SortedMultisetTestSuiteBuilder<E> result = 54 new SortedMultisetTestSuiteBuilder<E>(); 55 result.usingGenerator(generator); 56 return result; 57 } 58 59 @Override 60 public TestSuite createTestSuite() { 61 TestSuite suite = super.createTestSuite(); 62 for (TestSuite subSuite : createDerivedSuites(this)) { 63 suite.addTest(subSuite); 64 } 65 return suite; 66 } 67 68 @Override 69 protected List<Class<? extends AbstractTester>> getTesters() { 70 List<Class<? extends AbstractTester>> testers = 71 Helpers.copyToList(super.getTesters()); 72 testers.add(MultisetNavigationTester.class); 73 return testers; 74 } 75 76 /** 77 * To avoid infinite recursion, test suites with these marker features won't 78 * have derived suites created for them. 79 */ 80 enum NoRecurse implements Feature<Void> { 81 SUBMULTISET, DESCENDING; 82 83 @Override 84 public Set<Feature<? super Void>> getImpliedFeatures() { 85 return Collections.emptySet(); 86 } 87 } 88 89 /** 90 * Two bounds (from and to) define how to build a subMultiset. 91 */ 92 enum Bound { 93 INCLUSIVE, EXCLUSIVE, NO_BOUND; 94 } 95 96 List<TestSuite> createDerivedSuites( 97 SortedMultisetTestSuiteBuilder<E> parentBuilder) { 98 List<TestSuite> derivedSuites = Lists.newArrayList(); 99 100 if (!parentBuilder.getFeatures().contains(NoRecurse.DESCENDING)) { 101 derivedSuites.add(createDescendingSuite(parentBuilder)); 102 } 103 104 if (!parentBuilder.getFeatures().contains(NoRecurse.SUBMULTISET)) { 105 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.NO_BOUND, 106 Bound.EXCLUSIVE)); 107 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.NO_BOUND, 108 Bound.INCLUSIVE)); 109 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.EXCLUSIVE, 110 Bound.NO_BOUND)); 111 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.EXCLUSIVE, 112 Bound.EXCLUSIVE)); 113 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.EXCLUSIVE, 114 Bound.INCLUSIVE)); 115 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.INCLUSIVE, 116 Bound.NO_BOUND)); 117 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.INCLUSIVE, 118 Bound.EXCLUSIVE)); 119 derivedSuites.add(createSubMultisetSuite(parentBuilder, Bound.INCLUSIVE, 120 Bound.INCLUSIVE)); 121 } 122 123 return derivedSuites; 124 } 125 126 private TestSuite createSubMultisetSuite( 127 SortedMultisetTestSuiteBuilder<E> parentBuilder, final Bound from, 128 final Bound to) { 129 final TestMultisetGenerator<E> delegate = 130 (TestMultisetGenerator<E>) parentBuilder.getSubjectGenerator(); 131 132 List<Feature<?>> features = new ArrayList<Feature<?>>(); 133 features.add(NoRecurse.SUBMULTISET); 134 features.add(CollectionFeature.RESTRICTS_ELEMENTS); 135 features.addAll(parentBuilder.getFeatures()); 136 137 SortedMultiset<E> emptyMultiset = (SortedMultiset<E>) delegate.create(); 138 final Comparator<? super E> comparator = emptyMultiset.comparator(); 139 SampleElements<E> samples = delegate.samples(); 140 @SuppressWarnings("unchecked") 141 List<E> samplesList = 142 Arrays.asList(samples.e0, samples.e1, samples.e2, samples.e3, 143 samples.e4); 144 145 Collections.sort(samplesList, comparator); 146 final E firstInclusive = samplesList.get(0); 147 final E lastInclusive = samplesList.get(samplesList.size() - 1); 148 149 return SortedMultisetTestSuiteBuilder 150 .using(new ForwardingTestMultisetGenerator<E>(delegate) { 151 @Override 152 public SortedMultiset<E> create(Object... entries) { 153 @SuppressWarnings("unchecked") 154 // we dangerously assume E is a string 155 List<E> extremeValues = (List) getExtremeValues(); 156 @SuppressWarnings("unchecked") 157 // map generators must past entry objects 158 List<E> normalValues = (List) Arrays.asList(entries); 159 160 // prepare extreme values to be filtered out of view 161 Collections.sort(extremeValues, comparator); 162 E firstExclusive = extremeValues.get(1); 163 E lastExclusive = extremeValues.get(2); 164 if (from == Bound.NO_BOUND) { 165 extremeValues.remove(0); 166 extremeValues.remove(0); 167 } 168 if (to == Bound.NO_BOUND) { 169 extremeValues.remove(extremeValues.size() - 1); 170 extremeValues.remove(extremeValues.size() - 1); 171 } 172 173 // the regular values should be visible after filtering 174 List<E> allEntries = new ArrayList<E>(); 175 allEntries.addAll(extremeValues); 176 allEntries.addAll(normalValues); 177 SortedMultiset<E> multiset = 178 (SortedMultiset<E>) delegate.create(allEntries.toArray()); 179 180 // call the smallest subMap overload that filters out the extreme 181 // values 182 if (from == Bound.INCLUSIVE) { 183 multiset = 184 multiset.tailMultiset(firstInclusive, BoundType.CLOSED); 185 } else if (from == Bound.EXCLUSIVE) { 186 multiset = multiset.tailMultiset(firstExclusive, BoundType.OPEN); 187 } 188 189 if (to == Bound.INCLUSIVE) { 190 multiset = multiset.headMultiset(lastInclusive, BoundType.CLOSED); 191 } else if (to == Bound.EXCLUSIVE) { 192 multiset = multiset.headMultiset(lastExclusive, BoundType.OPEN); 193 } 194 195 return multiset; 196 } 197 }) 198 .named(parentBuilder.getName() + " subMultiset " + from + "-" + to) 199 .withFeatures(features) 200 .suppressing(parentBuilder.getSuppressedTests()) 201 .createTestSuite(); 202 } 203 204 /** 205 * Returns an array of four bogus elements that will always be too high or too 206 * low for the display. This includes two values for each extreme. 207 * 208 * <p> 209 * This method (dangerously) assume that the strings {@code "!! a"} and 210 * {@code "~~ z"} will work for this purpose, which may cause problems for 211 * navigable maps with non-string or unicode generators. 212 */ 213 private List<String> getExtremeValues() { 214 List<String> result = new ArrayList<String>(); 215 result.add("!! a"); 216 result.add("!! b"); 217 result.add("~~ y"); 218 result.add("~~ z"); 219 return result; 220 } 221 222 private TestSuite createDescendingSuite( 223 SortedMultisetTestSuiteBuilder<E> parentBuilder) { 224 final TestMultisetGenerator<E> delegate = 225 (TestMultisetGenerator<E>) parentBuilder.getSubjectGenerator(); 226 227 List<Feature<?>> features = new ArrayList<Feature<?>>(); 228 features.add(NoRecurse.DESCENDING); 229 features.addAll(parentBuilder.getFeatures()); 230 231 return SortedMultisetTestSuiteBuilder 232 .using(new ForwardingTestMultisetGenerator<E>(delegate) { 233 @Override 234 public SortedMultiset<E> create(Object... entries) { 235 return ((SortedMultiset<E>) super.create(entries)) 236 .descendingMultiset(); 237 } 238 239 @Override 240 public Iterable<E> order(List<E> insertionOrder) { 241 return ImmutableList.copyOf(super.order(insertionOrder)).reverse(); 242 } 243 }) 244 .named(parentBuilder.getName() + " descending") 245 .withFeatures(features) 246 .suppressing(parentBuilder.getSuppressedTests()) 247 .createTestSuite(); 248 } 249 250 private static class ForwardingTestMultisetGenerator<E> 251 implements TestMultisetGenerator<E> { 252 private final TestMultisetGenerator<E> delegate; 253 254 ForwardingTestMultisetGenerator(TestMultisetGenerator<E> delegate) { 255 this.delegate = delegate; 256 } 257 258 @Override 259 public SampleElements<E> samples() { 260 return delegate.samples(); 261 } 262 263 @Override 264 public E[] createArray(int length) { 265 return delegate.createArray(length); 266 } 267 268 @Override 269 public Iterable<E> order(List<E> insertionOrder) { 270 return delegate.order(insertionOrder); 271 } 272 273 @Override 274 public Multiset<E> create(Object... elements) { 275 return delegate.create(elements); 276 } 277 } 278 } 279