/*
* Copyright 2010 the original author or authors.
*
* 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.gradle.api.internal.tasks.testing.detection;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.gradle.api.GradleException;
import org.gradle.api.internal.tasks.testing.DefaultTestClassRunInfo;
import org.gradle.api.internal.tasks.testing.TestClassProcessor;
import org.gradle.util.internal.Java9ClassReader;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.gradle.internal.FileUtils.hasExtension;
public abstract class AbstractTestFrameworkDetector<T extends TestClassVisitor> implements TestFrameworkDetector {
protected static final String TEST_CASE = "junit/framework/TestCase";
protected static final String GROOVY_TEST_CASE = "groovy/util/GroovyTestCase";
protected static final String JAVA_LANG_OBJECT = "java/lang/Object";
private List<File> testClassDirectories;
private final ClassFileExtractionManager classFileExtractionManager;
private final Map<File, Boolean> superClasses;
private TestClassProcessor testClassProcessor;
private final List<String> knownTestCaseClassNames;
private Set<File> testClassesDirectories;
private Set<File> testClasspath;
protected AbstractTestFrameworkDetector(ClassFileExtractionManager classFileExtractionManager) {
assert classFileExtractionManager != null;
this.classFileExtractionManager = classFileExtractionManager;
this.superClasses = new HashMap<File, Boolean>();
this.knownTestCaseClassNames = new ArrayList<String>();
addKnownTestCaseClassNames(TEST_CASE, GROOVY_TEST_CASE);
}
protected abstract T createClassVisitor();
protected File getSuperTestClassFile(String superClassName) {
prepareClasspath();
if (StringUtils.isEmpty(superClassName)) {
throw new IllegalArgumentException("superClassName is empty!");
}
final Iterator<File> testClassDirectoriesIt = testClassDirectories.iterator();
File superTestClassFile = null;
while (superTestClassFile == null && testClassDirectoriesIt.hasNext()) {
final File testClassDirectory = testClassDirectoriesIt.next();
final File superTestClassFileCandidate = new File(testClassDirectory, superClassName + ".class");
if (superTestClassFileCandidate.exists()) {
superTestClassFile = superTestClassFileCandidate;
}
}
if (superTestClassFile != null) {
return superTestClassFile;
} else if (JAVA_LANG_OBJECT.equals(superClassName)) {
// java.lang.Object found, which is not a test class
return null;
} else {
// super test class file not in test class directories
return classFileExtractionManager.getLibraryClassFile(superClassName);
}
}
private void prepareClasspath() {
if (testClassDirectories != null) {
return;
}
testClassDirectories = new ArrayList<File>();
if (testClassesDirectories != null) {
testClassDirectories.addAll(testClassesDirectories);
}
if (testClasspath != null) {
for (File file : testClasspath) {
if (file.isDirectory()) {
testClassDirectories.add(file);
} else if (file.isFile() && hasExtension(file, ".jar")) {
classFileExtractionManager.addLibraryJar(file);
}
}
}
}
@Override
public void setTestClasses(Set<File> testClassesDirectories) {
this.testClassesDirectories = testClassesDirectories;
}
@Override
public void setTestClasspath(Set<File> testClasspath) {
this.testClasspath = testClasspath;
}
protected TestClassVisitor classVisitor(final File testClassFile) {
final TestClassVisitor classVisitor = createClassVisitor();
InputStream classStream = null;
try {
classStream = new BufferedInputStream(new FileInputStream(testClassFile));
final ClassReader classReader = new Java9ClassReader(IOUtils.toByteArray(classStream));
classReader.accept(classVisitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
} catch (Throwable e) {
throw new GradleException("failed to read class file " + testClassFile.getAbsolutePath(), e);
} finally {
IOUtils.closeQuietly(classStream);
}
return classVisitor;
}
@Override
public boolean processTestClass(File testClassFile) {
return processTestClass(testClassFile, false);
}
protected abstract boolean processTestClass(File testClassFile, boolean superClass);
protected boolean processSuperClass(File testClassFile) {
boolean isTest = false;
Boolean isSuperTest = superClasses.get(testClassFile);
if (isSuperTest == null) {
isTest = processTestClass(testClassFile, true);
superClasses.put(testClassFile, isTest);
} else {
isTest = isSuperTest;
}
return isTest;
}
/**
* In none super class mode a test class is published when the class is a test and it is not abstract. In super class mode it must not publish the class otherwise it will get published multiple
* times (for each extending class).
*/
protected void publishTestClass(boolean isTest, TestClassVisitor classVisitor, boolean superClass) {
if (isTest && !classVisitor.isAbstract() && !superClass) {
String className = Type.getObjectType(classVisitor.getClassName()).getClassName();
testClassProcessor.processTestClass(new DefaultTestClassRunInfo(className));
}
}
@Override
public void startDetection(TestClassProcessor testClassProcessor) {
this.testClassProcessor = testClassProcessor;
}
public void addKnownTestCaseClassNames(String... knownTestCaseClassNames) {
if (knownTestCaseClassNames != null && knownTestCaseClassNames.length != 0) {
for (String knownTestCaseClassName : knownTestCaseClassNames) {
if (StringUtils.isNotEmpty(knownTestCaseClassName)) {
this.knownTestCaseClassNames.add(knownTestCaseClassName.replaceAll("\\.", "/"));
}
}
}
}
protected boolean isKnownTestCaseClassName(String testCaseClassName) {
boolean isKnownTestCase = false;
if (StringUtils.isNotEmpty(testCaseClassName)) {
isKnownTestCase = knownTestCaseClassNames.contains(testCaseClassName);
}
return isKnownTestCase;
}
}