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}