Coverage Summary for Class: FeatureSpecificTestSuiteBuilder (com.google.common.collect.testing)

Class Class, % Method, % Line, %
FeatureSpecificTestSuiteBuilder 100% (1/1) 96% (24/25) 79.8% (91/114)


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  17 package com.google.common.collect.testing; 18  19 import static java.util.Collections.disjoint; 20 import static java.util.logging.Level.FINER; 21  22 import com.google.common.annotations.GwtIncompatible; 23 import com.google.common.collect.testing.features.ConflictingRequirementsException; 24 import com.google.common.collect.testing.features.Feature; 25 import com.google.common.collect.testing.features.FeatureUtil; 26 import com.google.common.collect.testing.features.TesterRequirements; 27 import java.lang.reflect.Method; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Collection; 31 import java.util.Collections; 32 import java.util.Enumeration; 33 import java.util.HashSet; 34 import java.util.LinkedHashSet; 35 import java.util.List; 36 import java.util.Set; 37 import java.util.logging.Logger; 38 import junit.framework.Test; 39 import junit.framework.TestCase; 40 import junit.framework.TestSuite; 41  42 /** 43  * Creates, based on your criteria, a JUnit test suite that exhaustively tests the object generated 44  * by a G, selecting appropriate tests by matching them against specified features. 45  * 46  * @param <B> The concrete type of this builder (the 'self-type'). All the Builder methods of this 47  * class (such as {@link #named}) return this type, so that Builder methods of more derived 48  * classes can be chained onto them without casting. 49  * @param <G> The type of the generator to be passed to testers in the generated test suite. An 50  * instance of G should somehow provide an instance of the class under test, plus any other 51  * information required to parameterize the test. 52  * @author George van den Driessche 53  */ 54 @GwtIncompatible 55 public abstract class FeatureSpecificTestSuiteBuilder< 56  B extends FeatureSpecificTestSuiteBuilder<B, G>, G> { 57  @SuppressWarnings("unchecked") 58  protected B self() { 59  return (B) this; 60  } 61  62  // Test Data 63  64  private G subjectGenerator; 65  // Gets run before every test. 66  private Runnable setUp; 67  // Gets run at the conclusion of every test. 68  private Runnable tearDown; 69  70  protected B usingGenerator(G subjectGenerator) { 71  this.subjectGenerator = subjectGenerator; 72  return self(); 73  } 74  75  public G getSubjectGenerator() { 76  return subjectGenerator; 77  } 78  79  public B withSetUp(Runnable setUp) { 80  this.setUp = setUp; 81  return self(); 82  } 83  84  protected Runnable getSetUp() { 85  return setUp; 86  } 87  88  public B withTearDown(Runnable tearDown) { 89  this.tearDown = tearDown; 90  return self(); 91  } 92  93  protected Runnable getTearDown() { 94  return tearDown; 95  } 96  97  // Features 98  99  private Set<Feature<?>> features = new LinkedHashSet<>(); 100  101  /** 102  * Configures this builder to produce tests appropriate for the given features. This method may be 103  * called more than once to add features in multiple groups. 104  */ 105  public B withFeatures(Feature<?>... features) { 106  return withFeatures(Arrays.asList(features)); 107  } 108  109  public B withFeatures(Iterable<? extends Feature<?>> features) { 110  for (Feature<?> feature : features) { 111  this.features.add(feature); 112  } 113  return self(); 114  } 115  116  public Set<Feature<?>> getFeatures() { 117  return Collections.unmodifiableSet(features); 118  } 119  120  // Name 121  122  private String name; 123  124  /** Configures this builder produce a TestSuite with the given name. */ 125  public B named(String name) { 126  if (name.contains("(")) { 127  throw new IllegalArgumentException( 128  "Eclipse hides all characters after " 129  + "'('; please use '[]' or other characters instead of parentheses"); 130  } 131  this.name = name; 132  return self(); 133  } 134  135  public String getName() { 136  return name; 137  } 138  139  // Test suppression 140  141  private Set<Method> suppressedTests = new HashSet<>(); 142  143  /** 144  * Prevents the given methods from being run as part of the test suite. 145  * 146  * <p><em>Note:</em> in principle this should never need to be used, but it might be useful if the 147  * semantics of an implementation disagree in unforeseen ways with the semantics expected by a 148  * test, or to keep dependent builds clean in spite of an erroneous test. 149  */ 150  public B suppressing(Method... methods) { 151  return suppressing(Arrays.asList(methods)); 152  } 153  154  public B suppressing(Collection<Method> methods) { 155  suppressedTests.addAll(methods); 156  return self(); 157  } 158  159  public Set<Method> getSuppressedTests() { 160  return suppressedTests; 161  } 162  163  private static final Logger logger = 164  Logger.getLogger(FeatureSpecificTestSuiteBuilder.class.getName()); 165  166  /** Creates a runnable JUnit test suite based on the criteria already given. */ 167  /* 168  * Class parameters must be raw. This annotation should go on testerClass in 169  * the for loop, but the 1.5 javac crashes on annotations in for loops: 170  * <http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6294589> 171  */ 172  @SuppressWarnings("unchecked") 173  public TestSuite createTestSuite() { 174  checkCanCreate(); 175  176  logger.fine(" Testing: " + name); 177  logger.fine("Features: " + formatFeatureSet(features)); 178  179  FeatureUtil.addImpliedFeatures(features); 180  181  logger.fine("Expanded: " + formatFeatureSet(features)); 182  183  // Class parameters must be raw. 184  List<Class<? extends AbstractTester>> testers = getTesters(); 185  186  TestSuite suite = new TestSuite(name); 187  for (Class<? extends AbstractTester> testerClass : testers) { 188  final TestSuite testerSuite = 189  makeSuiteForTesterClass((Class<? extends AbstractTester<?>>) testerClass); 190  if (testerSuite.countTestCases() > 0) { 191  suite.addTest(testerSuite); 192  } 193  } 194  return suite; 195  } 196  197  /** Throw {@link IllegalStateException} if {@link #createTestSuite()} can't be called yet. */ 198  protected void checkCanCreate() { 199  if (subjectGenerator == null) { 200  throw new IllegalStateException("Call using() before createTestSuite()."); 201  } 202  if (name == null) { 203  throw new IllegalStateException("Call named() before createTestSuite()."); 204  } 205  if (features == null) { 206  throw new IllegalStateException("Call withFeatures() before createTestSuite()."); 207  } 208  } 209  210  // Class parameters must be raw. 211  protected abstract List<Class<? extends AbstractTester>> getTesters(); 212  213  private boolean matches(Test test) { 214  final Method method; 215  try { 216  method = extractMethod(test); 217  } catch (IllegalArgumentException e) { 218  logger.finer(Platform.format("%s: including by default: %s", test, e.getMessage())); 219  return true; 220  } 221  if (suppressedTests.contains(method)) { 222  logger.finer(Platform.format("%s: excluding because it was explicitly suppressed.", test)); 223  return false; 224  } 225  final TesterRequirements requirements; 226  try { 227  requirements = FeatureUtil.getTesterRequirements(method); 228  } catch (ConflictingRequirementsException e) { 229  throw new RuntimeException(e); 230  } 231  if (!features.containsAll(requirements.getPresentFeatures())) { 232  if (logger.isLoggable(FINER)) { 233  Set<Feature<?>> missingFeatures = Helpers.copyToSet(requirements.getPresentFeatures()); 234  missingFeatures.removeAll(features); 235  logger.finer( 236  Platform.format( 237  "%s: skipping because these features are absent: %s", method, missingFeatures)); 238  } 239  return false; 240  } 241  if (intersect(features, requirements.getAbsentFeatures())) { 242  if (logger.isLoggable(FINER)) { 243  Set<Feature<?>> unwantedFeatures = Helpers.copyToSet(requirements.getAbsentFeatures()); 244  unwantedFeatures.retainAll(features); 245  logger.finer( 246  Platform.format( 247  "%s: skipping because these features are present: %s", method, unwantedFeatures)); 248  } 249  return false; 250  } 251  return true; 252  } 253  254  private static boolean intersect(Set<?> a, Set<?> b) { 255  return !disjoint(a, b); 256  } 257  258  private static Method extractMethod(Test test) { 259  if (test instanceof AbstractTester) { 260  AbstractTester<?> tester = (AbstractTester<?>) test; 261  return Helpers.getMethod(tester.getClass(), tester.getTestMethodName()); 262  } else if (test instanceof TestCase) { 263  TestCase testCase = (TestCase) test; 264  return Helpers.getMethod(testCase.getClass(), testCase.getName()); 265  } else { 266  throw new IllegalArgumentException("unable to extract method from test: not a TestCase."); 267  } 268  } 269  270  protected TestSuite makeSuiteForTesterClass(Class<? extends AbstractTester<?>> testerClass) { 271  final TestSuite candidateTests = new TestSuite(testerClass); 272  final TestSuite suite = filterSuite(candidateTests); 273  274  Enumeration<?> allTests = suite.tests(); 275  while (allTests.hasMoreElements()) { 276  Object test = allTests.nextElement(); 277  if (test instanceof AbstractTester) { 278  @SuppressWarnings("unchecked") 279  AbstractTester<? super G> tester = (AbstractTester<? super G>) test; 280  tester.init(subjectGenerator, name, setUp, tearDown); 281  } 282  } 283  284  return suite; 285  } 286  287  private TestSuite filterSuite(TestSuite suite) { 288  TestSuite filtered = new TestSuite(suite.getName()); 289  final Enumeration<?> tests = suite.tests(); 290  while (tests.hasMoreElements()) { 291  Test test = (Test) tests.nextElement(); 292  if (matches(test)) { 293  filtered.addTest(test); 294  } 295  } 296  return filtered; 297  } 298  299  protected static String formatFeatureSet(Set<? extends Feature<?>> features) { 300  List<String> temp = new ArrayList<>(); 301  for (Feature<?> feature : features) { 302  Object featureAsObject = feature; // to work around bogus JDK warning 303  if (featureAsObject instanceof Enum) { 304  Enum<?> f = (Enum<?>) featureAsObject; 305  temp.add(f.getDeclaringClass().getSimpleName() + "." + feature); 306  } else { 307  temp.add(feature.toString()); 308  } 309  } 310  return temp.toString(); 311  } 312 }