| /* |
| * 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.testng; |
| |
| import java.io.IOException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.lang.model.element.TypeElement; |
| import org.netbeans.api.actions.Savable; |
| import org.netbeans.api.java.classpath.ClassPath; |
| import static org.netbeans.api.java.classpath.ClassPath.SOURCE; |
| import static org.netbeans.api.java.project.JavaProjectConstants.SOURCES_TYPE_JAVA; |
| import org.netbeans.api.java.queries.UnitTestForSourceQuery; |
| import org.netbeans.api.java.source.ClasspathInfo; |
| import org.netbeans.api.java.source.ClasspathInfo.PathKind; |
| import org.netbeans.api.java.source.ElementHandle; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.project.FileOwnerQuery; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.api.project.ProjectUtils; |
| import org.netbeans.api.project.SourceGroup; |
| import org.netbeans.api.project.Sources; |
| import org.netbeans.api.queries.VisibilityQuery; |
| import org.netbeans.modules.gsf.testrunner.plugin.CommonPlugin.CreateTestParam; |
| import org.netbeans.modules.gsf.testrunner.plugin.CommonPlugin.Location; |
| import org.netbeans.modules.testng.TestabilityResult.SkippedClass; |
| import org.netbeans.spi.java.classpath.support.ClassPathSupport; |
| import org.openide.DialogDisplayer; |
| import org.openide.ErrorManager; |
| import org.openide.NotifyDescriptor; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.loaders.DataFolder; |
| import org.openide.loaders.DataObject; |
| import org.openide.loaders.DataObjectNotFoundException; |
| import org.openide.util.Mutex; |
| import org.openide.util.NbBundle; |
| import org.openide.util.NbBundle.Messages; |
| |
| /** |
| * Default TestNG plugin. |
| * |
| * @author Marian Petras |
| */ |
| public final class DefaultPlugin extends TestNGPlugin { |
| |
| /** name of FreeMarker template property - generate {@code @BeforeClass} method? */ |
| private static final String templatePropBeforeClass = "classSetUp"; //NOI18N |
| /** name of FreeMarker template property - generate {@code @AfterClass} method? */ |
| private static final String templatePropAfterClass = "classTearDown";//NOI18N |
| /** name of FreeMarker template property - generate {@code @Before} method? */ |
| private static final String templatePropBefore = "methodSetUp"; //NOI18N |
| /** name of FreeMarker template property - generate {@code @After} method? */ |
| private static final String templatePropAfter = "methodTearDown"; //NOI18N |
| /** name of FreeMarker template property - generate in-method source code hints? */ |
| private static final String templatePropCodeHints = "sourceCodeHint"; //NOI18N |
| /** name of FreeMarker template property - generate hints - method placeholders? */ |
| private static final String templatePropMethodPH = "testMethodsPlaceholder"; //NOI18N |
| /** name of FreeMarker template property - use Java annotations? */ |
| private static final String templatePropUseAnnotations = "useAnnotations"; //NOI18N |
| /** name of FreeMarker template property - list of class names */ |
| private static final String templatePropClassNames = "classNames"; //NOI18N |
| /** |
| * name of FreeMarker template property - list of class names, |
| * each with a suffix <code>".class"</code> |
| */ |
| private static final String templatePropClasses = "classes"; //NOI18N |
| private static final String NGPrefix = "NG"; //NOI18N |
| |
| |
| /** |
| * |
| */ |
| @Override |
| protected boolean canCreateTests(FileObject... fileObjects) { |
| if (fileObjects.length == 0) { |
| return false; |
| } |
| |
| final FileObject firstFile = fileObjects[0]; |
| final SourceGroup sourceGroup = findSourceGroup(firstFile); |
| if (sourceGroup == null) { |
| return false; |
| } |
| final FileObject rootFolder = sourceGroup.getRootFolder(); |
| if (UnitTestForSourceQuery.findUnitTests(rootFolder).length == 0) { |
| return false; |
| } |
| |
| /* |
| * Now we know that source folder of the first file has a corresponding |
| * test folder (possible non-existent). |
| */ |
| if (fileObjects.length == 1) { |
| /* ... if there is just one file selected, it is all we need: */ |
| return true; |
| } |
| |
| /* |
| * ...for multiple files, we just check that all the selected files |
| * have the same root folder: |
| */ |
| for (int i = 1; i < fileObjects.length; i++) { |
| FileObject fileObj = fileObjects[i]; |
| if (!FileUtil.isParentOf(rootFolder, fileObj) |
| || !sourceGroup.contains(fileObj)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Finds a Java source group the given file belongs to. |
| * |
| * @param file {@code FileObject} to find a {@code SourceGroup} for |
| * @return the found {@code SourceGroup}, or {@code null} if the given |
| * file does not belong to any Java source group |
| */ |
| private static SourceGroup findSourceGroup(FileObject file) { |
| final Project project = FileOwnerQuery.getOwner(file); |
| if (project == null) { |
| return null; |
| } |
| |
| Sources src = ProjectUtils.getSources(project); |
| SourceGroup[] srcGrps = src.getSourceGroups(SOURCES_TYPE_JAVA); |
| for (SourceGroup srcGrp : srcGrps) { |
| FileObject rootFolder = srcGrp.getRootFolder(); |
| if (((file == rootFolder) || FileUtil.isParentOf(rootFolder, file)) |
| && srcGrp.contains(file)) { |
| return srcGrp; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * |
| */ |
| protected Location getTestLocation(Location sourceLocation) { |
| FileObject fileObj = sourceLocation.getFileObject(); |
| ClassPath srcCp; |
| |
| if ((srcCp = ClassPath.getClassPath(fileObj, SOURCE)) == null) { |
| return null; |
| } |
| |
| String baseResName = srcCp.getResourceName(fileObj, '/', false); |
| if(baseResName == null) { |
| return null; |
| } |
| String testResName = !fileObj.isFolder() |
| ? getTestResName(baseResName, fileObj.getExt()) |
| : getSuiteResName(baseResName); |
| assert testResName != null; |
| |
| return getOppositeLocation(sourceLocation, |
| srcCp, |
| testResName, |
| true); |
| } |
| |
| /** |
| * |
| */ |
| protected Location getTestedLocation(Location testLocation) { |
| FileObject fileObj = testLocation.getFileObject(); |
| ClassPath srcCp; |
| |
| if (fileObj.isFolder() |
| || ((srcCp = ClassPath.getClassPath(fileObj, SOURCE)) == null)) { |
| return null; |
| } |
| |
| String baseResName = srcCp.getResourceName(fileObj, '/', false); |
| if (baseResName == null) { |
| return null; |
| } |
| String srcResName = getSrcResName(baseResName, fileObj.getExt()); |
| if (srcResName == null) { |
| return null; //if the selectedFO is not a test class (by name) |
| } |
| |
| return getOppositeLocation(testLocation, |
| srcCp, |
| srcResName, |
| false); |
| } |
| |
| /** |
| * |
| */ |
| private static Location getOppositeLocation( |
| final Location sourceLocation, |
| final ClassPath fileObjCp, |
| final String oppoResourceName, |
| final boolean sourceToTest) { |
| FileObject fileObj = sourceLocation.getFileObject(); |
| FileObject fileObjRoot; |
| |
| if ((fileObjRoot = fileObjCp.findOwnerRoot(fileObj)) == null) { |
| return null; |
| } |
| |
| URL[] oppoRootsURLs = sourceToTest |
| ? UnitTestForSourceQuery.findUnitTests(fileObjRoot) |
| : UnitTestForSourceQuery.findSources(fileObjRoot); |
| //if (sourceToTest && (oppoRootsURLs.length == 0)) { |
| // PENDING - offer creation of new unit tests root |
| //} |
| if ((oppoRootsURLs == null) || (oppoRootsURLs.length == 0)) { |
| return null; |
| } |
| |
| ClassPath oppoRootsClassPath = ClassPathSupport |
| .createClassPath(oppoRootsURLs); |
| final List<FileObject> oppoFiles = oppoRootsClassPath |
| .findAllResources(oppoResourceName); |
| if (oppoFiles.isEmpty()) { |
| //if (sourceToTest) { |
| // PENDING - offer creation of new test class |
| //} |
| return null; |
| } |
| |
| // final ElementHandle elementHandle = sourceLocation.getElementHandle(); |
| // if (elementHandle == null) { |
| return new Location(oppoFiles.get(0)/*, null*/); |
| // } |
| |
| // /* Build SOURCE classpath: */ |
| // ClassPath[] srcCpDelegates = new ClassPath[2]; |
| // if (sourceToTest) { |
| // srcCpDelegates[0] = fileObjCp; |
| // srcCpDelegates[1] = oppoRootsClassPath; |
| // } else { |
| // srcCpDelegates[0] = oppoRootsClassPath; |
| // srcCpDelegates[1] = fileObjCp; |
| // } |
| // ClassPath srcClassPath |
| // = ClassPathSupport.createProxyClassPath(srcCpDelegates); |
| // |
| // /* Build COMPILE classpath: */ |
| // FileObject[] oppoRoots = oppoRootsClassPath.getRoots(); |
| // ClassPath[] compCpDelegates = new ClassPath[oppoRoots.length + 1]; |
| // int delegateIndex = 0; |
| // if (sourceToTest) { |
| // compCpDelegates[delegateIndex++] |
| // = ClassPath.getClassPath(fileObjRoot, COMPILE); |
| // } |
| // for (FileObject oppoRoot : oppoRoots) { |
| // compCpDelegates[delegateIndex++] |
| // = ClassPath.getClassPath(oppoRoot, COMPILE); |
| // } |
| // if (!sourceToTest) { |
| // compCpDelegates[delegateIndex++] |
| // = ClassPath.getClassPath(fileObjRoot, COMPILE); |
| // } |
| // ClassPath compClassPath |
| // = ClassPathSupport.createProxyClassPath(compCpDelegates); |
| // |
| // /* Obtain the BOOT classpath: */ |
| // ClassPath bootClassPath = ClassPath.getClassPath(fileObj, BOOT); |
| // |
| // ClasspathInfo cpInfo = ClasspathInfo.create(bootClassPath, |
| // compClassPath, |
| // srcClassPath); |
| // List<FileObject> files = new ArrayList<FileObject>(oppoFiles.size() + 1); |
| // files.add(fileObj); |
| // files.addAll(oppoFiles); |
| // JavaSource javaSource = JavaSource.create(cpInfo, files); |
| // |
| // try { |
| // MatchFinder matchFinder = new MatchFinder(sourceLocation, |
| // oppoFiles, |
| // sourceToTest); |
| // javaSource.runUserActionTask(matchFinder, true); |
| // return matchFinder.getResult(); |
| // } catch (IOException ex) { |
| // Logger.getLogger("global").log(Level.SEVERE, null, ex); //NOI18N |
| // return null; |
| // } |
| } |
| |
| |
| /** |
| */ |
| private static String getTestResName(String baseResName, String ext) { |
| StringBuilder buf |
| = new StringBuilder(baseResName.length() + ext.length() + 10); |
| buf.append(baseResName).append(NGPrefix + "Test"); //NOI18N |
| if (ext.length() != 0) { |
| buf.append('.').append(ext); |
| } |
| return buf.toString(); |
| } |
| |
| /** |
| */ |
| private static String getSuiteResName(String baseResName) { |
| if (baseResName.length() == 0) { |
| return TestNGSettings.getDefault().getRootSuiteClassName(); |
| } |
| |
| final String suiteSuffix = "Suite"; //NOI18N |
| |
| String lastNamePart |
| = baseResName.substring(baseResName.lastIndexOf('/') + 1); |
| |
| StringBuilder buf = new StringBuilder(baseResName.length() |
| + lastNamePart.length() |
| + suiteSuffix.length() |
| + 6); |
| buf.append(baseResName).append('/'); |
| buf.append(Character.toUpperCase(lastNamePart.charAt(0))) |
| .append(lastNamePart.substring(1)); |
| buf.append(suiteSuffix); |
| buf.append(".java"); //NOI18N |
| |
| return buf.toString(); |
| } |
| |
| /** |
| */ |
| private static String getSrcResName(String testResName, String ext) { |
| if (!testResName.endsWith(NGPrefix + "Test")) { //NOI18N |
| return null; |
| } |
| |
| StringBuilder buf |
| = new StringBuilder(testResName.length() + ext.length()); |
| buf.append(testResName.substring(0, testResName.length() - 6)); |
| if (ext.length() != 0) { |
| buf.append('.').append(ext); |
| } |
| return buf.toString(); |
| } |
| |
| /** |
| */ |
| private static String getTestClassName(String baseClassName) { |
| return baseClassName + NGPrefix + "Test"; //NOI18N |
| } |
| |
| /** |
| */ |
| private static String getSourceClassName(String testClassName) { |
| final String suffix = "Test"; //NOI18N |
| final int suffixLen = suffix.length(); |
| |
| return ((testClassName.length() > suffixLen) |
| && testClassName.endsWith(suffix)) |
| ? testClassName.substring(0, testClassName.length() - suffixLen) |
| : null; |
| } |
| |
| /** |
| */ |
| private static String getTestMethodName(String baseMethodName) { |
| final String prefix = "test"; //NOI18N |
| final int prefixLen = prefix.length(); |
| |
| StringBuffer buf = new StringBuffer(prefixLen |
| + baseMethodName.length()); |
| buf.append(prefix).append(baseMethodName); |
| buf.setCharAt(prefixLen, |
| Character.toUpperCase(baseMethodName.charAt(0))); |
| return buf.toString(); |
| } |
| |
| /** |
| */ |
| private static String getSourceMethodName(String testMethodName) { |
| final String prefix = "test"; //NOI18N |
| final int prefixLen = prefix.length(); |
| |
| return ((testMethodName.length() > prefixLen) |
| && testMethodName.startsWith(prefix)) |
| ? new StringBuilder(testMethodName.length() - prefixLen) |
| .append(Character.toLowerCase(testMethodName.charAt(prefixLen))) |
| .append(testMethodName.substring(prefixLen + 1)) |
| .toString() |
| : null; |
| } |
| |
| /** |
| * Creates test classes for given source classes. |
| * |
| * @param filesToTest source files for which test classes should be |
| * created |
| * @param targetRoot root folder of the target source root |
| * @param params parameters of creating test class |
| * @return created test files |
| */ |
| @Messages({"MSG_StatusBar_CreateTest_Begin=Creating tests ...", |
| "# {0} - skipped class name", |
| "# {1} - reason for skipping", |
| "MSG_skipped_class=Class {0} was skipped because it was {1}.", |
| "# {0} - reason for skipping the classes", |
| "MSG_skipped_classes=Some classes were skipped because they were {0}.", |
| "MSG_No_test_created=No tests were created because no testable class was found.", |
| "PROP_testng_testClassTemplate=Templates/UnitTests/EmptyTestNGTest.java", |
| "PROP_testng_testSuiteTemplate=Templates/UnitTests/TestNGSuite.xml"}) |
| @Override |
| protected FileObject[] createTests( |
| final FileObject[] filesToTest, |
| final FileObject targetRoot, |
| final Map<CreateTestParam, Object> params) { |
| //XXX: not documented that in case that if filesToTest is <null>, |
| //the target root param works as a target folder |
| |
| ProgressIndicator progress = new ProgressIndicator(); |
| progress.show(); |
| |
| String msg = Bundle.MSG_StatusBar_CreateTest_Begin(); //NOI18N |
| progress.displayStatusText(msg); |
| |
| final TestCreator testCreator = new TestCreator(params); |
| |
| CreationResults results; |
| try { |
| final String templateId; |
| final String suiteTemplateId; |
| boolean forTestSuite = (filesToTest != null) |
| && (filesToTest.length != 0) |
| && ((filesToTest.length > 1) || !filesToTest[0].isData()); |
| templateId = Bundle.PROP_testng_testClassTemplate(); |
| suiteTemplateId = forTestSuite |
| ? Bundle.PROP_testng_testSuiteTemplate() |
| : null; |
| DataObject doTestTempl = (templateId != null) |
| ? loadTestTemplate(templateId) |
| : null; |
| if (doTestTempl == null) { |
| return null; |
| } |
| DataObject doSuiteTempl = (suiteTemplateId != null) |
| ? loadTestTemplate(suiteTemplateId) |
| : null; |
| if (forTestSuite && (doSuiteTempl == null)) { |
| return null; |
| } |
| |
| Map<String, Boolean> templateParams = createTemplateParams(params); |
| setAnnotationsSupport(targetRoot, templateParams); |
| |
| if ((filesToTest == null) || (filesToTest.length == 0)) { |
| //XXX: Not documented that filesToTest may be <null> |
| |
| addTemplateParamEntry(params, CreateTestParam.INC_CODE_HINT, |
| templateParams, templatePropMethodPH); |
| |
| String testClassName = (String) params.get(CreateTestParam.CLASS_NAME); |
| assert testClassName != null; |
| results = new CreationResults(1); |
| DataObject testDataObj = createEmptyTest(targetRoot, |
| testClassName, |
| testCreator, |
| templateParams, |
| doTestTempl); |
| if (testDataObj != null) { |
| results.addCreated(testDataObj); |
| } |
| |
| } else { |
| ClassPath testClassPath = ClassPathSupport.createClassPath( |
| new FileObject[] {targetRoot}); |
| if (!forTestSuite) { |
| String testClassName = (String) params.get(CreateTestParam.CLASS_NAME); |
| if (testClassName == null) { |
| String srcClassName |
| = ClassPath.getClassPath(filesToTest[0], SOURCE) |
| .getResourceName(filesToTest[0], '.', false); |
| testClassName = getTestClassName(srcClassName); |
| } |
| try { |
| results = createSingleTest( |
| filesToTest[0], |
| testClassName, |
| testCreator, |
| templateParams, |
| doTestTempl, |
| testClassPath, |
| TestabilityResult.NO_TESTEABLE_METHODS.getReasonValue(), |
| null, //parent suite |
| progress); |
| } catch (CreationError ex) { |
| ErrorManager.getDefault().notify(ex); |
| results = new CreationResults(1); |
| } |
| } else { |
| results = new CreationResults(); |
| |
| // go through all nodes |
| for (FileObject fileToTest : filesToTest) { |
| try { |
| results.combine(createTests(fileToTest, |
| testCreator, |
| templateParams, |
| doTestTempl, |
| doSuiteTempl, |
| testClassPath, |
| null, |
| progress)); |
| } catch (CreationError e) { |
| ErrorManager.getDefault().notify(e); |
| } |
| } |
| } |
| } |
| } finally { |
| progress.hide(); |
| } |
| |
| final Set<SkippedClass> skipped = results.getSkipped(); |
| final Set<DataObject> created = results.getCreated(); |
| if (!skipped.isEmpty()) { |
| // something was skipped |
| String message; |
| if (skipped.size() == 1) { |
| // one class? report it |
| SkippedClass skippedClass = skipped.iterator().next(); |
| message = Bundle.MSG_skipped_class(skippedClass.clsName, strReason(skippedClass.reason, "COMMA", "AND")); //NOI18N |
| } else { |
| // more classes, report a general error |
| // combine the results |
| TestabilityResult reason = TestabilityResult.OK; |
| for (SkippedClass sc : skipped) { |
| reason = TestabilityResult.combine(reason, sc.reason); |
| } |
| |
| message = Bundle.MSG_skipped_classes(strReason(reason, "COMMA", "OR")); //NOI18N |
| } |
| TestUtil.notifyUser(message, NotifyDescriptor.INFORMATION_MESSAGE); |
| |
| } |
| |
| if (created.isEmpty()) { |
| Mutex.EVENT.writeAccess(new Runnable() { |
| public void run() { |
| TestUtil.notifyUser( |
| Bundle.MSG_No_test_created(), |
| NotifyDescriptor.INFORMATION_MESSAGE); |
| } |
| }); |
| } |
| |
| FileObject[] createdFiles; |
| if (created.isEmpty()) { |
| createdFiles = new FileObject[0]; |
| } else { |
| createdFiles = new FileObject[created.size()]; |
| int i = 0; |
| for (DataObject dObj : created) { |
| createdFiles[i++] = dObj.getPrimaryFile(); |
| } |
| } |
| return createdFiles; |
| } |
| |
| /** |
| * Create a map of FreeMaker template parameters from a map |
| * of {@code CreateTestParam}s. |
| */ |
| public static final Map<String, Boolean> createTemplateParams( |
| Map<CreateTestParam, Object> params) { |
| Map<String,Boolean> result = new HashMap<String,Boolean>(7); |
| |
| addTemplateParamEntry(params, CreateTestParam.INC_CLASS_SETUP, |
| result, templatePropBeforeClass); |
| addTemplateParamEntry(params, CreateTestParam.INC_CLASS_TEAR_DOWN, |
| result, templatePropAfterClass); |
| addTemplateParamEntry(params, CreateTestParam.INC_SETUP, |
| result, templatePropBefore); |
| addTemplateParamEntry(params, CreateTestParam.INC_TEAR_DOWN, |
| result, templatePropAfter); |
| addTemplateParamEntry(params, CreateTestParam.INC_CODE_HINT, |
| result, templatePropCodeHints); |
| |
| return result; |
| } |
| |
| private static void addTemplateParamEntry(Map<CreateTestParam, Object> srcParams, |
| CreateTestParam srcParamKey, |
| Map<String, Boolean> templParams, |
| String templParamKey) { |
| Object value = srcParams.get(srcParamKey); |
| if (value instanceof Boolean) { |
| templParams.put(templParamKey, Boolean.class.cast(value)); |
| } |
| } |
| |
| /** |
| * Creates a new test class. |
| * |
| * @param targetRoot <!-- //PENDING --> |
| * @param testClassName <!-- //PENDING --> |
| * @param testCreator {@code TestCreator} to be used for filling |
| * the test class template |
| * @param templateDataObj {@code DataObject} representing |
| * the test file template |
| * @return the created test, or {@code null} if no test was created |
| */ |
| private DataObject createEmptyTest(FileObject targetRoot, |
| String testClassName, |
| TestCreator testCreator, |
| final Map<String, ? extends Object> templateParams, |
| DataObject templateDataObj) { |
| if (testClassName == null) { |
| throw new IllegalArgumentException("testClassName = null"); //NOI18N |
| } |
| |
| DataObject testDataObj = null; |
| try { |
| DataFolder targetFolderDataObj = DataFolder.findFolder(targetRoot); |
| testDataObj = templateDataObj.createFromTemplate( |
| targetFolderDataObj, |
| testClassName, |
| templateParams); |
| |
| /* fill in setup etc. according to dialog settings */ |
| testCreator.createEmptyTest(testDataObj.getPrimaryFile()); |
| } catch (IOException ex) { |
| ErrorManager.getDefault().notify(ex); |
| } |
| return testDataObj; |
| } |
| |
| /** |
| * |
| */ |
| private static CreationResults createSingleTest( |
| FileObject sourceFile, |
| String requestedTestClassName, |
| final TestCreator testCreator, |
| final Map<String, ? extends Object> templateParams, |
| DataObject templateDataObj, |
| ClassPath testClassPath, |
| long skipTestabilityResultMask, |
| List<String> parentSuite, |
| ProgressIndicator progress) throws CreationError { |
| |
| List<SkippedClass> nonTestable; |
| List<ElementHandle<TypeElement>> testable; |
| try { |
| JavaSource javaSource = JavaSource.forFileObject(sourceFile); |
| //issue 161598 |
| if (javaSource == null) |
| return CreationResults.EMPTY; |
| if (skipTestabilityResultMask != 0) { |
| nonTestable = new ArrayList<SkippedClass>(); |
| testable = TopClassFinder.findTestableTopClasses(javaSource, |
| testCreator, |
| nonTestable, |
| skipTestabilityResultMask); |
| } else { |
| nonTestable = Collections.<SkippedClass>emptyList(); |
| testable = TopClassFinder.findTopClasses(javaSource); |
| } |
| } catch (IOException ex) { |
| throw new CreationError(ex); |
| } |
| |
| CreationResults result = new CreationResults(4); |
| if (!nonTestable.isEmpty()) { |
| result.addSkipped(nonTestable); |
| } |
| if (!testable.isEmpty()) { |
| /* used only if (requestedTestClassName != null): */ |
| boolean mainClassProcessed = false; |
| |
| try { |
| for (ElementHandle<TypeElement> clsToTest : testable) { |
| String testClassName; |
| String srcClassNameFull = clsToTest.getQualifiedName(); |
| if ((requestedTestClassName != null) |
| && !mainClassProcessed |
| && TestUtil.getSimpleName(srcClassNameFull) |
| .equals(sourceFile.getName())) { |
| testClassName = requestedTestClassName; |
| mainClassProcessed = true; |
| } else { |
| testClassName = TestUtil.getTestClassName(srcClassNameFull.concat(NGPrefix)); |
| } |
| String testResourceName = testClassName.replace('.', '/'); |
| |
| /* find or create the test class DataObject: */ |
| DataObject testDataObj = null; |
| FileObject testFile = testClassPath.findResource( |
| testResourceName + ".java");//NOI18N |
| boolean isNew = (testFile == null); |
| if (testFile == null) { |
| testDataObj = createTestClass(testClassPath, |
| testResourceName, |
| templateDataObj, |
| templateParams); |
| testFile = testDataObj.getPrimaryFile(); |
| } |
| |
| testCreator.createSimpleTest(clsToTest, testFile, isNew); |
| if (testDataObj == null) { |
| testDataObj = DataObject.find(testFile); |
| } |
| save(testDataObj); |
| |
| result.addCreated(testDataObj); |
| // add the test class to the parent's suite |
| if (parentSuite != null) { |
| parentSuite.add(testClassName); |
| } |
| } |
| } catch (IOException ex) { //incl. DataObjectNotFoundException |
| throw new CreationError(ex); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * |
| */ |
| private static CreationResults createTests( |
| final FileObject srcFileObj, |
| final TestCreator testCreator, |
| final Map<String, ? extends Object> templateParams, |
| DataObject doTestT, |
| DataObject doSuiteT, |
| final ClassPath testClassPath, |
| List<String> parentSuite, |
| ProgressIndicator progress) throws CreationError { |
| |
| CreationResults results; |
| if (srcFileObj.isFolder()) { |
| results = new CreationResults(); |
| |
| List<String> mySuite = new LinkedList<String>(); |
| |
| progress.setMessage(getScanningMsg(srcFileObj.getName())); |
| |
| for (FileObject childFileObj : srcFileObj.getChildren()) { |
| if (progress.isCanceled()) { |
| results.setAbborted(); |
| break; |
| } |
| if (!VisibilityQuery.getDefault().isVisible(childFileObj)) { |
| continue; |
| } |
| results.combine(createTests(childFileObj, |
| testCreator, |
| templateParams, |
| doTestT, |
| doSuiteT, |
| testClassPath, |
| mySuite, |
| progress)); |
| if (results.isAbborted()) { |
| break; |
| } |
| } |
| |
| // if everything went ok, and the option is enabled, |
| // create a suite for the folder . |
| if (!results.isAbborted() |
| // && !mySuite.isEmpty() |
| && TestNGSettings.getDefault().isGenerateSuiteClasses()) { |
| createSuiteTest(srcFileObj, |
| (String) null, |
| testCreator, |
| templateParams, |
| doSuiteT, |
| testClassPath, |
| mySuite, |
| parentSuite, |
| progress); |
| } |
| } else if (srcFileObj.isData() && TestUtil.isJavaFile(srcFileObj)) { |
| results = createSingleTest(srcFileObj, |
| (String) null, //use the default clsName |
| testCreator, |
| templateParams, |
| doTestT, |
| testClassPath, |
| TestabilityResult.NO_TESTEABLE_METHODS.getReasonValue(), |
| parentSuite, |
| progress); |
| } else { |
| results = CreationResults.EMPTY; |
| } |
| return results; |
| } |
| |
| /** |
| * |
| */ |
| private static DataObject createSuiteTest( |
| FileObject folder, |
| String suiteName, |
| final TestCreator testCreator, |
| final Map<String, ? extends Object> templateParams, |
| DataObject templateDataObj, |
| ClassPath testClassPath, |
| List<String> classesToInclude, |
| List<String> parentSuite, |
| ProgressIndicator progress) throws CreationError { |
| |
| // find correct package name |
| ClassPath cp = ClassPath.getClassPath(folder, SOURCE); |
| assert cp != null : "SOURCE classpath was not found for " + folder; //NOI18N |
| if (cp == null) { |
| return null; |
| } |
| |
| String pkg = cp.getResourceName(folder, '/', false); |
| if (pkg.length() > 0) { |
| // #176958 : append the rootFolderName in the fullSuiteName, so that we can decide in which Classpath root folder |
| // the new Suite file will be generated, when createTestClass(...) is called later on |
| String rootFolderName = ""; //NOI18N |
| if (cp.getRoots().length > 1) { |
| FileObject rootOwnerFO = cp.findOwnerRoot(folder); |
| if (rootOwnerFO != null) { |
| rootFolderName = rootOwnerFO.getName() + '/'; |
| } |
| } |
| String dotPkg = pkg.replace('/', '.'); |
| String fullSuiteName = rootFolderName.concat((suiteName != null) |
| ? pkg + '/' + suiteName |
| : TestUtil.convertPackage2SuiteName(pkg)); |
| |
| // String classNames = makeListOfClasses(classesToInclude, null); |
| // String classes = makeListOfClasses(classesToInclude, ".class"); //NOI18N |
| |
| final Map<String, Object> suiteTemplParams = new HashMap<String, Object>(templateParams); |
| // suiteTemplParams.put(templatePropClassNames, classNames); |
| // suiteTemplParams.put(templatePropClasses, classes); |
| String projectName = ProjectUtils.getInformation(FileOwnerQuery.getOwner(folder)).getName(); |
| suiteTemplParams.put("suiteName", projectName); |
| suiteTemplParams.put("testName", dotPkg.concat(" suite")); |
| suiteTemplParams.put("pkg", dotPkg); |
| |
| try { |
| /* |
| * find or create the test class DataObject: |
| */ |
| DataObject testDataObj = null; |
| FileObject testFile = testClassPath.findResource( |
| fullSuiteName + ".xml"); //NOI18N |
| boolean isNew = (testFile == null); |
| if (testFile == null) { |
| testDataObj = createTestClass(testClassPath, |
| fullSuiteName, |
| templateDataObj, |
| suiteTemplParams); |
| testFile = testDataObj.getPrimaryFile(); |
| } |
| |
| try { |
| if (testDataObj == null) { |
| testDataObj = DataObject.find(testFile); |
| } |
| save(testDataObj); |
| } catch (Exception ex) { |
| ErrorManager.getDefault().notify(ErrorManager.ERROR, ex); |
| return null; |
| } |
| return testDataObj; |
| } catch (IOException ioe) { |
| throw new CreationError(ioe); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * |
| */ |
| public DataObject createSuiteTest( |
| final FileObject targetRootFolder, |
| final FileObject targetFolder, |
| final String suiteName, |
| final Map<CreateTestParam, Object> params) { |
| final Map<String, Boolean> templateParams = createTemplateParams(params); |
| setAnnotationsSupport(targetFolder, templateParams); |
| TestCreator testCreator = new TestCreator(params); |
| final ClasspathInfo cpInfo = ClasspathInfo.create(targetRootFolder); |
| List<String> testClassNames = TestUtil.getJavaFileNames(targetFolder, |
| cpInfo); |
| |
| final String templateId = Bundle.PROP_testng_testSuiteTemplate(); |
| |
| final DataObject doSuiteTempl |
| = loadTestTemplate(templateId); |
| if (doSuiteTempl == null) { |
| return null; |
| } |
| |
| DataObject suiteDataObj; |
| try { |
| return createSuiteTest(targetFolder, |
| suiteName, |
| testCreator, |
| templateParams, |
| doSuiteTempl, |
| cpInfo.getClassPath(PathKind.SOURCE), |
| new LinkedList<String>(testClassNames), |
| null, //parent suite |
| null); //progress indicator |
| } catch (CreationError ex) { |
| return null; |
| } |
| } |
| |
| /** |
| */ |
| private static DataObject createTestClass( |
| ClassPath cp, |
| String testClassName, |
| DataObject templateDataObj, |
| final Map<String, ? extends Object> templateParams) |
| throws DataObjectNotFoundException, |
| IOException { |
| |
| FileObject root = cp.getRoots()[0]; |
| // #176958 : decide in which Classpath root folder the new Suite file will be generated |
| String rootFolderName = ""; //NOI18N |
| if (cp.getRoots().length > 1) { |
| int indexFirst = testClassName.indexOf('/'); |
| int indexLast = testClassName.lastIndexOf('/'); |
| assert indexFirst != indexLast : // this should not happen |
| "ClassPath=" + cp + "\n" + "testClassName=" + testClassName; //NOI18N |
| rootFolderName = testClassName.substring(0, indexFirst); |
| testClassName = testClassName.substring(indexLast + 1); |
| for (int i = 0; i < cp.getRoots().length; i++) { |
| FileObject rootFO = cp.getRoots()[i]; |
| if (rootFO.getPath().endsWith(rootFolderName)) { |
| root = rootFO; |
| } |
| } |
| } |
| int index = testClassName.lastIndexOf('/'); |
| String pkg = index > -1 ? testClassName.substring(0, index) |
| : ""; //NOI18N |
| String clazz = index > -1 ? testClassName.substring(index+1) |
| : testClassName; |
| |
| // create package if it does not exist |
| if (pkg.length() > 0) { |
| root = FileUtil.createFolder(root, pkg); //IOException |
| } |
| // instantiate template into the package |
| return templateDataObj.createFromTemplate( //IOException |
| DataFolder.findFolder(root), |
| clazz, |
| templateParams); |
| } |
| |
| /** |
| * Determines whether annotations should be used in test classes in the |
| * given folder when generating the given type of TestNG tests. |
| * If annotations are supported, adds this information to the map of |
| * template parameters. |
| * |
| * @param testFolder target folder for generated test classes |
| * @param templateParams map of template params to store |
| * the information to |
| * @return {@code true} if it was detected that annotations are supported; |
| * {@code false} otherwise |
| */ |
| private static boolean setAnnotationsSupport( |
| FileObject testFolder, |
| Map<String, Boolean> templateParams) { |
| if (!testFolder.isFolder()) { |
| throw new IllegalArgumentException("not a folder"); //NOI18N |
| } |
| |
| templateParams.put(templatePropUseAnnotations, true); |
| return true; |
| } |
| |
| /** |
| * |
| */ |
| private static void save(DataObject dataObj) throws IOException { |
| Savable sc = dataObj.getLookup().lookup(Savable.class); |
| if (null != sc) { |
| sc.save(); |
| } |
| } |
| |
| /** |
| * Loads a test template. |
| * If the template loading fails, displays an error message. |
| * |
| * @param templateID bundle key identifying the template type |
| * @return loaded template, or <code>null</code> if the template |
| * could not be loaded |
| */ |
| private static DataObject loadTestTemplate(String templateID) { |
| // get the Test class template |
| String path = templateID; |
| try { |
| FileObject fo = FileUtil.getConfigFile(path); |
| if (fo == null) { |
| noTemplateMessage(path); |
| return null; |
| } |
| return DataObject.find(fo); |
| } |
| catch (DataObjectNotFoundException e) { |
| noTemplateMessage(path); |
| return null; |
| } |
| } |
| |
| /** |
| * |
| */ |
| @Messages({"# {0} - template file","MSG_template_not_found=Template file {0} was not found. Check the TestNG templates in the Template manager."}) |
| private static void noTemplateMessage(String temp) { |
| String msg = Bundle.MSG_template_not_found(temp); //NOI18N |
| NotifyDescriptor descr = new NotifyDescriptor.Message( |
| msg, |
| NotifyDescriptor.ERROR_MESSAGE); |
| DialogDisplayer.getDefault().notify(descr); |
| } |
| |
| /** |
| * A helper method to create the reason string from a result |
| * and two message bundle keys that indicate the separators to be used instead |
| * of "," and " and " in a connected reason like: |
| * "abstract, package-private and without testable methods". |
| * <p> |
| * The values of the keys are expected to be framed by two extra characters |
| * (e.g. as in " and "), which are stripped off. These characters serve to |
| * preserve the spaces in the properties file. |
| * |
| * @param reason the TestabilityResult to represent |
| * @param commaKey bundle key for the connective to be used instead of ", " |
| * @param andKey bundle key for the connective to be used instead of "and" |
| * @return String composed of the reasons contained in |
| * <code>reason</code> separated by the values of commaKey and |
| * andKey. |
| */ |
| @Messages({"COMMA=, ","OR= or ","AND= and "}) |
| private static String strReason(TestabilityResult reason, String commaKey, String andKey) { |
| String strComma = Bundle.COMMA(); |
| String strAnd = andKey.equals("OR") ? Bundle.OR() : Bundle.AND(); |
| String strReason = reason.getReason( // string representation of the reasons |
| strComma, strAnd); |
| |
| return strReason; |
| |
| } |
| |
| /** |
| * |
| */ |
| @NbBundle.Messages({"# {0} - class name", "FMT_generator_status_creating=Creating: {0} ..."}) |
| private static String getCreatingMsg(String className) { |
| return Bundle.FMT_generator_status_creating(className); |
| } |
| |
| /** |
| * |
| */ |
| @NbBundle.Messages({"# {0} - source folder", "FMT_generator_status_scanning=Scanning: {0} ..."}) |
| private static String getScanningMsg(String sourceName) { |
| return Bundle.FMT_generator_status_scanning(sourceName); |
| } |
| |
| /** |
| * |
| */ |
| @NbBundle.Messages({"# {0} - source folder", "FMT_generator_status_ignoring=Ignoring: {0} ..."}) |
| private static String getIgnoringMsg(String sourceName, String reason) { |
| return Bundle.FMT_generator_status_ignoring(sourceName); |
| } |
| |
| |
| /** |
| * Error thrown by failed test creation. |
| */ |
| @SuppressWarnings("serial") |
| private static final class CreationError extends Exception { |
| CreationError() {}; |
| CreationError(Throwable cause) { |
| super(cause); |
| } |
| } |
| |
| /** |
| * Utility class representing the results of a test creation |
| * process. It gatheres all tests (as DataObject) created and all |
| * classes (as JavaClasses) for which no test was created. |
| */ |
| static final class CreationResults { |
| static final CreationResults EMPTY = new CreationResults(); |
| |
| Set<DataObject> created; // Set< createdTest : DataObject > |
| Set<SkippedClass> skipped; |
| boolean abborted = false; |
| |
| CreationResults() { this(20);} |
| |
| CreationResults(int expectedSize) { |
| created = new HashSet<DataObject>(expectedSize * 2, 0.5f); |
| skipped = new HashSet<SkippedClass>(expectedSize * 2, 0.5f); |
| } |
| |
| void setAbborted() { |
| abborted = true; |
| } |
| |
| /** |
| * Returns true if the process of creation was abborted. The |
| * result contains the results gathered so far. |
| */ |
| boolean isAbborted() { |
| return abborted; |
| } |
| |
| /** |
| * Adds a new entry to the set of created tests. |
| * @return true if it was added, false if it was present before |
| */ |
| boolean addCreated(DataObject test) { |
| return created.add(test); |
| } |
| |
| /** |
| */ |
| boolean addSkipped(SkippedClass skippedClass) { |
| return skipped.add(skippedClass); |
| } |
| |
| /** |
| */ |
| void addSkipped(Collection<SkippedClass> skippedClasses) { |
| if (!skippedClasses.isEmpty()) { |
| skipped.addAll(skippedClasses); |
| } |
| } |
| |
| /** |
| * Returns a set of classes that were skipped in the process. |
| * @return Set<SkippedClass> |
| */ |
| Set<SkippedClass> getSkipped() { |
| return skipped; |
| } |
| |
| /** |
| * Returns a set of test data objects created. |
| * @return Set<DataObject> |
| */ |
| Set<DataObject> getCreated() { |
| return created; |
| } |
| |
| /** |
| * Combines two results into one. If any of the results is an |
| * abborted result, the combination is also abborted. The |
| * collections of created and skipped classes are unified. |
| * @param rhs the other CreationResult to combine into this |
| */ |
| void combine(CreationResults rhs) { |
| if (rhs.abborted) { |
| this.abborted = true; |
| } |
| |
| this.created.addAll(rhs.created); |
| this.skipped.addAll(rhs.skipped); |
| } |
| |
| } |
| |
| } |