/*
* JBoss, Home of Professional Open Source
* Copyright [2011], Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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 org.modeshape.jcr.perftests;
import javax.jcr.Credentials;
import javax.jcr.Repository;
import javax.jcr.RepositoryFactory;
import org.modeshape.jcr.perftests.output.CsvOutput;
import org.reflections.Reflections;
import org.reflections.scanners.TypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* Class which runs a set of test suites against all the JCR repositories which are found in the classpath. The
* <code>ServiceLoader</code> mechanism is used for scanning the <code>RepositoryFactory</code> instances. All subclasses of
* {@link org.modeshape.jcr.perftests.AbstractPerformanceTestSuite} found within the org.modeshape.jcr.perftests package (or
* subpackages) will be loaded by default.
*
* @author Horia Chiorean
*/
public final class SuiteRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(SuiteRunner.class);
private final TestData testData;
private final RunnerCfg runnerConfig;
/**
* Creates a new default test runner instance, which loads its properties from a file called "runner.properties" in the
* classpath.
*
* @param repositoryName the repository name
*/
public SuiteRunner( String repositoryName ) {
this(repositoryName, new RunnerCfg());
}
/**
* Creates a new runner instance passing a custom config.
*
* @param repositoryName the repository name
* @param runnerConfig the runner configuration
*/
public SuiteRunner( String repositoryName,
RunnerCfg runnerConfig ) {
this.testData = new TestData(repositoryName);
this.runnerConfig = runnerConfig;
}
/**
* Uses the given map of parameters together with the <code>ServiceLoader</code> mechanism to get all the
* <code>RepositoryFactory</code> instances and the subsequent repositories against which the tests will be run.
*
* @param repositoryConfigParams a map of config params {@link javax.jcr.RepositoryFactory#getRepository(java.util.Map)}
* @param credentials a set of credentials which may be needed by a certain repo to run. It can be null.
* @throws Exception if anything unexpected happens during the run. In case there are test exceptions, those will just be
* logged and the suite will continue to run.
*/
public void runPerformanceTests( Map<?, ?> repositoryConfigParams,
Credentials credentials ) throws Exception {
// Load the test suite, and run each suite by itself with a clean repository ...
Set<Class<? extends AbstractPerformanceTestSuite>> testSuites = loadPerformanceTestSuites();
for (Class<? extends AbstractPerformanceTestSuite> testSuiteClass : testSuites) {
// Before running each suite ...
runnerConfig.beforeRunningSuite();
// Run the suite ...
RepositoryFactory repositoryFactory = null;
Repository repository = null;
try {
for (RepositoryFactory factory : ServiceLoader.load(RepositoryFactory.class)) {
repository = new RepositoryInitRun(credentials, factory, repositoryConfigParams).execute();
if (repository == null) {
continue;
}
repositoryFactory = factory;
SuiteConfiguration suiteConfiguration = new SuiteConfiguration(repository, credentials,
"testsuite.properties");
runTestSuite(suiteConfiguration, testSuiteClass);
break;
}
} finally {
// Always after the suite is run ...
runnerConfig.afterRunningSuite(repositoryFactory, repository);
}
}
new CsvOutput().generateOutput(testData);
}
/**
* Returns the test data which has been recorded by this runner.
*
* @return a {@link TestData} instance.
*/
public TestData getTestData() {
return testData;
}
private void runTestSuite( SuiteConfiguration suiteConfiguration,
Class<? extends AbstractPerformanceTestSuite> testSuiteClass ) throws Exception {
final AbstractPerformanceTestSuite testSuite = testSuiteClass.getConstructor(SuiteConfiguration.class).newInstance(
suiteConfiguration);
if (isSuiteExcluded(testSuiteClass)) {
return;
}
if (!testSuite.isCompatibleWithCurrentRepository()) {
LOGGER.warn("Test suite {} not compatible with {}",
new Object[] { testSuite.getClass().getSimpleName(), testData.getRepositoryName() });
return;
}
LOGGER.info("Starting suite: {}[warmup #:{}, repeat#{}]",
new Object[] { testSuiteClass.getSimpleName(), runnerConfig.warmupCount, runnerConfig.repeatCount });
new SuiteRun(testSuite, runnerConfig.warmupCount, runnerConfig.repeatCount).execute();
}
private boolean isSuiteExcluded( Class<? extends AbstractPerformanceTestSuite> testSuiteClass ) {
// first search excluded list
if (patternMatchesSuiteName(testSuiteClass, runnerConfig.excludeTestsRegExp)) {
return true;
}
// then search included list
return !runnerConfig.includeTestsRegExp.isEmpty() && !patternMatchesSuiteName(testSuiteClass,
runnerConfig.includeTestsRegExp);
}
private boolean patternMatchesSuiteName( Class<? extends AbstractPerformanceTestSuite> suiteClass,
List<String> patternsList ) {
for (Iterator<String> iterator = patternsList.iterator(); iterator.hasNext(); ) {
String patternString = iterator.next();
try {
Pattern pattern = Pattern.compile(patternString);
if (pattern.matcher(suiteClass.getName()).matches() || pattern.matcher(suiteClass.getSimpleName()).matches()) {
return true;
}
} catch (PatternSyntaxException e) {
LOGGER.warn("Invalid regex " + patternString, e);
iterator.remove();
}
}
return false;
}
private Set<Class<? extends AbstractPerformanceTestSuite>> loadPerformanceTestSuites() {
ConfigurationBuilder builder = new ConfigurationBuilder().addUrls(ClasspathHelper.forPackage("org.modeshape"))
.setScanners(new TypesScanner()).useParallelExecutor();
Reflections reflections = new Reflections(builder);
return reflections.getSubTypesOf(AbstractPerformanceTestSuite.class);
}
private final class SuiteRun {
private final AbstractPerformanceTestSuite suite;
private final int warmupCount;
private final int runCount;
private SuiteRun( AbstractPerformanceTestSuite suite,
int warmupCount,
int runCount ) {
this.suite = suite;
this.warmupCount = warmupCount;
this.runCount = runCount;
}
void execute() throws Exception {
String suiteName = suite.getClass().getSimpleName();
LOGGER.info("Starting {} ....", suiteName);
//run the warmup without recording
try {
LOGGER.info("{} setUp()....", suiteName);
suite.setUp();
LOGGER.info("{} warming up....", suiteName);
for (int i = 0; i < warmupCount; i++) {
suite.run();
}
//run & record
for (int i = 0; i < runCount; i++) {
long start = System.nanoTime();
suite.run();
long duration = System.nanoTime() - start;
getTestData().recordSuccess(suiteName, duration, i + 1);
}
LOGGER.info("{} tearDown()....", suiteName);
suite.tearDown();
} catch (Throwable throwable) {
LOGGER.error("Error while running " + suiteName, throwable);
getTestData().recordFailure(suiteName, throwable);
}
}
}
private final class RepositoryInitRun {
private final RepositoryFactory repositoryFactory;
private final Map<?, ?> repositoryConfigParams;
private final Credentials credentials;
private RepositoryInitRun( Credentials credentials,
RepositoryFactory repositoryFactory,
Map<?, ?> repositoryConfigParams ) {
this.credentials = credentials;
this.repositoryFactory = repositoryFactory;
this.repositoryConfigParams = repositoryConfigParams;
}
Repository execute() throws Exception {
String operationName = "Repository initialization";
try {
long start = System.nanoTime();
Repository repository = repositoryFactory.getRepository(repositoryConfigParams);
if (repository == null) {
return null;
}
repository.login(credentials).logout();
long duration = System.nanoTime() - start;
getTestData().recordSuccess(operationName, duration, 1);
return repository;
} catch (Throwable t) {
getTestData().recordFailure(operationName, t);
return null;
}
}
}
}