/* * Copyright (C) 2012-2015 DataStax Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.datastax.driver.core; import com.datastax.driver.core.CCMAccess.Workload; import com.datastax.driver.core.CreateCCM.TestMode; import com.datastax.driver.core.exceptions.InvalidQueryException; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.io.Closer; import com.google.common.util.concurrent.Uninterruptibles; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.ITestResult; import org.testng.annotations.*; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static com.datastax.driver.core.CreateCCM.TestMode.PER_CLASS; import static com.datastax.driver.core.CreateCCM.TestMode.PER_METHOD; import static com.datastax.driver.core.TestUtils.*; import static org.assertj.core.api.Assertions.fail; @SuppressWarnings("unused") public class CCMTestsSupport { private static final Logger LOGGER = LoggerFactory.getLogger(CCMTestsSupport.class); private static final AtomicInteger CCM_COUNTER = new AtomicInteger(1); private static final List<String> TEST_GROUPS = Lists.newArrayList("isolated", "short", "long", "stress", "duration"); // A mapping of cassandra.yaml config options to their version requirements. // If a config is passed containing one of these options and the version requirement cannot be met // the option is simply filtered. private static final Map<String, VersionNumber> configVersionRequirements = ImmutableMap.<String, VersionNumber>builder() .put("enable_user_defined_functions", VersionNumber.parse("2.2.0")) .build(); private static class ReadOnlyCCMAccess implements CCMAccess { private final CCMAccess delegate; private ReadOnlyCCMAccess(CCMAccess delegate) { this.delegate = delegate; } @Override public String getClusterName() { return delegate.getClusterName(); } @Override public VersionNumber getCassandraVersion() { return delegate.getCassandraVersion(); } @Override public VersionNumber getDSEVersion() { return delegate.getDSEVersion(); } @Override public InetSocketAddress addressOfNode(int n) { return delegate.addressOfNode(n); } @Override public File getCcmDir() { return delegate.getCcmDir(); } @Override public File getClusterDir() { return delegate.getClusterDir(); } @Override public File getNodeDir(int n) { return delegate.getNodeDir(n); } @Override public File getNodeConfDir(int n) { return delegate.getNodeConfDir(n); } @Override public int getStoragePort() { return delegate.getStoragePort(); } @Override public int getThriftPort() { return delegate.getThriftPort(); } @Override public int getBinaryPort() { return delegate.getBinaryPort(); } @Override public void setKeepLogs(boolean keepLogs) { delegate.setKeepLogs(keepLogs); } @Override public String checkForErrors() { return delegate.checkForErrors(); } @Override public void close() { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void start() { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void stop() { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void forceStop() { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void remove() { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void start(int n) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void stop(int n) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void forceStop(int n) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void pause(int n) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void resume(int n) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void remove(int n) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void add(int n) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void add(int dc, int n) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void decommission(int n) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void updateConfig(Map<String, Object> configs) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void updateDSEConfig(Map<String, Object> configs) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void updateNodeConfig(int n, String key, Object value) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void updateNodeConfig(int n, Map<String, Object> configs) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void updateDSENodeConfig(int n, String key, Object value) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void updateDSENodeConfig(int n, Map<String, Object> configs) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void setWorkload(int node, Workload... workload) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void waitForUp(int node) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public void waitForDown(int node) { throw new UnsupportedOperationException("This CCM cluster is read-only"); } @Override public ProtocolVersion getProtocolVersion() { return delegate.getProtocolVersion(); } @Override public ProtocolVersion getProtocolVersion(ProtocolVersion maximumAllowed) { return delegate.getProtocolVersion(maximumAllowed); } @Override public String toString() { return delegate.toString(); } } private static class CCMTestConfig { private final List<CCMConfig> annotations; private CCMBridge.Builder ccmBuilder; public CCMTestConfig(List<CCMConfig> annotations) { this.annotations = annotations; } private int[] numberOfNodes() { for (CCMConfig ann : annotations) { if (ann != null && ann.numberOfNodes().length > 0) return ann.numberOfNodes(); } return new int[]{1}; } @SuppressWarnings("SimplifiableIfStatement") private String version() { for (CCMConfig ann : annotations) { if (ann != null && !ann.version().isEmpty()) return ann.version(); } return null; } @SuppressWarnings("SimplifiableIfStatement") private Boolean dse() { for (CCMConfig ann : annotations) { if (ann != null && ann.dse().length > 0) return ann.dse()[0]; } return null; } @SuppressWarnings("SimplifiableIfStatement") private boolean ssl() { for (CCMConfig ann : annotations) { if (ann != null && ann.ssl().length > 0) return ann.ssl()[0]; } return false; } @SuppressWarnings("SimplifiableIfStatement") private boolean auth() { for (CCMConfig ann : annotations) { if (ann != null && ann.auth().length > 0) return ann.auth()[0]; } return false; } @SuppressWarnings("SimplifiableIfStatement") private Map<String, Object> config() { Map<String, Object> config = new HashMap<String, Object>(); for (int i = annotations.size() - 1; i >= 0; i--) { CCMConfig ann = annotations.get(i); addConfigOptions(ann.config(), config); } return config; } @SuppressWarnings("SimplifiableIfStatement") private Map<String, Object> dseConfig() { Map<String, Object> config = new HashMap<String, Object>(); for (int i = annotations.size() - 1; i >= 0; i--) { CCMConfig ann = annotations.get(i); addConfigOptions(ann.dseConfig(), config); } return config; } private void addConfigOptions(String[] conf, Map<String, Object> config) { VersionNumber version = VersionNumber.parse(version()); if (version == null) { version = CCMBridge.getGlobalCassandraVersion(); } else { Boolean dse = dse(); if (dse != null && dse) { version = CCMBridge.getCassandraVersion(version); } } for (String aConf : conf) { String[] tokens = aConf.split(":"); if (tokens.length != 2) fail("Wrong configuration option: " + aConf); String key = tokens[0]; String value = tokens[1]; // If we've detected a property with a version requirement, skip it if the version requirement // cannot be met. if (configVersionRequirements.containsKey(key)) { VersionNumber requirement = configVersionRequirements.get(key); if (version != null && version.compareTo(requirement) < 0) { LOGGER.debug("Skipping inclusion of '{}' in cassandra.yaml since it requires >= C* {} and {} " + "was detected.", aConf, requirement, version); continue; } } config.put(key, value); } } @SuppressWarnings("SimplifiableIfStatement") private Set<String> jvmArgs() { Set<String> args = new LinkedHashSet<String>(); for (int i = annotations.size() - 1; i >= 0; i--) { CCMConfig ann = annotations.get(i); Collections.addAll(args, ann.jvmArgs()); } return args; } @SuppressWarnings("SimplifiableIfStatement") private Set<String> startOptions() { Set<String> args = new LinkedHashSet<String>(); for (int i = annotations.size() - 1; i >= 0; i--) { CCMConfig ann = annotations.get(i); Collections.addAll(args, ann.options()); } return args; } private List<Workload[]> workloads() { int total = 0; for (int perDc : numberOfNodes()) total += perDc; List<Workload[]> workloads = new ArrayList<Workload[]>(Collections.<Workload[]>nCopies(total, null)); for (int i = annotations.size() - 1; i >= 0; i--) { CCMConfig ann = annotations.get(i); CCMWorkload[] annWorkloads = ann.workloads(); for (int j = 0; j < annWorkloads.length; j++) { CCMWorkload nodeWorkloads = annWorkloads[j]; workloads.set(j, nodeWorkloads.value()); } } return workloads; } @SuppressWarnings("SimplifiableIfStatement") private boolean createCcm() { for (CCMConfig ann : annotations) { if (ann != null && ann.createCcm().length > 0) return ann.createCcm()[0]; } return true; } @SuppressWarnings("SimplifiableIfStatement") private boolean createCluster() { for (CCMConfig ann : annotations) { if (ann != null && ann.createCluster().length > 0) return ann.createCluster()[0]; } return true; } @SuppressWarnings("SimplifiableIfStatement") private boolean createSession() { for (CCMConfig ann : annotations) { if (ann != null && ann.createSession().length > 0) return ann.createSession()[0]; } return true; } @SuppressWarnings("SimplifiableIfStatement") private boolean createKeyspace() { for (CCMConfig ann : annotations) { if (ann != null && ann.createKeyspace().length > 0) return ann.createKeyspace()[0]; } return true; } @SuppressWarnings("SimplifiableIfStatement") private boolean dirtiesContext() { for (CCMConfig ann : annotations) { if (ann != null && ann.dirtiesContext().length > 0) return ann.dirtiesContext()[0]; } return false; } private CCMBridge.Builder ccmBuilder(Object testInstance) throws Exception { if (ccmBuilder == null) { ccmBuilder = ccmProvider(testInstance); if (ccmBuilder == null) { ccmBuilder = CCMBridge.builder().withNodes(numberOfNodes()).notStarted(); } String versionStr = version(); if (versionStr != null) { VersionNumber version = VersionNumber.parse(versionStr); ccmBuilder.withVersion(version); } Boolean dse = dse(); if (dse != null) ccmBuilder.withDSE(dse); if (ssl()) ccmBuilder.withSSL(); if (auth()) ccmBuilder.withAuth(); for (Map.Entry<String, Object> entry : config().entrySet()) { ccmBuilder.withCassandraConfiguration(entry.getKey(), entry.getValue()); } for (Map.Entry<String, Object> entry : dseConfig().entrySet()) { ccmBuilder.withDSEConfiguration(entry.getKey(), entry.getValue()); } for (String option : startOptions()) { ccmBuilder.withCreateOptions(option); } for (String arg : jvmArgs()) { ccmBuilder.withJvmArgs(arg); } List<Workload[]> workloads = workloads(); for (int i = 0; i < workloads.size(); i++) { Workload[] workload = workloads.get(i); if (workload != null) ccmBuilder.withWorkload(i + 1, workload); } } return ccmBuilder; } private CCMBridge.Builder ccmProvider(Object testInstance) throws Exception { String methodName = null; Class<?> clazz = null; for (int i = annotations.size() - 1; i >= 0; i--) { CCMConfig ann = annotations.get(i); if (!ann.ccmProvider().isEmpty()) { methodName = ann.ccmProvider(); } if (!ann.ccmProviderClass().equals(CCMConfig.Undefined.class)) { clazz = ann.ccmProviderClass(); } } if (methodName == null) return null; if (clazz == null) clazz = testInstance.getClass(); Method method = locateMethod(methodName, clazz); assert CCMBridge.Builder.class.isAssignableFrom(method.getReturnType()); if (Modifier.isStatic(method.getModifiers())) { return (CCMBridge.Builder) method.invoke(null); } else { Object receiver = testInstance.getClass().equals(clazz) ? testInstance : instantiate(clazz); return (CCMBridge.Builder) method.invoke(receiver); } } private Cluster.Builder clusterProvider(Object testInstance) throws Exception { String methodName = null; Class<?> clazz = null; for (int i = annotations.size() - 1; i >= 0; i--) { CCMConfig ann = annotations.get(i); if (!ann.clusterProvider().isEmpty()) { methodName = ann.clusterProvider(); } if (!ann.clusterProviderClass().equals(CCMConfig.Undefined.class)) { clazz = ann.clusterProviderClass(); } } if (methodName == null) methodName = "createClusterBuilder"; if (clazz == null) clazz = testInstance.getClass(); Method method = locateMethod(methodName, clazz); assert Cluster.Builder.class.isAssignableFrom(method.getReturnType()); if (Modifier.isStatic(method.getModifiers())) { return (Cluster.Builder) method.invoke(null); } else { Object receiver = testInstance.getClass().equals(clazz) ? testInstance : instantiate(clazz); return (Cluster.Builder) method.invoke(receiver); } } @SuppressWarnings("unchecked") private void invokeInitTest(Object testInstance) throws Exception { String methodName = null; Class<?> clazz = null; for (int i = annotations.size() - 1; i >= 0; i--) { CCMConfig ann = annotations.get(i); if (!ann.testInitializer().isEmpty()) { methodName = ann.testInitializer(); } if (!ann.testInitializerClass().equals(CCMConfig.Undefined.class)) { clazz = ann.testInitializerClass(); } } if (methodName == null) methodName = "onTestContextInitialized"; if (clazz == null) clazz = testInstance.getClass(); Method method = locateMethod(methodName, clazz); if (Modifier.isStatic(method.getModifiers())) { method.invoke(null); } else { Object receiver = testInstance.getClass().equals(clazz) ? testInstance : instantiate(clazz); method.invoke(receiver); } } } private TestMode testMode; protected CCMTestConfig ccmTestConfig; private CCMAccess ccm; private CCMBridge.Builder ccmBuilder; private Cluster cluster; private Session session; protected String keyspace; private boolean erroredOut = false; private Closer closer; /** * Hook invoked at the beginning of a test class to initialize CCM test context. * * @throws Exception */ @BeforeClass(groups = {"isolated", "short", "long", "stress", "duration"}) public void beforeTestClass() throws Exception { beforeTestClass(this); } /** * Hook invoked at the beginning of a test class to initialize CCM test context. * <p/> * Useful when this class is not a superclass of the test being run. * * @throws Exception */ public void beforeTestClass(Object testInstance) throws Exception { testMode = determineTestMode(testInstance.getClass()); if (testMode == PER_CLASS) { closer = Closer.create(); try { initTestContext(testInstance, null); initTestCluster(testInstance); initTestSession(); initTestKeyspace(); initTest(testInstance); } catch (Exception e) { LOGGER.error(e.getMessage(), e); errorOut(); fail(e.getMessage()); } } } /** * Hook executed before each test method. * * @throws Exception */ @BeforeMethod(groups = {"isolated", "short", "long", "stress", "duration"}) public void beforeTestMethod(Method testMethod) throws Exception { beforeTestMethod(this, testMethod); } /** * Hook executed before each test method. * <p/> * Useful when this class is not a superclass of the test being run. * * @throws Exception */ public void beforeTestMethod(Object testInstance, Method testMethod) throws Exception { if (isCcmEnabled(testMethod)) { if (closer == null) closer = Closer.create(); if (testMode == PER_METHOD || erroredOut) { try { initTestContext(testInstance, testMethod); initTestCluster(testInstance); initTestSession(); initTestKeyspace(); initTest(testInstance); } catch (Exception e) { LOGGER.error(e.getMessage(), e); errorOut(); fail(e.getMessage()); } } assert ccmTestConfig != null; assert !ccmTestConfig.createCcm() || ccm != null; } } /** * Hook executed after each test method. */ @AfterMethod(groups = {"isolated", "short", "long", "stress", "duration"}, alwaysRun = true) public void afterTestMethod(ITestResult tr) throws Exception { if (isCcmEnabled(tr.getMethod().getConstructorOrMethod().getMethod())) { if (tr.getStatus() == ITestResult.FAILURE) { errorOut(); } if (erroredOut || testMode == PER_METHOD) { closeCloseables(); closeTestCluster(); } if (testMode == PER_METHOD) closeTestContext(); } } /** * Hook executed after each test class. */ @AfterClass(groups = {"isolated", "short", "long", "stress", "duration"}, alwaysRun = true) public void afterTestClass() throws Exception { if (testMode == PER_CLASS) { closeCloseables(); closeTestCluster(); closeTestContext(); } } /** * Returns the cluster builder to use for this test. * <p/> * The default implementation returns a vanilla builder. * <p/> * It's not required to call {@link com.datastax.driver.core.Cluster.Builder#addContactPointsWithPorts}, * it will be done automatically. * * @return The cluster builder to use for the tests. */ public Cluster.Builder createClusterBuilder() { return Cluster.builder() // use a different codec registry for each cluster instance .withCodecRegistry(new CodecRegistry()); } /** * Returns an alternate cluster builder to use for this test, * with no event debouncing. * <p/> * It's not required to call {@link com.datastax.driver.core.Cluster.Builder#addContactPointsWithPorts}, * it will be done automatically. * * @return The cluster builder to use for the tests. */ public Cluster.Builder createClusterBuilderNoDebouncing() { return Cluster.builder().withQueryOptions(TestUtils.nonDebouncingQueryOptions()); } /** * Hook invoked when the test context is ready, but before the test itself. * Useful to create fixtures or to insert test data. * <p/> * This method is invoked once per class is the {@link TestMode} is {@link TestMode#PER_CLASS} * or before each test method, if the {@link TestMode} is {@link TestMode#PER_METHOD}. * <p/> * When this method is called, the cluster and the session are ready * to be used (unless the configuration specifies that such objects * should not be created). * <p/> * Statements executed inside this method do not need to be qualified with a keyspace name, * in which case they are executed with the default keyspace for the test * (unless the configuration specifies that no keyspace should be creaed for the test). * <p/> * The default implementation does nothing (no fixtures required). */ public void onTestContextInitialized() { // nothing to do by default } /** * @return The {@link CCMAccess} instance to use with this test. */ public CCMAccess ccm() { return ccm; } /** * @return The {@link Cluster} instance to use with this test. * Can be null if CCM configuration specifies {@code createCluster = false}. */ public Cluster cluster() { return cluster; } /** * @return The {@link Session} instance to use with this test. * Can be null if CCM configuration specifies {@code createSession = false}. */ public Session session() { return session; } /** * Executes the given statements with the test's session object. * <p/> * Useful to create test fixtures and/or load data before tests. * <p/> * This method should not be called if a session object hasn't been created * (if CCM configuration specifies {@code createSession = false}) * * @param statements The statements to execute. */ public void execute(String... statements) { execute(Arrays.asList(statements)); } /** * Executes the given statements with the test's session object. * <p/> * Useful to create test fixtures and/or load data before tests. * <p/> * This method should not be called if a session object hasn't been created * (if CCM configuration specifies {@code createSession = false}) * * @param statements The statements to execute. */ public void execute(Collection<String> statements) { assert session != null; for (String stmt : statements) { try { session.execute(stmt); } catch (Exception e) { errorOut(); LOGGER.error("Could not execute statement: " + stmt, e); Throwables.propagate(e); } } } /** * Signals that the test has encountered an unexpected error. * <p/> * This method is automatically called when a test finishes with an unexpected exception * being thrown, but it is also possible to manually invoke it. * <p/> * Calling this method will close the current cluster and session. * The CCM data directory will be kept after the test session is finished, * for debugging purposes * <p/> * This method should not be called before the test has started, nor after the test is finished. */ public void errorOut() { erroredOut = true; if (ccm != null) { ccm.setKeepLogs(true); } } /** * Returns the contact points to use to contact the CCM cluster. * <p/> * This method returns as many contact points as the number of nodes initially present in the CCM cluster, * according to {@link CCMConfig} annotations. * <p/> * On a multi-DC setup, this will include nodes in all data centers. * <p/> * This method should not be called before the test has started, nor after the test is finished. * * @return the contact points to use to contact the CCM cluster. */ public List<InetAddress> getContactPoints() { assert ccmTestConfig != null; List<InetAddress> contactPoints = new ArrayList<InetAddress>(); int n = 1; int[] numberOfNodes = ccmTestConfig.numberOfNodes(); for (int dc = 1; dc <= numberOfNodes.length; dc++) { int nodesInDc = numberOfNodes[dc - 1]; for (int i = 0; i < nodesInDc; i++) { try { contactPoints.add(InetAddress.getByName(ipOfNode(n))); } catch (UnknownHostException e) { Throwables.propagate(e); } n++; } } return contactPoints; } /** * Returns the contact points to use to contact the CCM cluster. * <p/> * This method returns as many contact points as the number of nodes initially present in the CCM cluster, * according to {@link CCMConfig} annotations. * <p/> * On a multi-DC setup, this will include nodes in all data centers. * <p/> * This method should not be called before the test has started, nor after the test is finished. * * @return the contact points to use to contact the CCM cluster. */ public List<InetSocketAddress> getContactPointsWithPorts() { assert ccmTestConfig != null; List<InetSocketAddress> contactPoints = new ArrayList<InetSocketAddress>(); int n = 1; int[] numberOfNodes = ccmTestConfig.numberOfNodes(); for (int dc = 1; dc <= numberOfNodes.length; dc++) { int nodesInDc = numberOfNodes[dc - 1]; for (int i = 0; i < nodesInDc; i++) { contactPoints.add(new InetSocketAddress(ipOfNode(n), ccm.getBinaryPort())); n++; } } return contactPoints; } /** * Registers the given {@link Closeable} to be closed at the end of the current test method. * <p/> * This method should not be called before the test has started, nor after the test is finished. * * @param closeable The closeable to close * @return The closeable to close */ public <T extends Closeable> T register(T closeable) { closer.register(closeable); return closeable; } /** * Tests fail randomly with InvalidQueryException: Keyspace 'xxx' does not exist; * this method tries at most 3 times to issue a successful USE statement. * * @param ks The keyspace to USE */ public void useKeyspace(String ks) { useKeyspace(session(), ks); } /** * Tests fail randomly with InvalidQueryException: Keyspace 'xxx' does not exist; * this method tries at most 3 times to issue a successful USE statement. * * @param session The session to use * @param ks The keyspace to USE */ public void useKeyspace(Session session, String ks) { final int maxTries = 3; for (int i = 1; i <= maxTries; i++) { try { session.execute("USE " + ks); } catch (InvalidQueryException e) { if (i == maxTries) throw e; LOGGER.error("Could not USE keyspace, retrying"); Uninterruptibles.sleepUninterruptibly(1, TimeUnit.MINUTES); } } } protected void initTestContext(Object testInstance, Method testMethod) throws Exception { erroredOut = false; ccmTestConfig = createCCMTestConfig(testInstance, testMethod); assert ccmTestConfig != null; if (ccmTestConfig.createCcm()) { ccmBuilder = ccmTestConfig.ccmBuilder(testInstance); CCMAccess ccm = CCMCache.get(ccmBuilder); assert ccm != null; if (ccmTestConfig.dirtiesContext()) { this.ccm = ccm; } else { this.ccm = new ReadOnlyCCMAccess(ccm); } try { ccm.start(); } catch (CCMException e) { errorOut(); fail(e.getMessage()); } LOGGER.debug("Using {}", ccm); } } protected void initTestCluster(Object testInstance) throws Exception { if (ccmTestConfig.createCcm() && ccmTestConfig.createCluster()) { Cluster.Builder builder = ccmTestConfig.clusterProvider(testInstance); // add contact points only if the provided builder didn't do so if (builder.getContactPoints().isEmpty()) builder.addContactPoints(getContactPoints()); builder.withPort(ccm.getBinaryPort()); cluster = register(builder.build()); cluster.init(); } } protected void initTestSession() throws Exception { if (ccmTestConfig.createCcm() && ccmTestConfig.createCluster() && ccmTestConfig.createSession()) session = register(cluster.connect()); } protected void initTestKeyspace() { if (ccmTestConfig.createCcm() && ccmTestConfig.createCluster() && ccmTestConfig.createSession() && ccmTestConfig.createKeyspace()) { try { keyspace = TestUtils.generateIdentifier("ks_"); LOGGER.debug("Using keyspace " + keyspace); session.execute(String.format(CREATE_KEYSPACE_SIMPLE_FORMAT, keyspace, 1)); useKeyspace(keyspace); } catch (Exception e) { errorOut(); LOGGER.error("Could not create test keyspace", e); Throwables.propagate(e); } } } protected void initTest(Object testInstance) throws Exception { ccmTestConfig.invokeInitTest(testInstance); } protected void closeTestContext() throws Exception { if (ccmTestConfig != null && ccmBuilder != null && ccm != null) { if (ccmTestConfig.dirtiesContext()) { CCMCache.remove(ccmBuilder); ccm.close(); } else { ((ReadOnlyCCMAccess) ccm).delegate.close(); } } ccmTestConfig = null; ccmBuilder = null; ccm = null; } protected void closeTestCluster() { if (cluster != null && !cluster.isClosed()) executeNoFail(new Runnable() { @Override public void run() { cluster.close(); } }, false); cluster = null; session = null; keyspace = null; } protected void closeCloseables() { if (closer != null) executeNoFail(new Callable<Void>() { @Override public Void call() throws IOException { closer.close(); return null; } }, false); } private static boolean isCcmEnabled(Method testMethod) { Test ann = locateAnnotation(testMethod, Test.class); return !Collections.disjoint(Arrays.asList(ann.groups()), TEST_GROUPS); } private static CCMTestConfig createCCMTestConfig(Object testInstance, Method testMethod) throws Exception { ArrayList<CCMConfig> annotations = new ArrayList<CCMConfig>(); CCMConfig ann = locateAnnotation(testMethod, CCMConfig.class); if (ann != null) annotations.add(ann); locateClassAnnotations(testInstance.getClass(), CCMConfig.class, annotations); return new CCMTestConfig(annotations); } private static TestMode determineTestMode(Class<?> testClass) { List<CreateCCM> annotations = locateClassAnnotations(testClass, CreateCCM.class, new ArrayList<CreateCCM>()); if (!annotations.isEmpty()) return annotations.get(0).value(); return PER_CLASS; } private static <A extends Annotation> A locateAnnotation(Method testMethod, Class<? extends A> clazz) { if (testMethod == null) return null; testMethod.setAccessible(true); return testMethod.getAnnotation(clazz); } private static <A extends Annotation> List<A> locateClassAnnotations(Class<?> clazz, Class<? extends A> annotationClass, List<A> annotations) { A ann = clazz.getAnnotation(annotationClass); if (ann != null) annotations.add(ann); clazz = clazz.getSuperclass(); if (clazz == null) return annotations; return locateClassAnnotations(clazz, annotationClass, annotations); } private static Method locateMethod(String methodName, Class<?> clazz) throws NoSuchMethodException { try { Method method = clazz.getDeclaredMethod(methodName); method.setAccessible(true); return method; } catch (NoSuchMethodException e) { clazz = clazz.getSuperclass(); if (clazz == null) throw e; return locateMethod(methodName, clazz); } } @SuppressWarnings("unchecked") private static <T> T instantiate(Class<? extends T> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, java.lang.reflect.InvocationTargetException { if (clazz.getEnclosingClass() == null || Modifier.isStatic(clazz.getModifiers())) { Constructor<?> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); return (T) constructor.newInstance(); } else { Class<?> enclosingClass = clazz.getEnclosingClass(); Object enclosingInstance = enclosingClass.newInstance(); Constructor<?> constructor = clazz.getDeclaredConstructor(enclosingClass); constructor.setAccessible(true); return (T) constructor.newInstance(enclosingInstance); } } }