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