junit/testng: pattern configuration isValid respect to chosen method
[idea/community.git] / plugins / testng / src / com / theoryinpractice / testng / configuration / TestNGConfiguration.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * Created by IntelliJ IDEA.
19  * User: amrk
20  * Date: Jul 2, 2005
21  * Time: 12:16:02 AM
22  */
23 package com.theoryinpractice.testng.configuration;
24
25 import com.intellij.diagnostic.logging.LogConfigurationPanel;
26 import com.intellij.execution.*;
27 import com.intellij.execution.configuration.EnvironmentVariablesComponent;
28 import com.intellij.execution.configurations.*;
29 import com.intellij.execution.junit.RefactoringListeners;
30 import com.intellij.execution.runners.ExecutionEnvironment;
31 import com.intellij.execution.testframework.SourceScope;
32 import com.intellij.execution.util.JavaParametersUtil;
33 import com.intellij.execution.util.ProgramParametersUtil;
34 import com.intellij.openapi.components.PathMacroManager;
35 import com.intellij.openapi.module.Module;
36 import com.intellij.openapi.options.SettingsEditor;
37 import com.intellij.openapi.options.SettingsEditorGroup;
38 import com.intellij.openapi.project.Project;
39 import com.intellij.openapi.util.DefaultJDOMExternalizer;
40 import com.intellij.openapi.util.InvalidDataException;
41 import com.intellij.openapi.util.WriteExternalException;
42 import com.intellij.openapi.util.text.StringUtil;
43 import com.intellij.psi.*;
44 import com.intellij.psi.search.GlobalSearchScope;
45 import com.intellij.refactoring.listeners.RefactoringElementAdapter;
46 import com.intellij.refactoring.listeners.RefactoringElementListener;
47 import com.intellij.refactoring.listeners.UndoRefactoringElementListener;
48 import com.theoryinpractice.testng.model.TestData;
49 import com.theoryinpractice.testng.model.TestType;
50 import com.theoryinpractice.testng.util.TestNGUtil;
51 import org.jdom.Element;
52 import org.jetbrains.annotations.NonNls;
53 import org.jetbrains.annotations.NotNull;
54 import org.jetbrains.annotations.Nullable;
55 import org.testng.xml.Parser;
56
57 import java.util.*;
58
59 public class TestNGConfiguration extends ModuleBasedConfiguration<JavaRunConfigurationModule>
60   implements CommonJavaRunConfigurationParameters, RefactoringListenerProvider {
61   @NonNls private static final String PATTERNS_EL_NAME = "patterns";
62   @NonNls private static final String PATTERN_EL_NAME = "pattern";
63   @NonNls private static final String TEST_CLASS_ATT_NAME = "testClass";
64   
65   //private TestNGResultsContainer resultsContainer;
66   protected TestData data;
67   protected transient Project project;
68   public boolean ALTERNATIVE_JRE_PATH_ENABLED;
69   public String ALTERNATIVE_JRE_PATH;
70   
71   private static final Object PARSE_LOCK = new Object();
72
73   public static final String DEFAULT_PACKAGE_NAME = ExecutionBundle.message("default.package.presentable.name");
74   public static final String DEFAULT_PACKAGE_CONFIGURATION_NAME = ExecutionBundle.message("default.package.configuration.name");
75   private final RefactoringListeners.Accessor<PsiPackage> myPackage = new RefactoringListeners.Accessor<PsiPackage>() {
76     public void setName(final String qualifiedName) {
77       final boolean generatedName = isGeneratedName();
78       data.PACKAGE_NAME = qualifiedName;
79       if (generatedName) setGeneratedName();
80     }
81
82     @Nullable
83     public PsiPackage getPsiElement() {
84       final String qualifiedName = data.getPackageName();
85       return qualifiedName != null ? JavaPsiFacade.getInstance(getProject()).findPackage(qualifiedName) : null;
86     }
87
88     public void setPsiElement(final PsiPackage psiPackage) {
89       setName(psiPackage.getQualifiedName());
90     }
91   };
92
93   private final RefactoringListeners.Accessor<PsiClass> myClass = new RefactoringListeners.Accessor<PsiClass>() {
94     public void setName(final String qualifiedName) {
95       final boolean generatedName = isGeneratedName();
96       data.MAIN_CLASS_NAME = qualifiedName;
97       if (generatedName) setGeneratedName();
98     }
99
100     @Nullable
101     public PsiClass getPsiElement() {
102       final String qualifiedName = data.getMainClassName();
103       return qualifiedName != null
104              ? JavaPsiFacade.getInstance(getProject()).findClass(qualifiedName, GlobalSearchScope.allScope(project))
105              : null;
106     }
107
108     public void setPsiElement(final PsiClass psiClass) {
109       setName(psiClass.getQualifiedName());
110     }
111   };
112
113   public TestNGConfiguration(String s, Project project, ConfigurationFactory factory) {
114     this(s, project, new TestData(), factory);
115   }
116
117   private TestNGConfiguration(String s, Project project, TestData data, ConfigurationFactory factory) {
118     super(s, new JavaRunConfigurationModule(project, false), factory);
119     this.data = data;
120     this.project = project;
121   }
122
123   public RunProfileState getState(@NotNull final Executor executor, @NotNull final ExecutionEnvironment env) throws ExecutionException {
124     return new TestNGRunnableState(env, this);
125   }
126
127   public TestData getPersistantData() {
128     return data;
129   }
130
131   @Override
132   protected ModuleBasedConfiguration createInstance() {
133     try {
134       return new TestNGConfiguration(getName(), getProject(), (TestData)data.clone(),
135                                      TestNGConfigurationType.getInstance().getConfigurationFactories()[0]);
136     }
137     catch (CloneNotSupportedException e) {
138       //can't happen right?
139       e.printStackTrace();
140     }
141     return null;
142   }
143
144   @Override
145   public Collection<Module> getValidModules() {
146     //TODO add handling for package
147     return JavaRunConfigurationModule.getModulesForClass(getProject(), data.getMainClassName());
148   }
149
150   @Override
151   public boolean isGeneratedName() {
152     return data.isGeneratedName(getName(), getConfigurationModule());
153   }
154
155   @Override
156   public String suggestedName() {
157     if (TestType.CLASS.getType().equals(data.TEST_OBJECT)) {
158       String shortName = JavaExecutionUtil.getShortClassName(data.MAIN_CLASS_NAME);
159       return ProgramRunnerUtil.shortenName(shortName, 0);
160     }
161     if (TestType.PACKAGE.getType().equals(data.TEST_OBJECT)) {
162       String s = getName();
163       if (!isGeneratedName()) return '\"' + s + '\"';
164       if (data.getPackageName().trim().length() > 0) {
165         return "Tests in \"" + data.getPackageName() + '\"';
166       }
167       else {
168         return "All Tests";
169       }
170     }
171     if (TestType.METHOD.getType().equals(data.TEST_OBJECT)) {
172       return data.getMethodName() + "()";
173     }
174     if (TestType.SUITE.getType().equals(data.TEST_OBJECT)) {
175       return data.getSuiteName();
176     }
177     return data.getGroupName();
178   }
179
180   public void setVMParameters(String value) {
181     data.setVMParameters(value);
182   }
183
184   public String getVMParameters() {
185     return data.getVMParameters();
186   }
187
188   public void setProgramParameters(String value) {
189     data.setProgramParameters(value);
190   }
191
192   public String getProgramParameters() {
193     return data.getProgramParameters();
194   }
195
196   public void setWorkingDirectory(String value) {
197     data.setWorkingDirectory(value);
198   }
199
200   public String getWorkingDirectory() {
201     return data.getWorkingDirectory(project);
202   }
203
204   public void setEnvs(@NotNull Map<String, String> envs) {
205     data.setEnvs(envs);
206   }
207
208   @NotNull
209   public Map<String, String> getEnvs() {
210     return data.getEnvs();
211   }
212
213   public void setPassParentEnvs(boolean passParentEnvs) {
214     data.PASS_PARENT_ENVS = passParentEnvs;
215   }
216
217   public boolean isPassParentEnvs() {
218     return data.PASS_PARENT_ENVS;
219   }
220
221   public boolean isAlternativeJrePathEnabled() {
222      return ALTERNATIVE_JRE_PATH_ENABLED;
223    }
224
225    public void setAlternativeJrePathEnabled(boolean enabled) {
226      this.ALTERNATIVE_JRE_PATH_ENABLED = enabled;
227    }
228
229    public String getAlternativeJrePath() {
230      return ALTERNATIVE_JRE_PATH;
231    }
232
233    public void setAlternativeJrePath(String path) {
234      this.ALTERNATIVE_JRE_PATH = path;
235    }
236
237   public String getRunClass() {
238     return !data.TEST_OBJECT.equals(TestType.CLASS.getType()) && !data.TEST_OBJECT.equals(TestType.METHOD.getType()) ? null : data.getMainClassName();
239   }
240
241   public String getPackage() {
242     return !data.TEST_OBJECT.equals(TestType.PACKAGE.getType()) ? null : data.getPackageName();
243   }
244
245   public void setClassConfiguration(PsiClass psiclass) {
246     setModule(data.setMainClass(psiclass));
247     data.TEST_OBJECT = TestType.CLASS.getType();
248     setGeneratedName();
249   }
250
251   public void setPackageConfiguration(Module module, PsiPackage pkg) {
252     data.setPackage(pkg);
253     setModule(module);
254     data.TEST_OBJECT = TestType.PACKAGE.getType();
255     setGeneratedName();
256   }
257
258   public void setMethodConfiguration(Location<PsiMethod> location) {
259     setModule(data.setTestMethod(location));
260     setGeneratedName();
261   }
262   
263   
264   public void bePatternConfiguration(List<PsiClass> classes, PsiMethod method) {
265     data.TEST_OBJECT = TestType.PATTERN.getType();
266     data.METHOD_NAME = method.getName();
267     Set<String> patterns = new HashSet<String>();
268     for (PsiClass pattern : classes) {
269       patterns.add(JavaExecutionUtil.getRuntimeQualifiedName(pattern) + "," + data.METHOD_NAME);
270     }
271     data.setPatterns(patterns);
272     setGeneratedName();
273   }
274
275   public void setGeneratedName() {
276     setName(getGeneratedName());
277   }
278
279   public String getGeneratedName() {
280     return data.getGeneratedName(getConfigurationModule());
281   }
282
283   public SettingsEditor<? extends RunConfiguration> getConfigurationEditor() {
284     SettingsEditorGroup<TestNGConfiguration> group = new SettingsEditorGroup<TestNGConfiguration>();
285     group.addEditor(ExecutionBundle.message("run.configuration.configuration.tab.title"), new TestNGConfigurationEditor(getProject()));
286     JavaRunConfigurationExtensionManager.getInstance().appendEditors(this, group);
287     group.addEditor(ExecutionBundle.message("logs.tab.title"), new LogConfigurationPanel<TestNGConfiguration>());
288     return group;
289   }
290
291   @Override
292   public void checkConfiguration() throws RuntimeConfigurationException {
293     if (data.TEST_OBJECT.equals(TestType.CLASS.getType()) || data.TEST_OBJECT.equals(TestType.METHOD.getType())) {
294       final SourceScope scope = data.getScope().getSourceScope(this);
295       if (scope == null) {
296         throw new RuntimeConfigurationException("Invalid scope specified");
297       }
298       PsiClass psiClass = JavaPsiFacade.getInstance(project).findClass(data.getMainClassName(), scope.getGlobalSearchScope());
299       if (psiClass == null) throw new RuntimeConfigurationException("Class '" + data.getMainClassName() + "' not found");
300       if (data.TEST_OBJECT.equals(TestType.METHOD.getType())) {
301         PsiMethod[] methods = psiClass.findMethodsByName(data.getMethodName(), true);
302         if (methods.length == 0) {
303           throw new RuntimeConfigurationException("Method '" + data.getMethodName() + "' not found");
304         }
305         for (PsiMethod method : methods) {
306           if (!method.hasModifierProperty(PsiModifier.PUBLIC)) {
307             throw new RuntimeConfigurationException("Non public method '" + data.getMethodName() + "'specified");
308           }
309         }
310       }
311     }
312     else if (data.TEST_OBJECT.equals(TestType.PACKAGE.getType())) {
313       PsiPackage psiPackage = JavaPsiFacade.getInstance(project).findPackage(data.getPackageName());
314       if (psiPackage == null) throw new RuntimeConfigurationException("Package '" + data.getMainClassName() + "' not found");
315     }
316     else if (data.TEST_OBJECT.equals(TestType.SUITE.getType())) {
317       try {
318         final Parser parser = new Parser(data.getSuiteName());
319         parser.setLoadClasses(false);
320         synchronized (PARSE_LOCK) {
321           parser.parse();//try to parse suite.xml
322         }
323       }
324       catch (Exception e) {
325         throw new RuntimeConfigurationException("Unable to parse '" + data.getSuiteName() + "' specified");
326       }
327     } else if (data.TEST_OBJECT.equals(TestType.PATTERN.getType())) {
328       final Set<String> patterns = data.getPatterns();
329       if (patterns.isEmpty()) {
330         throw new RuntimeConfigurationWarning("No pattern selected");
331       }
332       final GlobalSearchScope searchScope = GlobalSearchScope.allScope(getProject());
333       for (String pattern : patterns) {
334         final String className = pattern.contains(",") ? StringUtil.getPackageName(pattern, ',') : pattern;
335         final PsiClass psiClass = JavaExecutionUtil.findMainClass(getProject(), className, searchScope);
336         if (psiClass == null) {
337           throw new RuntimeConfigurationWarning("Class " + className + " not found");
338         }
339         if (!TestNGUtil.hasTest(psiClass)) {
340           throw new RuntimeConfigurationWarning("Class " + className + " not a test");
341         }
342       }
343     }
344     JavaRunConfigurationExtensionManager.checkConfigurationIsValid(this);
345     ProgramParametersUtil.checkWorkingDirectoryExist(this, getProject(), getConfigurationModule().getModule());
346     JavaParametersUtil.checkAlternativeJRE(this);
347     //TODO add various checks here
348   }
349
350   @Override
351   public void readExternal(Element element) throws InvalidDataException {
352     PathMacroManager.getInstance(getProject()).expandPaths(element);
353     super.readExternal(element);
354     JavaRunConfigurationExtensionManager.getInstance().readExternal(this, element);
355     readModule(element);
356     DefaultJDOMExternalizer.readExternal(this, element);
357     DefaultJDOMExternalizer.readExternal(getPersistantData(), element);
358     EnvironmentVariablesComponent.readExternal(element, getPersistantData().getEnvs());
359
360     Map<String, String> properties = getPersistantData().TEST_PROPERTIES;
361     properties.clear();
362     Element propertiesElement = element.getChild("properties");
363     if (propertiesElement != null) {
364       List<Element> children = propertiesElement.getChildren("property");
365       for (Element property : children) {
366         properties.put(property.getAttributeValue("name"), property.getAttributeValue("value"));
367       }
368     }
369
370     List<String> listeners = getPersistantData().TEST_LISTENERS;
371     listeners.clear();
372     Element listenersElement = element.getChild("listeners");
373     if (listenersElement != null) {
374       List<Element> children = listenersElement.getChildren("listener");
375       for (Element listenerClassName : children) {
376         listeners.add(listenerClassName.getAttributeValue("class"));
377       }
378     }
379     final Element patternsElement = element.getChild(PATTERNS_EL_NAME);
380     if (patternsElement != null) {
381       final Set<String> tests = new LinkedHashSet<String>();
382       for (Object o : patternsElement.getChildren(PATTERN_EL_NAME)) {
383         Element patternElement = (Element)o;
384         tests.add(patternElement.getAttributeValue(TEST_CLASS_ATT_NAME));
385       }
386       getPersistantData().setPatterns(tests);
387     }
388   }
389
390   @Override
391   public void writeExternal(Element element) throws WriteExternalException {
392     super.writeExternal(element);
393     JavaRunConfigurationExtensionManager.getInstance().writeExternal(this, element);
394     writeModule(element);
395     DefaultJDOMExternalizer.writeExternal(this, element);
396     DefaultJDOMExternalizer.writeExternal(getPersistantData(), element);
397     EnvironmentVariablesComponent.writeExternal(element, getPersistantData().getEnvs());
398
399     Element propertiesElement = element.getChild("properties");
400
401     if (propertiesElement == null) {
402       propertiesElement = new Element("properties");
403       element.addContent(propertiesElement);
404     }
405
406     Map<String, String> properties = getPersistantData().TEST_PROPERTIES;
407     for (Map.Entry<String, String> entry : properties.entrySet()) {
408       Element property = new Element("property");
409       property.setAttribute("name", entry.getKey());
410       property.setAttribute("value", entry.getValue());
411       propertiesElement.addContent(property);
412     }
413
414     Element listenersElement = element.getChild("listeners");
415     if (listenersElement == null) {
416       listenersElement = new Element("listeners");
417       element.addContent(listenersElement);
418     }
419
420     List<String> listeners = getPersistantData().TEST_LISTENERS;
421     for (String listener : listeners) {
422       Element listenerElement = new Element("listener");
423       listenerElement.setAttribute("class", listener);
424       listenersElement.addContent(listenerElement);
425     }
426     final Set<String> patterns = getPersistantData().getPatterns();
427     if (!patterns.isEmpty()) {
428       final Element patternsElement = new Element(PATTERNS_EL_NAME);
429       for (String o : patterns) {
430         final Element patternElement = new Element(PATTERN_EL_NAME);
431         patternElement.setAttribute(TEST_CLASS_ATT_NAME, o);
432         patternsElement.addContent(patternElement);
433       }
434       element.addContent(patternsElement);
435     }
436
437     PathMacroManager.getInstance(getProject()).collapsePathsRecursively(element);
438   }
439
440   @Nullable
441   public RefactoringElementListener getRefactoringElementListener(final PsiElement element) {
442     if (data.TEST_OBJECT.equals(TestType.PACKAGE.getType())) {
443       if (!(element instanceof PsiPackage)) return null;
444       final RefactoringElementListener listener = RefactoringListeners.getListener((PsiPackage)element, myPackage);
445       return RunConfigurationExtension.wrapRefactoringElementListener(element, this, listener);
446     }
447     else if (data.TEST_OBJECT.equals(TestType.CLASS.getType())) {
448       if (!(element instanceof PsiClass)) return null;
449       final RefactoringElementListener listener = RefactoringListeners.getClassOrPackageListener(element, myClass);
450       return RunConfigurationExtension.wrapRefactoringElementListener(element, this, listener);
451     }
452     else if (data.TEST_OBJECT.equals(TestType.METHOD.getType())) {
453       if (!(element instanceof PsiMethod)) {
454         final RefactoringElementListener listener = RefactoringListeners.getClassOrPackageListener(element, myClass);
455         return RunConfigurationExtension.wrapRefactoringElementListener(element, this, listener);
456       }
457       final PsiMethod method = (PsiMethod)element;
458       if (!method.getName().equals(data.getMethodName())) return null;
459       if (!method.getContainingClass().equals(myClass.getPsiElement())) return null;
460       class Listener extends RefactoringElementAdapter implements UndoRefactoringElementListener {
461         public void elementRenamedOrMoved(@NotNull final PsiElement newElement) {
462           final boolean generatedName = isGeneratedName();
463           data.setTestMethod(PsiLocation.fromPsiElement((PsiMethod)newElement));
464           if (generatedName) setGeneratedName();
465         }
466
467         @Override
468         public void undoElementMovedOrRenamed(@NotNull PsiElement newElement, @NotNull String oldQualifiedName) {
469           final int methodIdx = oldQualifiedName.indexOf("#") + 1;
470           if (methodIdx <= 0 || methodIdx >= oldQualifiedName.length()) return;
471           final boolean generatedName = isGeneratedName();
472           data.METHOD_NAME = oldQualifiedName.substring(methodIdx);
473           if (generatedName) setGeneratedName();
474         }
475       }
476       return RunConfigurationExtension.wrapRefactoringElementListener(element, this, new Listener());
477     }
478     return null;
479   }
480
481   @Override
482   public boolean collectOutputFromProcessHandler() {
483     return false;
484   }
485 }