/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2013. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.bigdata.rdf.sail.webapp; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collections; import java.util.Enumeration; import java.util.Set; import java.util.regex.Pattern; import junit.extensions.TestSetup; import junit.extensions.proxy.ProxyTestSuite; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import com.bigdata.journal.BufferMode; /** * This class provides static methods to help creating * test classes and suites of tests that use the proxy test * approach. For creating test classes use {@link #suiteWhenStandalone(Class, String, TestMode...)}, * when creating test suites use {@link #suiteWithOptionalProxy(String, TestMode...)} * <p> * The intent is to enable the developer in eclipse to run JUnit tests * from a test file or a test suite file, while still allowing that same file * to be included unchanged in the main test suite. The methods defined here * hence provide a default behavior in the case that the {@link TestNanoSparqlServerWithProxyIndexManager} * has already loaded before this class. * @author jeremycarroll * */ public class ProxySuiteHelper { private static class CloningTestSuite extends ProxyTestSuite { public CloningTestSuite(Test delegate, String name) { super(delegate, name); } @Override public void addTest(Test test) { super.addTest(cloneTest(getDelegate(),test)); } } private static class MultiModeTestSuite extends TestSuite { private final ProxyTestSuite subs[]; public MultiModeTestSuite(final String name, final Set<BufferMode> bufferModes, final TestMode ...testModes ) { super(name); if (bufferModes.isEmpty()) throw new IllegalArgumentException(); final int ntests = testModes.length * bufferModes.size(); subs = new ProxyTestSuite[ntests]; int i = 0; for( final BufferMode bufferMode : bufferModes) { for (final TestMode testMode: testModes) { final ProxyTestSuite suite2 = TestNanoSparqlServerWithProxyIndexManager.createProxyTestSuite(TestNanoSparqlServerWithProxyIndexManager.getTemporaryJournal(bufferMode),testMode); super.addTest(new TestSetup(suite2) { @Override protected void setUp() throws Exception { } @SuppressWarnings("rawtypes") @Override protected void tearDown() throws Exception { ((TestNanoSparqlServerWithProxyIndexManager)suite2.getDelegate()).tearDownAfterSuite(); /* * Note: Do not clear. Will not leak unless the * QueryEngine objects are pinned. They will not be * pinned if you shutdown the Journal correctly for each * test; the call to tearDownAfterSuite above calls the destroy() method * on temporary journals, which appears to do the necessary thing. */ // QueryEngineFactory.clearStandAloneQECacheDuringTesting(); } }); // suite2.setName(name + ", bufferMode=" + bufferMode.name() // + ", testMode=" + testMode.name()); subs[i++] = suite2; } } } @SuppressWarnings("rawtypes") @Override public void addTestSuite(final Class clazz) { for (final ProxyTestSuite s:subs) { s.addTestSuite(clazz); } } @Override public void addTest(final Test test) { for (final ProxyTestSuite s:subs) { s.addTest(cloneTest(s.getDelegate(),test)); } } } private static Test cloneTest(final Test delegate, final Test test) { if (test instanceof TestSuite) { return cloneSuite(delegate, (TestSuite)test); } if (test instanceof TestCase) { return cloneTestCase((TestCase)test); } throw new IllegalArgumentException("Cannot handle test of type: "+test.getClass().getName()); } private static Test cloneTestCase(final TestCase test) { return createTest(test.getClass(),test.getName()); } private static Test cloneSuite(final Test delegate, final TestSuite suite) { final TestSuite rslt = new CloningTestSuite(delegate,suite.getName()); final Enumeration<Test> enumerate = suite.tests(); while( enumerate.hasMoreElements() ) { rslt.addTest(enumerate.nextElement()); } return rslt; } /** * This variable tells us if the class {@link TestNanoSparqlServerWithProxyIndexManager} * (or potentially a similar class that sets this variable) * has loaded. This information is used by {@link #suiteWhenStandalone(Class, String, TestMode...)} * to decide whether to operate in stand-alone fashion, or to default to participating * in a larger test suite, managed by the proxy. */ static boolean proxyIndexManagerTestingHasStarted = false; /** * Call this method to create local testing using one or more proxies. * e.g. right clicking in eclipse and running JUnit tests works. * Also using this within a TestSuite also works. * * * @param clazz The clazz to be tested, i.e. the calling class * @param regex Matched against the test names to decide which tests to run. Should usually start in "test.*" * @param bufferMode The {@link BufferMode}(s) to be tested. * @param testModes One or more TestModes. * @return */ public static TestSuite suiteWhenStandalone(final Class<? extends TestCase> clazz, final String regex, final Set<BufferMode> bufferModes, final TestMode ... testModes) { if (!proxyIndexManagerTestingHasStarted) { final Pattern pat = Pattern.compile(regex); proxyIndexManagerTestingHasStarted = true; final TestSuite suite = new MultiModeTestSuite(clazz.getName(),bufferModes, testModes); addMatchingTestsFromClass(suite, clazz, pat); return suite; } else { return new TestSuite(clazz); } } public static Test suiteWhenStandalone(final Class<? extends TestCase> clazz, final String regex, final TestMode ... testModes) { return suiteWhenStandalone(clazz, regex, Collections.singleton(BufferMode.Transient), testModes); } /** * Call this method to create a new test suite which can include * other test suites and tests using proxies. * Having created the test suite then the classes and tests and suites * are added in the usual way. * @param modes One or more TestModes. * @return */ public static TestSuite suiteWithOptionalProxy(final String name, final Set<BufferMode> bufferModes, final TestMode ... testMode) { if (!proxyIndexManagerTestingHasStarted) { proxyIndexManagerTestingHasStarted = true; return new MultiModeTestSuite(name,bufferModes,testMode); } else { return new TestSuite(name); } } public static TestSuite suiteWithOptionalProxy(final String name, final TestMode ... testMode) { return suiteWithOptionalProxy(name,Collections.singleton(BufferMode.Transient),testMode); } private static void addMatchingTestsFromClass(final TestSuite suite3, final Class<? extends TestCase> clazz, final Pattern pat) { for (final Method m:clazz.getMethods()) { if ( m.getParameterTypes().length==0 && pat.matcher(m.getName()).matches() ) { suite3.addTest(createTest(clazz,m.getName())); } } } private static Test createTest(final Class<? extends TestCase> clazz, final String name) { try { final Constructor<?> cons = TestSuite.getTestConstructor(clazz); if (cons.getParameterTypes().length == 1) { return (Test) cons.newInstance(name); } else { final TestCase test = (TestCase) cons.newInstance(); test.setName(name); return test; } } catch (final NoSuchMethodException e) { throw new RuntimeException("Failed to find constructor"); } catch (final InstantiationException e) { throw new RuntimeException(e); } catch (final IllegalAccessException e) { throw new RuntimeException(e); } catch (final IllegalArgumentException e) { throw new RuntimeException(e); } catch (final InvocationTargetException e) { throw new RuntimeException(e); } } }