| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * 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 com.theoryinpractice.testng.util; |
| |
| import com.intellij.codeInsight.AnnotationUtil; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.ModuleRootModificationUtil; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.io.JarUtil; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VfsUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.*; |
| import com.intellij.psi.javadoc.PsiDocComment; |
| import com.intellij.psi.javadoc.PsiDocTag; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.searches.AllClassesSearch; |
| import com.intellij.psi.util.PsiElementFilter; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.util.PathUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.xml.NanoXmlUtil; |
| import com.theoryinpractice.testng.model.TestClassFilter; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.testng.Assert; |
| import org.testng.ITestNGListener; |
| import org.testng.TestNG; |
| import org.testng.annotations.*; |
| |
| import java.io.File; |
| import java.util.*; |
| import java.util.jar.Attributes; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * @author Hani Suleiman |
| * @since Jul 20, 2005 |
| */ |
| public class TestNGUtil { |
| private static final Logger LOGGER = Logger.getInstance("TestNG Runner"); |
| |
| public static final String TESTNG_GROUP_NAME = "TestNG"; |
| |
| public static boolean hasDocTagsSupport = hasDocTagsSupport(); |
| |
| private static boolean hasDocTagsSupport() { |
| String testngJarPath = PathUtil.getJarPathForClass(Test.class); |
| String version = JarUtil.getJarAttribute(new File(testngJarPath), Attributes.Name.IMPLEMENTATION_VERSION); |
| return version == null || StringUtil.compareVersionNumbers(version, "5.12") <= 0; |
| } |
| |
| public static final String TEST_ANNOTATION_FQN = Test.class.getName(); |
| public static final String FACTORY_ANNOTATION_FQN = Factory.class.getName(); |
| @SuppressWarnings("deprecation") public static final String[] CONFIG_ANNOTATIONS_FQN = { |
| Configuration.class.getName(), |
| Factory.class.getName(), |
| ObjectFactory.class.getName(), |
| DataProvider.class.getName(), |
| BeforeClass.class.getName(), |
| BeforeGroups.class.getName(), |
| BeforeMethod.class.getName(), |
| BeforeSuite.class.getName(), |
| BeforeTest.class.getName(), |
| AfterClass.class.getName(), |
| AfterGroups.class.getName(), |
| AfterMethod.class.getName(), |
| AfterSuite.class.getName(), |
| AfterTest.class.getName() |
| }; |
| |
| @NonNls |
| private static final String[] CONFIG_JAVADOC_TAGS = { |
| "testng.configuration", |
| "testng.before-class", |
| "testng.before-groups", |
| "testng.before-method", |
| "testng.before-suite", |
| "testng.before-test", |
| "testng.after-class", |
| "testng.after-groups", |
| "testng.after-method", |
| "testng.after-suite", |
| "testng.after-test" |
| }; |
| |
| private static final List<String> JUNIT_ANNOTATIONS = |
| Arrays.asList("org.junit.Test", "org.junit.Before", "org.junit.BeforeClass", "org.junit.After", "org.junit.AfterClass"); |
| |
| @NonNls |
| private static final String SUITE_TAG_NAME = "suite"; |
| |
| public static boolean hasConfig(PsiModifierListOwner element) { |
| if (element instanceof PsiClass) { |
| for (PsiMethod method : ((PsiClass)element).getAllMethods()) { |
| if (isConfigMethod(method)) return true; |
| } |
| } else { |
| if (!(element instanceof PsiMethod)) return false; |
| return isConfigMethod((PsiMethod)element); |
| } |
| return false; |
| } |
| |
| private static boolean isConfigMethod(PsiMethod method) { |
| for (String fqn : CONFIG_ANNOTATIONS_FQN) { |
| if (AnnotationUtil.isAnnotated(method, fqn, false)) return true; |
| } |
| |
| if (hasDocTagsSupport) { |
| final PsiDocComment comment = method.getDocComment(); |
| if (comment != null) { |
| for (String javadocTag : CONFIG_JAVADOC_TAGS) { |
| if (comment.findTagByName(javadocTag) != null) return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public static String getConfigAnnotation(PsiMethod method) { |
| if (method != null) { |
| for (String fqn : CONFIG_ANNOTATIONS_FQN) { |
| if (AnnotationUtil.isAnnotated(method, fqn, false)) return fqn; |
| } |
| } |
| return null; |
| } |
| |
| public static boolean isTestNGAnnotation(PsiAnnotation annotation) { |
| String qName = annotation.getQualifiedName(); |
| if (qName != null) { |
| if (qName.equals(TEST_ANNOTATION_FQN)) return true; |
| for (String qn : CONFIG_ANNOTATIONS_FQN) { |
| if (qName.equals(qn)) return true; |
| } |
| if (qName.equals(TEST_ANNOTATION_FQN)) return true; |
| for (String qn : CONFIG_ANNOTATIONS_FQN) { |
| if (qName.equals(qn)) return true; |
| } |
| } |
| return false; |
| } |
| |
| public static boolean hasTest(PsiModifierListOwner element) { |
| return hasTest(element, true); |
| } |
| |
| public static boolean hasTest(PsiModifierListOwner element, boolean checkDisabled) { |
| return hasTest(element, true, checkDisabled, hasDocTagsSupport); |
| } |
| |
| public static boolean hasTest(PsiModifierListOwner element, boolean checkHierarchy, boolean checkDisabled, boolean checkJavadoc) { |
| //LanguageLevel effectiveLanguageLevel = element.getManager().getEffectiveLanguageLevel(); |
| //boolean is15 = effectiveLanguageLevel != LanguageLevel.JDK_1_4 && effectiveLanguageLevel != LanguageLevel.JDK_1_3; |
| boolean hasAnnotation = AnnotationUtil.isAnnotated(element, TEST_ANNOTATION_FQN, checkHierarchy, true); |
| if (hasAnnotation) { |
| if (checkDisabled) { |
| PsiAnnotation annotation = AnnotationUtil.findAnnotation(element, true, TEST_ANNOTATION_FQN); |
| if (annotation != null) { |
| if (isDisabled(annotation)) return false; |
| } |
| } |
| return true; |
| } |
| if (element instanceof PsiDocCommentOwner && hasTestJavaDoc((PsiDocCommentOwner) element, checkJavadoc)) |
| return true; |
| //now we check all methods for the test annotation |
| if (element instanceof PsiClass) { |
| PsiClass psiClass = (PsiClass) element; |
| for (PsiMethod method : psiClass.getAllMethods()) { |
| if (AnnotationUtil.isAnnotated(method, TEST_ANNOTATION_FQN, false, true)) return true; |
| if (AnnotationUtil.isAnnotated(method, FACTORY_ANNOTATION_FQN, false, true)) return true; |
| if (hasTestJavaDoc(method, checkJavadoc)) return true; |
| } |
| return AnnotationUtil.isAnnotated(element, TEST_ANNOTATION_FQN, true, true); |
| } else if (element instanceof PsiMethod) { |
| //if it's a method, we check if the class it's in has a global @Test annotation |
| PsiClass psiClass = ((PsiMethod)element).getContainingClass(); |
| if (psiClass != null) { |
| if (AnnotationUtil.isAnnotated(psiClass, TEST_ANNOTATION_FQN, true, true)) { |
| //even if it has a global test, we ignore private methods |
| boolean isPrivate = element.hasModifierProperty(PsiModifier.PRIVATE); |
| return !isPrivate && !element.hasModifierProperty(PsiModifier.STATIC) && !hasConfig(element); |
| } |
| if (hasTestJavaDoc(psiClass, checkJavadoc)) return true; |
| } |
| } |
| return false; |
| } |
| |
| public static boolean isDisabled(PsiAnnotation annotation) { |
| final PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue("enabled"); |
| return attributeValue != null && attributeValue.textMatches("false"); |
| } |
| |
| private static boolean hasTestJavaDoc(@NotNull PsiDocCommentOwner element, final boolean checkJavadoc) { |
| if (checkJavadoc) { |
| return getTextJavaDoc(element) != null; |
| } |
| return false; |
| } |
| |
| private static PsiDocTag getTextJavaDoc(@NotNull final PsiDocCommentOwner element) { |
| final PsiDocComment docComment = element.getDocComment(); |
| if (docComment != null) { |
| return docComment.findTagByName("testng.test"); |
| } |
| return null; |
| } |
| |
| /** |
| * Ignore these, they cause an NPE inside of AnnotationUtil |
| */ |
| private static boolean isBrokenPsiClass(PsiClass psiClass) { |
| return (psiClass == null |
| || psiClass instanceof PsiAnonymousClass |
| || psiClass instanceof PsiSyntheticClass); |
| } |
| |
| /** |
| * Filter the specified collection of classes to return only ones that contain any of the specified values in the |
| * specified annotation parameter. For example, this method can be used to return all classes that contain all tesng |
| * annotations that are in the groups 'foo' or 'bar'. |
| */ |
| public static Map<PsiClass, Collection<PsiMethod>> filterAnnotations(String parameter, Set<String> values, Collection<PsiClass> classes) { |
| Map<PsiClass, Collection<PsiMethod>> results = new HashMap<PsiClass, Collection<PsiMethod>>(); |
| Set<String> test = new HashSet<String>(1); |
| test.add(TEST_ANNOTATION_FQN); |
| ContainerUtil.addAll(test, CONFIG_ANNOTATIONS_FQN); |
| for (PsiClass psiClass : classes) { |
| if (isBrokenPsiClass(psiClass)) continue; |
| |
| PsiAnnotation annotation; |
| try { |
| annotation = AnnotationUtil.findAnnotation(psiClass, test); |
| } |
| catch (Exception e) { |
| LOGGER.error("Exception trying to findAnnotation on " + psiClass.getClass().getName() + ".\n\n" + e.getMessage()); |
| annotation = null; |
| } |
| if (annotation != null) { |
| if (isAnnotatedWithParameter(annotation, parameter, values)) { |
| results.put(psiClass, new LinkedHashSet<PsiMethod>()); |
| } |
| } |
| else { |
| Collection<String> matches = extractAnnotationValuesFromJavaDoc(getTextJavaDoc(psiClass), parameter); |
| for (String s : matches) { |
| if (values.contains(s)) { |
| results.put(psiClass, new LinkedHashSet<PsiMethod>()); |
| break; |
| } |
| } |
| } |
| |
| //we already have the class, no need to look through its methods |
| PsiMethod[] methods = psiClass.getMethods(); |
| for (PsiMethod method : methods) { |
| if (method != null) { |
| annotation = AnnotationUtil.findAnnotation(method, test); |
| if (annotation != null) { |
| if (isAnnotatedWithParameter(annotation, parameter, values)) { |
| if (results.get(psiClass) == null) results.put(psiClass, new LinkedHashSet<PsiMethod>()); |
| results.get(psiClass).add(method); |
| } |
| } |
| else { |
| Collection<String> matches = extractAnnotationValuesFromJavaDoc(getTextJavaDoc(psiClass), parameter); |
| for (String s : matches) { |
| if (values.contains(s)) { |
| results.get(psiClass).add(method); |
| } |
| } |
| } |
| } |
| } |
| } |
| return results; |
| } |
| |
| public static boolean isAnnotatedWithParameter(PsiAnnotation annotation, String parameter, Set<String> values) { |
| final PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue(parameter); |
| if (attributeValue != null) { |
| Collection<String> matches = extractValuesFromParameter(attributeValue); |
| for (String s : matches) { |
| if (values.contains(s)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public static Set<String> getAnnotationValues(String parameter, PsiClass... classes) { |
| Set<String> results = new HashSet<String>(); |
| collectAnnotationValues(results, parameter, null, classes); |
| return results; |
| } |
| |
| /** |
| * @return were javadoc params used |
| */ |
| public static void collectAnnotationValues(final Set<String> results, final String parameter, PsiMethod[] psiMethods, PsiClass... classes) { |
| final Set<String> test = new HashSet<String>(1); |
| test.add(TEST_ANNOTATION_FQN); |
| ContainerUtil.addAll(test, CONFIG_ANNOTATIONS_FQN); |
| if (psiMethods != null) { |
| for (final PsiMethod psiMethod : psiMethods) { |
| ApplicationManager.getApplication().runReadAction( |
| new Runnable() { |
| public void run() { |
| appendAnnotationAttributeValues(parameter, results, AnnotationUtil.findAnnotation(psiMethod, test), psiMethod); |
| } |
| } |
| ); |
| } |
| } |
| else { |
| for (final PsiClass psiClass : classes) { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| if (psiClass != null && hasTest(psiClass)) { |
| appendAnnotationAttributeValues(parameter, results, AnnotationUtil.findAnnotation(psiClass, test), psiClass); |
| PsiMethod[] methods = psiClass.getMethods(); |
| for (PsiMethod method : methods) { |
| if (method != null) { |
| appendAnnotationAttributeValues(parameter, results, AnnotationUtil.findAnnotation(method, test), method); |
| } |
| } |
| } |
| } |
| }); |
| } |
| } |
| } |
| |
| private static void appendAnnotationAttributeValues(final String parameter, |
| final Collection<String> results, |
| final PsiAnnotation annotation, |
| final PsiDocCommentOwner commentOwner) { |
| if (annotation != null) { |
| final PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue(parameter); |
| if (value != null) { |
| results.addAll(extractValuesFromParameter(value)); |
| } |
| } else { |
| results.addAll(extractAnnotationValuesFromJavaDoc(getTextJavaDoc(commentOwner), parameter)); |
| } |
| } |
| |
| private static Collection<String> extractAnnotationValuesFromJavaDoc(PsiDocTag tag, String parameter) { |
| if (tag == null) return Collections.emptyList(); |
| Collection<String> results = new ArrayList<String>(); |
| Matcher matcher = Pattern.compile("\\@testng.test(?:.*)" + parameter + "\\s*=\\s*\"(.*?)\".*").matcher(tag.getText()); |
| if (matcher.matches()) { |
| String[] groups = matcher.group(1).split("[,\\s]"); |
| for (String group : groups) { |
| final String trimmed = group.trim(); |
| if (trimmed.length() > 0) { |
| results.add(trimmed); |
| } |
| } |
| } |
| return results; |
| } |
| |
| private static Collection<String> extractValuesFromParameter(PsiAnnotationMemberValue value) { |
| Collection<String> results = new ArrayList<String>(); |
| if (value instanceof PsiArrayInitializerMemberValue) { |
| for (PsiElement child : value.getChildren()) { |
| if (child instanceof PsiLiteralExpression) { |
| results.add((String) ((PsiLiteralExpression) child).getValue()); |
| } |
| } |
| } else { |
| if (value instanceof PsiLiteralExpression) { |
| results.add((String) ((PsiLiteralExpression) value).getValue()); |
| } |
| } |
| return results; |
| } |
| |
| @Nullable |
| public static PsiClass[] getAllTestClasses(final TestClassFilter filter, boolean sync) { |
| final PsiClass[][] holder = new PsiClass[1][]; |
| final Runnable process = new Runnable() { |
| public void run() { |
| final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); |
| |
| final Collection<PsiClass> set = new HashSet<PsiClass>(); |
| PsiManager manager = PsiManager.getInstance(filter.getProject()); |
| GlobalSearchScope scope = filter.getScope(); |
| GlobalSearchScope projectScope = GlobalSearchScope.projectScope(manager.getProject()); |
| scope = projectScope.intersectWith(scope); |
| for (final PsiClass psiClass : AllClassesSearch.search(scope, manager.getProject())) { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| if (filter.isAccepted(psiClass)) { |
| indicator.setText2("Found test class " + psiClass.getQualifiedName()); |
| set.add(psiClass); |
| } |
| } |
| }); |
| } |
| holder[0] = set.toArray(new PsiClass[set.size()]); |
| } |
| }; |
| if (sync) { |
| ProgressManager.getInstance().runProcessWithProgressSynchronously(process, "Searching For Tests...", true, filter.getProject()); |
| } |
| else { |
| process.run(); |
| } |
| return holder[0]; |
| } |
| |
| public static PsiAnnotation[] getTestNGAnnotations(PsiElement element) { |
| PsiElement[] annotations = PsiTreeUtil.collectElements(element, new PsiElementFilter() |
| { |
| public boolean isAccepted(PsiElement element) { |
| if (!(element instanceof PsiAnnotation)) return false; |
| String name = ((PsiAnnotation) element).getQualifiedName(); |
| if (null == name) return false; |
| if (name.startsWith("org.testng.annotations")) { |
| return true; |
| } |
| return false; |
| } |
| }); |
| PsiAnnotation[] array = new PsiAnnotation[annotations.length]; |
| System.arraycopy(annotations, 0, array, 0, annotations.length); |
| return array; |
| } |
| |
| public static boolean isTestNGClass(PsiClass psiClass) { |
| return hasTest(psiClass, true, true, false); |
| } |
| |
| public static boolean checkTestNGInClasspath(PsiElement psiElement) { |
| final Project project = psiElement.getProject(); |
| final PsiManager manager = PsiManager.getInstance(project); |
| if (JavaPsiFacade.getInstance(manager.getProject()).findClass(TestNG.class.getName(), psiElement.getResolveScope()) == null) { |
| if (!ApplicationManager.getApplication().isUnitTestMode()) { |
| if (Messages.showOkCancelDialog(psiElement.getProject(), "TestNG will be added to module classpath", "Unable to convert.", Messages.getWarningIcon()) != |
| Messages.OK) { |
| return false; |
| } |
| } |
| final Module module = ModuleUtilCore.findModuleForPsiElement(psiElement); |
| if (module == null) return false; |
| String url = VfsUtil.getUrlForLibraryRoot(new File(PathUtil.getJarPathForClass(Assert.class))); |
| ModuleRootModificationUtil.addModuleLibrary(module, url); |
| } |
| return true; |
| } |
| |
| public static boolean containsJunitAnnotions(PsiClass psiClass) { |
| if (psiClass != null) { |
| for (PsiMethod method : psiClass.getMethods()) { |
| if (containsJunitAnnotions(method)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public static boolean containsJunitAnnotions(PsiMethod method) { |
| return method != null && AnnotationUtil.isAnnotated(method, JUNIT_ANNOTATIONS); |
| } |
| |
| public static boolean inheritsJUnitTestCase(PsiClass psiClass) { |
| PsiClass current = psiClass; |
| while (current != null) { |
| PsiClass[] supers = current.getSupers(); |
| if (supers.length > 0) { |
| PsiClass parent = supers[0]; |
| if ("junit.framework.TestCase".equals(parent.getQualifiedName())) return true; |
| current = parent; |
| //handle typo where class extends itself |
| if (current == psiClass) return false; |
| } else { |
| current = null; |
| } |
| } |
| return false; |
| } |
| |
| public static boolean inheritsITestListener(@NotNull PsiClass psiClass) { |
| final Project project = psiClass.getProject(); |
| final PsiClass aListenerClass = JavaPsiFacade.getInstance(project) |
| .findClass(ITestNGListener.class.getName(), GlobalSearchScope.allScope(project)); |
| return aListenerClass != null && psiClass.isInheritor(aListenerClass, true); |
| } |
| |
| public static boolean isTestngXML(final VirtualFile virtualFile) { |
| if ("xml".equalsIgnoreCase(virtualFile.getExtension()) && virtualFile.isValid()) { |
| final String result = NanoXmlUtil.parseHeader(virtualFile).getRootTagLocalName(); |
| if (result != null && result.equals(SUITE_TAG_NAME)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |