145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin/* 245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * Copyright (C) 2016 The Android Open Source Project 345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * 445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * Licensed under the Apache License, Version 2.0 (the "License"); 545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * you may not use this file except in compliance with the License. 645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * You may obtain a copy of the License at 745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * 845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * http://www.apache.org/licenses/LICENSE-2.0 945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * 1045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * Unless required by applicable law or agreed to in writing, software 1145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * distributed under the License is distributed on an "AS IS" BASIS, 1245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * See the License for the specific language governing permissions and 1445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * limitations under the License. 1545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin */ 1645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 1745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinpackage vogar.target; 1845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 1945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport com.google.common.base.Function; 2045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport com.google.common.base.Functions; 2145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport java.util.List; 2245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport java.util.Properties; 2345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport java.util.concurrent.atomic.AtomicInteger; 2445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport org.junit.After; 2545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport org.junit.Before; 2645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport org.junit.Rule; 2745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport vogar.Result; 28527980476e5d16cc0eee3ea3ca585be2776e8762Paul Duffinimport vogar.target.junit.JUnitUtils; 2945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport vogar.testing.InterceptOutputStreams; 3045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport vogar.testing.InterceptOutputStreams.Stream; 3145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 3245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport static org.junit.Assert.assertEquals; 3345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinimport static org.junit.Assert.assertTrue; 3445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 3545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin/** 3645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * Provides support for testing {@link TestRunner} class. 3745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * 3845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * <p>Subclasses provide the individual test methods, each test method has the following structure. 3945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * 4045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * <p>It is annotated with {@link TestRunnerProperties @TestRunnerProperties} that specifies the 4145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * properties that the main Vogar process supplies to the client process that actually runs the 4245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * tests. It must specify either a {@link TestRunnerProperties#testClass()} or 4345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * {@link TestRunnerProperties#testClassOrPackage()}, all the remaining properties are optional. 4445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * 4545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * <p>It calls {@code testRunnerRule.createTestRunner(...)} to create a {@link TestRunner}; passing 4645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * in any additional command line arguments that {@link TestRunner#TestRunner(Properties, List)} 4745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * accepts. 4845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * 4945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * <p>It calls {@link TestRunner#run()} to actually run the tests. 5045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * 5145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * <p>It calls {@link #expectedResults()} to obtain a {@link ExpectedResults} instance that it uses 5245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * to specify the expected results for the test. Once it has specified the expected results then it 5345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * must call either {@link ExpectedResults#completedNormally()} or 5445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * {@link ExpectedResults#aborted()}. They indicate whether the test process completed normally or 5545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * would abort (due to a test timing out) and cause the actual results to be checked against the 5645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * expected results. 5745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin */ 5845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffinpublic abstract class AbstractTestRunnerTest { 5945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 6045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin @Rule 6145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public InterceptOutputStreams ios = new InterceptOutputStreams(Stream.OUT); 6245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 6345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin @Rule 6445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public TestRunnerRule testRunnerRule = new TestRunnerRule(); 6545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 6645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin /** 6745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * Keeps track of number of times {@link #expectedResults()} has been called without 6845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * {@link ExpectedResults#checkFilteredOutput(String)} 6945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin * also being called. If it is {@code > 0} then the test is in error. 7045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin */ 7145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin private AtomicInteger checkCount; 7245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 7345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin @Before 7445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public void beforeTest() { 7545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin checkCount = new AtomicInteger(); 7645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 7745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 7845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin @After 7945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public void afterTest() { 8045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin if (checkCount.get() != 0) { 8145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin throw new IllegalStateException("Test called expectedResults() but failed to call" 8245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin + "either aborted() or completedNormally()"); 8345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 8445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 8545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 8645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin protected ExpectedResults expectedResults() { 8745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin checkCount.incrementAndGet(); 8845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return new ExpectedResults(testRunnerRule.testClass(), ios, 8945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin checkCount); 9045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 9145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 9245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin protected static class ExpectedResults { 9345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 9445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin private final StringBuilder builder = new StringBuilder(); 9545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin private final InterceptOutputStreams ios; 9645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin private final AtomicInteger checkCount; 9745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin private String testClassName; 9845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin private Function<String, String> filter; 9945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 10045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin private ExpectedResults( 10145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin Class<?> testClass, InterceptOutputStreams ios, AtomicInteger checkCount) { 10245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin this.testClassName = testClass.getName(); 10345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin this.checkCount = checkCount; 10445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin // Automatically strip out methods from a stack trace to avoid making tests dependent 10545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin // on either the call hierarchy or on source line numbers which would make the tests 10645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin // incredibly fragile. If a test fails then the unfiltered output containing the full 10745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin // stack trace will be output so this will not lose information needed to debug errors. 10845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin filter = new Function<String, String>() { 10945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin @Override 11045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public String apply(String input) { 11145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin // Remove stack trace from output. 11245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return input.replaceAll("\\t(at[^\\n]+|\\.\\.\\. [0-9]+ more)\\n", ""); 11345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 11445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin }; 11545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin this.ios = ios; 11645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 11745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 11845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public ExpectedResults text(String message) { 11945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin builder.append(message); 12045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return this; 12145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 12245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 12345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin private ExpectedResults addFilter(Function<String, String> function) { 12445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin filter = Functions.compose(filter, function); 12545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return this; 12645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 12745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 12845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public ExpectedResults ensureProfilingWasRequested() { 12945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return addFilter(new Function<String, String>() { 13045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin @Override 13145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public String apply(String input) { 13245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin // Make sure that profiling is requested (even though it's not supported). 13345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin assertTrue("Profiling was not requested", 13445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin input.startsWith("Profiling is disabled: ")); 13545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 13645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin input = input.replaceAll("^Profiling is disabled:[^\n]+\\n", ""); 13745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return input; 13845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 13945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin }); 14045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 14145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 14245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public ExpectedResults forTestClass(Class<?> testClass) { 14345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin this.testClassName = testClass.getName(); 14445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return this; 14545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 14645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 14745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public ExpectedResults forTestClass(String testClassName) { 14845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin this.testClassName = testClassName; 14945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return this; 15045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 15145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 15245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public ExpectedResults failure(String methodName, String message) { 15345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin String output = outcome(testClassName, methodName, message, Result.EXEC_FAILED); 15445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return text(output); 15545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 15645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 15745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public ExpectedResults success(String methodName) { 15845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin String output = outcome(testClassName, methodName, null, Result.SUCCESS); 15945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return text(output); 16045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 16145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 16245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public ExpectedResults success(String methodName, String message) { 16345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin String output = outcome(testClassName, methodName, message, Result.SUCCESS); 16445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return text(output); 16545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 16645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 16745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 16845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public ExpectedResults unsupported() { 16945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin String output = outcome( 17045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin testClassName, null, 17145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin "Skipping " + testClassName + ": no associated runner class\n", 17245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin Result.UNSUPPORTED); 17345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return text(output); 17445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 17545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 17645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public ExpectedResults noRunner() { 17745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin String message = 17845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin String.format("Skipping %s: no associated runner class\n", testClassName); 17945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin String output = outcome(testClassName, null, message, Result.UNSUPPORTED); 18045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return text(output); 18145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 18245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 18345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public void completedNormally() { 18445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin text("//00xx{\"completedNormally\":true}\n"); 18545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin checkFilteredOutput(builder.toString()); 18645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 18745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 18845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin public void aborted() { 18945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin checkFilteredOutput(builder.toString()); 19045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 19145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 19245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 19345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin private static String outcome( 19445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin String testClassName, String methodName, String message, Result result) { 195527980476e5d16cc0eee3ea3ca585be2776e8762Paul Duffin String testName = JUnitUtils.getTestName(testClassName, methodName); 19645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 19745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin return String.format("//00xx{\"outcome\":\"%s\"}\n" 19845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin + "%s" 19945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin + "//00xx{\"result\":\"%s\"}\n", 20045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin testName, message == null ? "" : message, result); 20145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 20245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin 20345b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin private void checkFilteredOutput(String expected) { 20445b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin checkCount.decrementAndGet(); 20545b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin String output = ios.contents(Stream.OUT); 20645b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin String filtered = filter.apply(output); 20745b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin if (!expected.equals(filtered)) { 20845b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin assertEquals(expected, output); 20945b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 21045b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 21145b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin } 21245b3ffb2d3d0c310718926c1acc69f5bf946ee3fPaul Duffin} 213