/* * 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; } }