/*
* Copyright 2014 NAVER Corp.
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.navercorp.pinpoint.test.plugin;
import static com.navercorp.pinpoint.test.plugin.PinpointPluginTestConstants.CHILD_CLASS_PATH_PREFIX;
import com.navercorp.pinpoint.common.Charsets;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.junit.rules.RunRules;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.manipulation.Sorter;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerScheduler;
import org.junit.runners.model.Statement;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
/**
*
* We have referred OrderedThreadPoolExecutor ParentRunner of JUnit.
*
* @author Jongho Moon
* @author Taejin Koo
*/
public class PinpointPluginTestSuite extends AbstractPinpointPluginTestSuite {
private static final String DEFAULT_ENCODING = Charsets.UTF_8_NAME;
private final boolean testOnSystemClassLoader;
private final boolean testOnChildClassLoader;
private final String[] repositories;
private final String[] dependencies;
private final String libraryPath;
private final String[] librarySubDirs;
private final Object childrenLock = new Object();
private volatile Collection<Runner> filteredChildren = null;
private volatile RunnerScheduler scheduler = new RunnerScheduler() {
public void schedule(Runnable childStatement) {
childStatement.run();
}
public void finished() {
// do nothing
}
};
public PinpointPluginTestSuite(Class<?> testClass) throws InitializationError, ArtifactResolutionException, DependencyResolutionException {
super(testClass);
OnClassLoader onClassLoader = testClass.getAnnotation(OnClassLoader.class);
this.testOnChildClassLoader = onClassLoader == null ? true : onClassLoader.child();
this.testOnSystemClassLoader = onClassLoader == null ? false : onClassLoader.system();
Dependency deps = testClass.getAnnotation(Dependency.class);
this.dependencies = deps == null ? null : deps.value();
TestRoot lib = testClass.getAnnotation(TestRoot.class);
if (lib == null) {
this.libraryPath = null;
this.librarySubDirs = null;
} else {
String path = lib.value();
if (path.isEmpty()) {
path = lib.path();
}
this.libraryPath = path;
this.librarySubDirs = lib.libraryDir();
}
if (deps != null && lib != null) {
throw new IllegalArgumentException("@Dependency and @TestRoot can not annotate a class at the same time");
}
Repository repos = testClass.getAnnotation(Repository.class);
this.repositories = repos == null ? new String[0] : repos.value();
}
@Override
protected List<PinpointPluginTestInstance> createTestCases(PinpointPluginTestContext context) throws Exception {
if (dependencies != null) {
return createCasesWithDependencies(context);
} else if (libraryPath != null) {
return createCasesWithLibraryPath(context);
}
return createCasesWithJdkOnly(context);
}
private List<PinpointPluginTestInstance> createCasesWithJdkOnly(PinpointPluginTestContext context) {
List<PinpointPluginTestInstance> cases = new ArrayList<PinpointPluginTestInstance>();
if (testOnSystemClassLoader) {
cases.add(new NormalPluginTestCase(context, "", Collections.<String>emptyList(), true));
}
if (testOnChildClassLoader) {
cases.add(new NormalPluginTestCase(context, "", Collections.<String>emptyList(), false));
}
return cases;
}
private List<PinpointPluginTestInstance> createCasesWithLibraryPath(PinpointPluginTestContext context) {
File file = new File(libraryPath);
if (!file.isDirectory()) {
throw new RuntimeException("value of @TestRoot is not a directory: " + libraryPath);
}
File[] children = file.listFiles();
if (children == null) {
return Collections.emptyList();
}
List<PinpointPluginTestInstance> cases = new ArrayList<PinpointPluginTestInstance>();
for (File child : children) {
if (!child.isDirectory()) {
continue;
}
List<String> libraries = new ArrayList<String>();
if (librarySubDirs.length == 0) {
addJars(child, libraries);
libraries.add(child.getAbsolutePath());
} else {
for (String subDir : librarySubDirs) {
File libDir = new File(child, subDir);
addJars(libDir, libraries);
libraries.add(libDir.getAbsolutePath());
}
}
if (testOnSystemClassLoader) {
cases.add(new NormalPluginTestCase(context, child.getName(), libraries, true));
}
if (testOnChildClassLoader) {
cases.add(new NormalPluginTestCase(context, child.getName(), libraries, false));
}
}
return cases;
}
private void addJars(File libDir, List<String> libraries) {
if (!libDir.isDirectory()) {
return;
}
File[] children = libDir.listFiles();
if (children == null) {
return;
}
for (File f : children) {
if (f.getName().endsWith(".jar")) {
libraries.add(f.getAbsolutePath());
}
}
}
private List<PinpointPluginTestInstance> createCasesWithDependencies(PinpointPluginTestContext context) throws ArtifactResolutionException, DependencyResolutionException {
List<PinpointPluginTestInstance> cases = new ArrayList<PinpointPluginTestInstance>();
DependencyResolver resolver = DependencyResolver.get(repositories);
Map<String, List<Artifact>> dependencyCases = resolver.resolveDependencySets(dependencies);
for (Map.Entry<String, List<Artifact>> dependencyCase : dependencyCases.entrySet()) {
List<String> libs = new ArrayList<String>();
for (File lib : resolver.resolveArtifactsAndDependencies(dependencyCase.getValue())) {
libs.add(lib.getAbsolutePath());
}
if (testOnSystemClassLoader) {
cases.add(new NormalPluginTestCase(context, dependencyCase.getKey(), libs, true));
}
if (testOnChildClassLoader) {
cases.add(new NormalPluginTestCase(context, dependencyCase.getKey(), libs, false));
}
}
return cases;
}
protected Statement classBlock(final RunNotifier notifier) {
Statement statement = childrenInvoker(notifier);
if (!areAllChildrenIgnored()) {
statement = withBeforeClasses(statement);
statement = withAfterClasses(statement);
statement = withClassRules(statement);
}
return statement;
}
private Statement withClassRules(Statement statement) {
List<TestRule> classRules = classRules();
return classRules.isEmpty() ? statement :
new RunRules(statement, classRules, getDescription());
}
private boolean areAllChildrenIgnored() {
for (Runner child : getFilteredChildren()) {
if (!isIgnored(child)) {
return false;
}
}
return true;
}
private Collection<Runner> getFilteredChildren() {
if (filteredChildren == null) {
synchronized (childrenLock) {
if (filteredChildren == null) {
filteredChildren = Collections.unmodifiableCollection(getChildren());
}
}
}
return filteredChildren;
}
public void sort(Sorter sorter) {
synchronized (childrenLock) {
for (Runner each : getFilteredChildren()) {
sorter.apply(each);
}
List<Runner> sortedChildren = new ArrayList<Runner>(getFilteredChildren());
Collections.sort(sortedChildren, comparator(sorter));
filteredChildren = Collections.unmodifiableCollection(sortedChildren);
}
}
private Comparator<? super Runner> comparator(final Sorter sorter) {
return new Comparator<Runner>() {
public int compare(Runner o1, Runner o2) {
return sorter.compare(describeChild(o1), describeChild(o2));
}
};
}
private void runChildren(final RunNotifier notifier) {
final RunnerScheduler currentScheduler = scheduler;
try {
for (final Runner each : getFilteredChildren()) {
currentScheduler.schedule(new Runnable() {
public void run() {
runChild(each, notifier);
}
});
}
} finally {
currentScheduler.finished();
}
}
protected Statement childrenInvoker(final RunNotifier notifier) {
return new Statement() {
@Override
public void evaluate() {
runChildren(notifier);
}
};
}
@Override
public void filter(Filter filter) throws NoTestsRemainException {
synchronized (childrenLock) {
List<Runner> children = new ArrayList<Runner>(getFilteredChildren());
for (Iterator<Runner> iter = children.iterator(); iter.hasNext(); ) {
Runner each = iter.next();
if (shouldRun(filter, each)) {
try {
filter.apply(each);
} catch (NoTestsRemainException e) {
iter.remove();
}
} else {
iter.remove();
}
}
filteredChildren = Collections.unmodifiableCollection(children);
if (filteredChildren.isEmpty()) {
throw new NoTestsRemainException();
}
}
}
private boolean shouldRun(Filter filter, Runner each) {
if (filter.shouldRun(describeChild(each))) {
return true;
}
if (each instanceof PinpointPluginTestRunner) {
return ((PinpointPluginTestRunner) each).isAvaiable(filter);
}
return false;
}
@Override
public Description getDescription() {
Description description = Description.createSuiteDescription(getName(), getRunnerAnnotations());
for (Runner child : getFilteredChildren()) {
description.addChild(describeChild(child));
}
return description;
}
private static class NormalPluginTestCase implements PinpointPluginTestInstance {
private final PinpointPluginTestContext context;
private final String testId;
private final List<String> libs;
private final boolean onSystemClassLoader;
public NormalPluginTestCase(PinpointPluginTestContext context, String testId, List<String> libs, boolean onSystemClassLoader) {
this.context = context;
this.testId = testId + ":" + (onSystemClassLoader ? "system" : "child") + ":" + context.getJvmVersion();
this.libs = libs;
this.onSystemClassLoader = onSystemClassLoader;
}
@Override
public String getTestId() {
return testId;
}
@Override
public List<String> getClassPath() {
if (onSystemClassLoader) {
List<String> libs = new ArrayList<String>(context.getRequiredLibraries());
libs.addAll(this.libs);
libs.add(context.getTestClassLocation());
return libs;
} else {
return context.getRequiredLibraries();
}
}
@Override
public List<String> getVmArgs() {
return Arrays.asList("-Dfile.encoding=" + DEFAULT_ENCODING);
}
@Override
public String getMainClass() {
return ForkedPinpointPluginTest.class.getName();
}
@Override
public List<String> getAppArgs() {
List<String> args = new ArrayList<String>();
args.add(context.getTestClass().getName());
if (!onSystemClassLoader) {
StringBuilder classPath = new StringBuilder();
classPath.append(CHILD_CLASS_PATH_PREFIX);
for (String lib : libs) {
classPath.append(lib);
classPath.append(File.pathSeparatorChar);
}
classPath.append(context.getTestClassLocation());
args.add(classPath.toString());
}
return args;
}
@Override
public Scanner startTest(Process process) throws Exception {
InputStream inputStream = process.getInputStream();
return new Scanner(inputStream, DEFAULT_ENCODING);
}
@Override
public void endTest(Process process) throws Exception {
// do nothing
}
@Override
public File getWorkingDirectory() {
return new File(".");
}
}
}