blob: 30b09790374f568ad9cb7105359b0f173ff62c6e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.modules.junit;
import org.netbeans.modules.junit.api.JUnitSettings;
import org.netbeans.modules.junit.api.JUnitTestUtil;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import org.netbeans.api.java.source.CompilationInfo;
//import org.netbeans.modules.junit.plugin.JUnitPlugin.CreateTestParam;
import org.netbeans.modules.gsf.testrunner.plugin.CommonPlugin.CreateTestParam;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.STATIC;
import static org.netbeans.modules.junit.TestCreator.ACCESS_MODIFIERS;
/**
*
* @author Marian Petras
*/
public final class TestGeneratorSetup implements TestabilityJudge {
/* the class is final only for performance reasons */
/* attributes - private */
static private final String JUNIT_SUPER_CLASS_NAME = "TestCase";
static private final String JUNIT_FRAMEWORK_PACKAGE_NAME = "junit.framework";
static private final String METHOD_NAME_SETUP = "setUp"; //NOI18N
static private final String METHOD_NAME_TEARDOWN = "tearDown"; //NOI18N
static private final String CLASS_COMMENT_LINE1 = "TestCreator.javaClass.addTestsHereComment.l1";
static private final String CLASS_COMMENT_LINE2 = "TestCreator.javaClass.addTestsHereComment.l2";
/** should test classes be skipped during generation of tests? */
private boolean skipTestClasses = true;
/** should package-private classes be skipped during generation of tests? */
private boolean skipPkgPrivateClasses = false;
/** should abstract classes be skipped during generation of tests? */
private boolean skipAbstractClasses = false;
/** should exception classes be skipped during generation of tests? */
private boolean skipExceptionClasses = false;
/**
* should test suite classes be generated when creating tests for folders
* and/or packages?
*/
private boolean generateSuiteClasses = true;
/**
* bitmap defining whether public/protected methods should be tested
*
* @see #testPackagePrivateMethods
*/
private Set<Modifier> methodAccessModifiers
= AbstractTestGenerator.createModifierSet(Modifier.PUBLIC,
Modifier.PROTECTED);
/**
* should package-private methods be tested?
*
* @see #methodAccessModifiers
*/
private boolean testPkgPrivateMethods = true;
/**
* should default method bodies be generated for newly created test methods?
*
* @see #generateMethodJavadoc
* @see #generateMethodBodyComments
*/
private boolean generateDefMethodBody = true;
/**
* should Javadoc comment be generated for newly created test methods?
*
* @see #generateDefMethodBody
* @see #generateMethodBodyComments
*/
private boolean generateMethodJavadoc = true;
/**
* should method body comment be generated for newly created test methods?
*
* @see #generateDefMethodBody
* @see #generateMethodJavadoc
*/
private boolean generateSourceCodeHints = true;
/**
* should {@literal setUp()} (or {@literal @Before}) method be generated
* in test classes?
*
* @see #generateTestTearDown
* @see #generateClassSetUp
* @see #generateMainMethod
*/
private boolean generateTestSetUp = true;
/**
* should {@literal tearDown()} (or {@literal @After}) method be generated
* in test classes?
*
* @see #generateTestSetUp
* @see #generateClassTearDown
* @see #generateMainMethod
*/
private boolean generateTestTearDown = true;
/**
* should {@literal @BeforeClass} method be generated in test classes?
*
* @see #generateClassTearDown
* @see #generateTestSetUp
* @see #generateMainMethod
*/
private boolean generateClassSetUp = true;
/**
* should {@literal @AfterClass} method be generated in test classes?
*
* @see #generateClassSetUp
* @see #generateTestTearDown
* @see #generateMainMethod
*/
private boolean generateClassTearDown = true;
/**
* should static method <code>main(String args[])</code>
* be generated in test classes?
*
* @see #generateSetUp
* @see #generateTearDown
*/
private boolean generateMainMethod = true;
/* public methods */
/**
* Creates a new <code>TestCreator</code>.
*
* @param loadDefaults <code>true</code> if defaults should be loaded
* from <code>JUnitSettings</code>;
* <code>false</code> otherwise
*/
public TestGeneratorSetup(boolean loadDefaults) {
if (loadDefaults) {
loadDefaults();
}
}
/**
*
*/
public TestGeneratorSetup(Map<CreateTestParam, Object> params) {
final JUnitSettings settings = JUnitSettings.getDefault();
skipTestClasses = !JUnitSettings.GENERATE_TESTS_FROM_TEST_CLASSES;
skipPkgPrivateClasses = !Boolean.TRUE.equals(params.get(
CreateTestParam.INC_PKG_PRIVATE_CLASS));
skipAbstractClasses = !Boolean.TRUE.equals(params.get(
CreateTestParam.INC_ABSTRACT_CLASS));
skipExceptionClasses = !Boolean.TRUE.equals(params.get(
CreateTestParam.INC_EXCEPTION_CLASS));
generateSuiteClasses = Boolean.TRUE.equals(params.get(
CreateTestParam.INC_GENERATE_SUITE));
methodAccessModifiers.clear();
if (Boolean.TRUE.equals(params.get(CreateTestParam.INC_PUBLIC))) {
methodAccessModifiers.add(Modifier.PUBLIC);
}
if (Boolean.TRUE.equals(params.get(CreateTestParam.INC_PROTECTED))) {
methodAccessModifiers.add(Modifier.PROTECTED);
}
testPkgPrivateMethods = Boolean.TRUE.equals(params.get(
CreateTestParam.INC_PKG_PRIVATE));
generateDefMethodBody = Boolean.TRUE.equals(params.get(
CreateTestParam.INC_METHOD_BODIES));
generateMethodJavadoc = Boolean.TRUE.equals(params.get(
CreateTestParam.INC_JAVADOC));
generateSourceCodeHints = Boolean.TRUE.equals(params.get(
CreateTestParam.INC_CODE_HINT));
generateTestSetUp = Boolean.TRUE.equals(params.get(
CreateTestParam.INC_SETUP));
generateTestTearDown = Boolean.TRUE.equals(params.get(
CreateTestParam.INC_TEAR_DOWN));
generateClassSetUp = Boolean.TRUE.equals(params.get(
CreateTestParam.INC_CLASS_SETUP));
generateClassTearDown = Boolean.TRUE.equals(params.get(
CreateTestParam.INC_CLASS_TEAR_DOWN));
generateMainMethod = settings.isGenerateMainMethod();
}
/**
* Loads default settings from <code>JUnitSettings</code>.
*/
private void loadDefaults() {
final JUnitSettings settings = JUnitSettings.getDefault();
skipTestClasses = JUnitSettings.GENERATE_TESTS_FROM_TEST_CLASSES;
skipPkgPrivateClasses = !settings.isIncludePackagePrivateClasses();
skipAbstractClasses = !settings.isGenerateAbstractImpl();
skipExceptionClasses = !settings.isGenerateExceptionClasses();
generateSuiteClasses = settings.isGenerateSuiteClasses();
methodAccessModifiers.clear();
if (settings.isMembersPublic()) {
methodAccessModifiers.add(Modifier.PUBLIC);
}
if (settings.isMembersProtected()) {
methodAccessModifiers.add(Modifier.PROTECTED);
}
testPkgPrivateMethods = settings.isMembersPackage();
generateDefMethodBody = settings.isBodyContent();
generateMethodJavadoc = settings.isJavaDoc();
generateSourceCodeHints = settings.isBodyComments();
generateTestSetUp = settings.isGenerateSetUp();
generateTestTearDown = settings.isGenerateTearDown();
generateClassSetUp = settings.isGenerateClassSetUp();
generateClassTearDown = settings.isGenerateClassTearDown();
generateMainMethod = settings.isGenerateMainMethod();
}
/**
* Sets whether tests for test classes should be generated
* The default is <code>true</code>.
*
* @param test <code>false</code> if test classes should be skipped
* during test creation;
* <code>true</code> otherwise
*/
public void setSkipTestClasses(boolean skip) {
this.skipTestClasses = skip;
}
/**
*/
public boolean isSkipTestClasses() {
return skipTestClasses;
}
/**
* Sets whether tests for package-private classes should be generated
* The default is <code>false</code>.
*
* @param test <code>false</code> if package-private classes should
* be skipped during test creation;
* <code>true</code> otherwise
*/
public void setSkipPackagePrivateClasses(boolean skip) {
this.skipPkgPrivateClasses = skip;
}
/**
*/
public boolean isSkipPackagePrivateClasses() {
return skipPkgPrivateClasses;
}
/**
* Sets whether tests for abstract classes should be generated
* The default is <code>false</code>.
*
* @param test <code>false</code> if abstract classes should be skipped
* during test creation;
* <code>true</code> otherwise
*/
public void setSkipAbstractClasses(boolean skip) {
this.skipAbstractClasses = skip;
}
/**
*/
public boolean isSkipAbstractClasses() {
return skipAbstractClasses;
}
/**
* Sets whether tests for exception classes should be generated
* The default is <code>false</code>.
*
* @param test <code>false</code> if exception classes should be skipped
* during test creation;
* <code>true</code> otherwise
*/
public void setSkipExceptionClasses(boolean skip) {
this.skipExceptionClasses = skip;
}
/**
*/
public boolean isSkipExceptionClasses() {
return skipExceptionClasses;
}
/**
* Sets whether test suite classes should be generated when creating tests
* for folders and/or packages.
*
* @param generate <code>true</code> if test suite classes should
* be generated; <code>false</code> otherwise
*/
public void setGenerateSuiteClasses(boolean generate) {
this.generateSuiteClasses = generate;
}
/**
*/
public boolean isGenerateSuiteClasses() {
return generateSuiteClasses;
}
/**
* Sets whether public methods should be tested or not.
* The default is <code>true</code>.
*
* @param test <code>true</code> if public methods should be tested;
* <code>false</code> if public methods should be skipped
*/
public void setTestPublicMethods(boolean test) {
if (test) {
methodAccessModifiers.add(Modifier.PUBLIC);
} else {
methodAccessModifiers.remove(Modifier.PUBLIC);
}
}
/**
*/
public boolean isTestPublicMethods() {
return methodAccessModifiers.contains(Modifier.PUBLIC);
}
/**
* Sets whether protected methods should be tested or not.
* The default is <code>true</code>.
*
* @param test <code>true</code> if protected methods should be tested;
* <code>false</code> if protected methods should be skipped
*/
public void setTestProtectedMethods(boolean test) {
if (test) {
methodAccessModifiers.add(Modifier.PROTECTED);
} else {
methodAccessModifiers.remove(Modifier.PROTECTED);
}
}
/**
*/
public boolean isTestProtectedMethods() {
return methodAccessModifiers.contains(Modifier.PROTECTED);
}
/**
* Tells which methods should be tested - public, protected, private
* or a combination of these.
*
* @return {@literal EnumSet} of access modifiers that determine which methods
* should be tested (for which test skeletons should be created).
*/
public EnumSet<Modifier> getMethodAccessModifiers() {
return EnumSet.copyOf(methodAccessModifiers);
}
/**
* Sets whether package-private methods should be tested or not.
* The default is <code>true</code>.
*
* @param test <code>true</code> if package-private methods should be
* tested;
* <code>false</code> if package-private methods should be
* skipped
*/
public void setTestPackagePrivateMethods(boolean test) {
this.testPkgPrivateMethods = test;
}
/**
*/
public boolean isTestPackagePrivateMethods() {
return testPkgPrivateMethods;
}
/**
* Sets whether default method bodies should be generated for newly created
* test methods.
* The default is <code>true</code>.
*
* @param generate <code>true</code> if default method bodies should
* be generated; <code>false</code> otherwise
*/
public void setGenerateDefMethodBody(boolean generate) {
this.generateDefMethodBody = generate;
}
/**
*/
public boolean isGenerateDefMethodBody() {
return generateDefMethodBody;
}
/**
* Sets whether Javadoc comment should be generated for newly created
* test methods.
* The default is <code>true</code>.
*
* @param generate <code>true</code> if Javadoc comment should
* be generated; <code>false</code> otherwise
*/
public void setGenerateMethodJavadoc(boolean generate) {
this.generateMethodJavadoc = generate;
}
/**
*/
public boolean isGenerateMethodJavadoc() {
return generateMethodJavadoc;
}
/**
* Sets whether method body comment should be generated for newly created
* test methods.
* The default is <code>true</code>.
*
* @param generate <code>true</code> if method body comment should
* be generated; <code>false</code> otherwise
*/
public void setGenerateMethodBodyComment(boolean generate) {
this.generateSourceCodeHints = generate;
}
/**
*/
public boolean isGenerateMethodBodyComment() {
return generateSourceCodeHints;
}
/**
* Sets whether <code>setUp()</code> method should be generated
* in test classes being created/updated.
* The default is <code>true</code>.
*
* @param generate <code>true</code> if <code>setUp()</code> method
* should be generated; <code>false</code> otherwise
* @see #setGenerateTearDown
* @see #setGenerateMainMethod
*/
public void setGenerateSetUp(boolean generate) {
this.generateTestSetUp = generate;
}
/**
*/
public boolean isGenerateSetUp() {
return generateTestSetUp;
}
/**
* Sets whether <code>tearDown()</code> method should be generated
* in test classes being created/updated.
* The default is <code>true</code>.
*
* @param generate <code>true</code> if <code>tearDown()</code> method
* should be generated; <code>false</code> otherwise
* @see #setGenerateSetUp
* @see #setGenerateMainMethod
*/
public void setGenerateTearDown(boolean generate) {
this.generateTestTearDown = generate;
}
/**
*/
public boolean isGenerateTearDown() {
return generateTestTearDown;
}
/**
* Sets whether <code>setUp()</code> method should be generated
* in test classes being created/updated.
* The default is <code>true</code>.
*
* @param generate <code>true</code> if <code>setUp()</code> method
* should be generated; <code>false</code> otherwise
* @see #setGenerateTearDown
* @see #setGenerateMainMethod
*/
public void setGenerateBefore(boolean generate) {
setGenerateSetUp(generate);
}
/**
*/
public boolean isGenerateBefore() {
return isGenerateSetUp();
}
/**
* Sets whether <code>tearDown()</code> method should be generated
* in test classes being created/updated.
* The default is <code>true</code>.
*
* @param generate <code>true</code> if <code>tearDown()</code> method
* should be generated; <code>false</code> otherwise
* @see #setGenerateSetUp
* @see #setGenerateMainMethod
*/
public void setGenerateAfter(boolean generate) {
setGenerateTearDown(generate);
}
/**
*/
public boolean isGenerateAfter() {
return isGenerateTearDown();
}
/**
* Sets whether <code>setUp()</code> method should be generated
* in test classes being created/updated.
* The default is <code>true</code>.
*
* @param generate <code>true</code> if <code>setUp()</code> method
* should be generated; <code>false</code> otherwise
* @see #setGenerateTearDown
* @see #setGenerateMainMethod
*/
public void setGenerateBeforeClass(boolean generate) {
this.generateClassSetUp = generate;
}
/**
*/
public boolean isGenerateBeforeClass() {
return generateClassSetUp;
}
/**
* Sets whether <code>tearDown()</code> method should be generated
* in test classes being created/updated.
* The default is <code>true</code>.
*
* @param generate <code>true</code> if <code>tearDown()</code> method
* should be generated; <code>false</code> otherwise
* @see #setGenerateSetUp
* @see #setGenerateMainMethod
*/
public void setGenerateAfterClass(boolean generate) {
this.generateClassTearDown = generate;
}
/**
*/
public boolean isGenerateAfterClass() {
return generateClassTearDown;
}
/**
* Sets whether static method <code>main(String args[])</code> should
* be generated in test classes.
* The default is <code>true</code>.
*
* @param generate <code>true</code> if the method should be generated;
* <code>false</code> otherwise
* @see #setGenerateSetUp
* @see #setGenerateTearDown
*/
public void setGenerateMainMethod(boolean generate) {
this.generateMainMethod = generate;
}
/**
*/
public boolean isGenerateMainMethod() {
return generateMainMethod;
}
/**
* Checks whether the given class or at least one of its nested classes
* is testable.
*
* @param compInfo used for {@link CompilationInfo#getElements()}
* and {@link CompilationInfo#getTypes()}
* @param classElem class to be checked
* @return TestabilityResult that isOk, if the class is testable or carries
* the information why the class is not testable
*/
public TestabilityResult isClassTestable(CompilationInfo compInfo,
TypeElement classElem, long skipTestabilityResultMask) {
assert classElem != null;
TestabilityResult result = isClassTestableSingle(compInfo, classElem, skipTestabilityResultMask);
if (result.isTestable()) {
return TestabilityResult.OK;
}
List<? extends Element> enclosedElems = classElem.getEnclosedElements();
if (enclosedElems.isEmpty()) {
/* Not testable, no contained types - no more chance: */
return result;
}
List<TypeElement> enclosedTypes = ElementFilter.typesIn(enclosedElems);
if (enclosedTypes.isEmpty()) {
/* Not testable, no contained types - no more chance: */
return result;
}
/* Not testable but maybe one of its nested classes is testable: */
List<TypeElement> stack
= new ArrayList<TypeElement>(Math.max(10, enclosedTypes.size()));
stack.addAll(enclosedTypes);
int stackSize = stack.size();
Set<TypeElement> nonTestable = new HashSet<TypeElement>(64);
nonTestable.add(classElem);
do {
TypeElement classToCheck = stack.remove(--stackSize);
if (!TopClassFinder.isTestable(classElem)) { //it is an annotation
continue;
}
if (!nonTestable.add(classToCheck)) {
continue; //we already know this single class is nontestable
}
TestabilityResult resultSingle
= isClassTestableSingle(compInfo, classToCheck, skipTestabilityResultMask);
if (resultSingle.isTestable()) {
return TestabilityResult.OK;
} else {
result = TestabilityResult.combine(result, resultSingle);
}
enclosedTypes = ElementFilter.typesIn(classToCheck.getEnclosedElements());
if (!enclosedTypes.isEmpty()) {
stack.addAll(enclosedTypes);
stackSize = stack.size();
}
} while (stackSize != 0);
/* So not a single contained class is testable - no more chance: */
return result;
}
/**
* Checks whether the given class is testable.
*
* @param jc class to be checked
* @return TestabilityResult that isOk, if the class is testable or carries
* the information why the class is not testable
*/
private TestabilityResult isClassTestableSingle(CompilationInfo compInfo,
TypeElement classElem, long skipTestabilityResultMask) {
assert classElem != null;
TestabilityResult result = TestabilityResult.OK;
/*
* If the class is a test class and test classes should be skipped,
* do not check nested classes (skip all):
*/
/* Check if the class itself (w/o nested classes) is testable: */
Set<Modifier> modifiers = classElem.getModifiers();
if (modifiers.contains(PRIVATE))
result = TestabilityResult.combine(result, TestabilityResult.PRIVATE_CLASS);
if (isSkipTestClasses() && JUnitTestUtil.isClassImplementingTestInterface(compInfo, classElem))
result = TestabilityResult.combine(result, TestabilityResult.TEST_CLASS);
if (isSkipPackagePrivateClasses() && (modifiers.isEmpty() || !EnumSet.copyOf(modifiers).removeAll(ACCESS_MODIFIERS)))
result = TestabilityResult.combine(result, TestabilityResult.PACKAGE_PRIVATE_CLASS);
if (isSkipAbstractClasses() && modifiers.contains(ABSTRACT))
result = TestabilityResult.combine(result, TestabilityResult.ABSTRACT_CLASS);
if (!modifiers.contains(STATIC) && (classElem.getNestingKind() != NestingKind.TOP_LEVEL))
result = TestabilityResult.combine(result, TestabilityResult.NONSTATIC_INNER_CLASS);
// #175201
// if (!hasTestableMethods(classElem))
// result = TestabilityResult.combine(result, TestabilityResult.NO_TESTEABLE_METHODS);
if (isSkipExceptionClasses() && JUnitTestUtil.isClassException(compInfo, classElem))
result = TestabilityResult.combine(result, TestabilityResult.EXCEPTION_CLASS);
//apply filter mask
result = TestabilityResult.filter(result, skipTestabilityResultMask);
return result;
}
/**
*/
private boolean hasTestableMethods(TypeElement classElem) {
List<? extends Element> enclosedElems = classElem.getEnclosedElements();
if (enclosedElems.isEmpty()) {
return false;
}
List<ExecutableElement> methods = ElementFilter.methodsIn(enclosedElems);
if (methods.isEmpty()) {
return false;
}
for (ExecutableElement method : methods) {
if (isMethodTestable(method)) {
return true;
}
}
return false;
}
/**
* Checks whether a test for the given method should be created.
* Access modifiers of the given method are compared to this creator's
* settings.
*
* @param m method to be checked
* @return <code>true</code> if this creator is configured to create tests
* for methods having the given method's access modifiers;
* <code>false</code> otherwise
*/
public boolean isMethodTestable(ExecutableElement method) {
Set<Modifier> modifiers = method.getModifiers();
if (modifiers.isEmpty()) {
/*
* EnumSet.copyOf(modifiers) may throw an exception if 'modifiers'
* is empty.
*/
return isTestPackagePrivateMethods();
} else {
return (isTestPackagePrivateMethods()
&& !EnumSet.copyOf(modifiers).removeAll(ACCESS_MODIFIERS))
|| EnumSet.copyOf(modifiers).removeAll(getMethodAccessModifiers());
}
}
}