diff --git a/src/org/atoum/intellij/plugin/atoum/Utils.java b/src/org/atoum/intellij/plugin/atoum/Utils.java index d4bbbf6..9c8a91b 100644 --- a/src/org/atoum/intellij/plugin/atoum/Utils.java +++ b/src/org/atoum/intellij/plugin/atoum/Utils.java @@ -1,13 +1,17 @@ package org.atoum.intellij.plugin.atoum; -import com.google.common.collect.Lists; import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.php.PhpIndex; +import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment; +import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag; import com.jetbrains.php.lang.psi.PhpFile; +import com.jetbrains.php.lang.psi.elements.Method; +import com.jetbrains.php.lang.psi.elements.MethodReference; import com.jetbrains.php.lang.psi.elements.ClassReference; import com.jetbrains.php.lang.psi.elements.PhpClass; -import org.apache.commons.lang.StringUtils; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -19,8 +23,26 @@ public class Utils { public static Boolean isClassAtoumTest(PhpClass checkedClass) { // First, we check if the class is in the units tests namespace - if (!checkedClass.getNamespaceName().toLowerCase().contains(getTestsNamespaceSuffix().toLowerCase())) { - return false; + String testNamespaceSuffix = getTestsNamespaceSuffix(checkedClass); + if (testNamespaceSuffix != null) { + // this is in a custom test namespace, check if it is really in + if (!checkedClass.getNamespaceName().toLowerCase().contains(testNamespaceSuffix.toLowerCase())) { + return false; + } + } else { + // no custom namespace. We test all default possibilities + boolean found = false; + for (String defaultNamespace : getDefaultTestsNamespaces()) { + if (checkedClass.getNamespaceName().toLowerCase().contains(defaultNamespace)) { + found = true; + break; + } + } + + // None of the default namespace matched, not a test class + if (!found) { + return false; + } } if (checkedClass.isAbstract() || checkedClass.isInterface()) { @@ -64,24 +86,27 @@ public static PhpClass locateTestClass(Project project, PhpClass testedClass) { @Nullable public static Collection locateTestClasses(Project project, PhpClass testedClass) { - if (testedClass.getNamespaceName().length() == 1) { - Collection foundClasses = locatePhpClasses(project, getTestsNamespaceSuffix() + testedClass.getName()); - if (foundClasses.size() > 0) { - return foundClasses; + // Search classes with same name + for (PhpClass similarClass : PhpIndex.getInstance(project).getClassesByName(testedClass.getName())) { + // Skip testedClass + if (similarClass.getFQN().equals(testedClass.getFQN())) { + continue; } - } - - String[] namespaceParts = testedClass.getNamespaceName().split("\\\\"); - for(int i=namespaceParts.length; i>=1; i--){ - List foo = Lists.newArrayList(namespaceParts); - foo.add(i, getTestsNamespaceSuffix().substring(0, getTestsNamespaceSuffix().length() - 1)); + // Skip classes which don't have the same base namespace + if (!similarClass.getNamespaceName().startsWith(testedClass.getNamespaceName())) { + continue; + } - String possibleClassname = StringUtils.join(foo, "\\") + "\\" + testedClass.getName(); - Collection foundClasses = locatePhpClasses(project, possibleClassname); - if (foundClasses.size() > 0) { - return foundClasses; + // Skip non atoum classes + if (!isClassAtoumTest(similarClass)) { + continue; } + + ArrayList data = new ArrayList<>(); + data.add(similarClass); + + return data; } return new ArrayList(); @@ -97,11 +122,36 @@ public static PhpClass locateTestedClass(Project project, PhpClass testClass) { return null; } - @Nullable public static Collection locateTestedClasses(Project project, PhpClass testClass) { - String testClassNamespaceName = testClass.getNamespaceName(); - String testedClassname = testClassNamespaceName.toLowerCase().replace(getTestsNamespaceSuffix().toLowerCase(), "") + testClass.getName(); - return locatePhpClasses(project, testedClassname); + String testClassNamespaceName = testClass.getNamespaceName().toLowerCase(); + String testNamespaceSuffix = getTestsNamespaceSuffix(testClass); + + if (testNamespaceSuffix != null) { + // it's a custom namespace. Ensure the class is really in this namespace + if (!testClassNamespaceName.contains(testNamespaceSuffix.toLowerCase())) { + return new ArrayList<>(); + } + + String testedClassname = testClassNamespaceName.replace(testNamespaceSuffix.toLowerCase(), "") + testClass.getName(); + + return locatePhpClasses(project, testedClassname); + } + + // Try with default namespaces + for (String defaultNamespace : getDefaultTestsNamespaces()) { + if (!testClassNamespaceName.contains(defaultNamespace)) { + continue; + } + + String testedClassname = testClassNamespaceName.replace(defaultNamespace, "") + testClass.getName(); + + Collection items = locatePhpClasses(project, testedClassname); + if (!items.isEmpty()) { + return items; + } + } + + return new ArrayList<>(); } @Nullable @@ -114,14 +164,91 @@ protected static PhpClass locatePhpClass(Project project, String name) { return (PhpClass)phpClasses.toArray()[0]; } - @Nullable protected static Collection locatePhpClasses(Project project, String name) { return PhpIndex.getInstance(project).getAnyByFQN(name); } - private static String getTestsNamespaceSuffix() + @Nullable + private static String getTestsNamespaceSuffix(PhpClass phpClass) + { + while (phpClass != null) { + // Check @namespace annotation in the class + PhpDocComment docComment = phpClass.getDocComment(); + if (docComment != null) { + PhpDocTag[] tags = docComment.getTagElementsByName("@namespace"); + + if (tags.length > 0) { + return checkBackslashes(tags[0].getTagValue()); + } + } + + // Check setTestNamespace call in __construct + Method constructor = phpClass.getOwnConstructor(); + if (constructor != null) { + MethodReference setTestNamespaceCall = findMethodCallInElement(constructor, "setTestNamespace"); + + if (setTestNamespaceCall != null && setTestNamespaceCall.getParameters().length > 0) { + PsiElement callParameter = setTestNamespaceCall.getParameters()[0]; + + if (callParameter instanceof StringLiteralExpression) { + return checkBackslashes(((StringLiteralExpression) callParameter).getContents()); + } + } + } + + // Not found? Try with parent class + phpClass = phpClass.getSuperClass(); + } + + // Return null to use default value if no @namespace or $this->setTestNamespace() were found + return null; + } + + // Always return lowercase namespaces + private static String[] getDefaultTestsNamespaces() + { + return new String[] { + "tests\\units\\", + "test\\unit\\", + }; + } + + /** + * Ensure namespace doesn't start with \ but ends with \ + */ + private static String checkBackslashes(String namespace) + { + if (namespace.startsWith("\\")) { + namespace = namespace.substring(1); + } + + if (!namespace.endsWith("\\")) { + namespace += "\\"; + } + + return namespace; + } + + @Nullable + private static MethodReference findMethodCallInElement(PsiElement element, String name) { - return "tests\\units\\"; + for (PsiElement child : element.getChildren()) + { + if (child instanceof MethodReference) { + MethodReference method = (MethodReference) child; + + if (method.getName() != null && method.getName().equals(name)) { + return method; + } + } + + MethodReference result = findMethodCallInElement(child, name); + if (result != null) { + return result; + } + } + + return null; } //https://github.com/Haehnchen/idea-php-symfony2-plugin/blob/cb422db9779025d65fdf0ba5d26a38d401eca939/src/fr/adrienbrault/idea/symfony2plugin/util/PhpElementsUtil.java#L784 diff --git a/tests/org/atoum/intellij/plugin/atoum/tests/UtilsTest.java b/tests/org/atoum/intellij/plugin/atoum/tests/UtilsTest.java index 2d03747..e2cf806 100644 --- a/tests/org/atoum/intellij/plugin/atoum/tests/UtilsTest.java +++ b/tests/org/atoum/intellij/plugin/atoum/tests/UtilsTest.java @@ -14,13 +14,13 @@ protected String getTestDataPath() { public void testGetFirstClassFromFile() { PhpClass phpClass = Utils.getFirstClassFromFile((PhpFile) myFixture.configureByFile( - "TestSimpleClass.php" + "tests/TestSimpleClass.php" )); assertNotNull(phpClass); assertEquals("\\PhpStormPlugin\\tests\\units\\TestSimpleClass", phpClass.getFQN()); phpClass = Utils.getFirstClassFromFile((PhpFile) myFixture.configureByFile( - "TestMultipleClassNotFirst.php" + "tests/TestMultipleClassNotFirst.php" )); assertNotNull(phpClass); assertEquals("\\PhpStormPlugin\\tests\\units\\AnotherClassDefinedBeforeTheTest", phpClass.getFQN()); @@ -31,73 +31,171 @@ public void testGetFirstTestClassFromFile() { myFixture.copyFileToProject("atoum.php"); PhpClass phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( - "TestSimpleClass.php" + "tests/TestSimpleClass.php" )); assertNotNull(phpClass); assertEquals("\\PhpStormPlugin\\tests\\units\\TestSimpleClass", phpClass.getFQN()); phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( - "TestMultipleClassNotFirst.php" + "tests/TestMultipleClassNotFirst.php" )); assertNotNull(phpClass); assertEquals("\\PhpStormPlugin\\tests\\units\\TestMultipleClassNotFirst", phpClass.getFQN()); phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( - "TestSimpleClassWithoutUse.php" + "tests/TestSimpleClassWithoutUse.php" )); assertNotNull(phpClass); assertEquals("\\PhpStormPlugin\\tests\\units\\TestSimpleClassWithoutUse", phpClass.getFQN()); phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( - "TestWithParentClass.php" + "tests/TestWithParentClass.php" )); assertNotNull(phpClass); assertEquals("\\PhpStormPlugin\\tests\\units\\TestWithParentClass", phpClass.getFQN()); phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( - "TestWrongExtends.php" + "tests/TestWrongExtends.php" )); assertNull(phpClass); phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( - "TestWrongNamespace.php" + "tests/TestWrongNamespace.php" )); assertNull(phpClass); + + phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( + "tests/TestCustomNamespaceAnnotation.php" + )); + assertNotNull(phpClass); + assertEquals("\\PhpStormPlugin\\toto\\tata\\TestCustomNamespaceAnnotation", phpClass.getFQN()); + + phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( + "tests/TestCustomNamespaceMethodCall.php" + )); + assertNotNull(phpClass); + assertEquals("\\PhpStormPlugin\\toto\\tata\\TestCustomNamespaceMethodCall", phpClass.getFQN()); } public void testGetFirstTestClassFromFileWithoutStubs() { PhpClass phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( - "TestSimpleClass.php" + "tests/TestSimpleClass.php" )); assertNotNull(phpClass); assertEquals("\\PhpStormPlugin\\tests\\units\\TestSimpleClass", phpClass.getFQN()); phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( - "TestMultipleClassNotFirst.php" + "tests/TestMultipleClassNotFirst.php" )); assertNotNull(phpClass); assertEquals("\\PhpStormPlugin\\tests\\units\\TestMultipleClassNotFirst", phpClass.getFQN()); phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( - "TestSimpleClassWithoutUse.php" + "tests/TestSimpleClassWithoutUse.php" )); assertNotNull(phpClass); assertEquals("\\PhpStormPlugin\\tests\\units\\TestSimpleClassWithoutUse", phpClass.getFQN()); phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( - "TestWithParentClass.php" + "tests/TestWithParentClass.php" )); assertNotNull(phpClass); assertEquals("\\PhpStormPlugin\\tests\\units\\TestWithParentClass", phpClass.getFQN()); phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( - "TestWrongExtends.php" + "tests/TestWrongExtends.php" )); assertNull(phpClass); phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( - "TestWrongNamespace.php" + "tests/TestWrongNamespace.php" )); assertNull(phpClass); + + phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( + "tests/TestCustomNamespaceAnnotation.php" + )); + assertNotNull(phpClass); + assertEquals("\\PhpStormPlugin\\toto\\tata\\TestCustomNamespaceAnnotation", phpClass.getFQN()); + + phpClass = Utils.getFirstTestClassFromFile((PhpFile) myFixture.configureByFile( + "tests/TestCustomNamespaceMethodCall.php" + )); + assertNotNull(phpClass); + assertEquals("\\PhpStormPlugin\\toto\\tata\\TestCustomNamespaceMethodCall", phpClass.getFQN()); + } + + public void testLocateTestClass() + { + // Load fake tests + myFixture.copyDirectoryToProject("tests", "tests"); + + PhpClass phpClass = Utils.getFirstClassFromFile((PhpFile) myFixture.configureByFile( + "src/TestSimpleClass.php" + )); + phpClass = Utils.locateTestClass(this.getProject(), phpClass); + assertNotNull(phpClass); + assertEquals("\\PhpStormPlugin\\tests\\units\\TestSimpleClass", phpClass.getFQN()); + + phpClass = Utils.getFirstClassFromFile((PhpFile) myFixture.configureByFile( + "src/TestCustomNamespaceAnnotation.php" + )); + phpClass = Utils.locateTestClass(this.getProject(), phpClass); + assertNotNull(phpClass); + assertEquals("\\PhpStormPlugin\\toto\\tata\\TestCustomNamespaceAnnotation", phpClass.getFQN()); + + phpClass = Utils.getFirstClassFromFile((PhpFile) myFixture.configureByFile( + "src/TestCustomNamespaceMethodCall.php" + )); + phpClass = Utils.locateTestClass(this.getProject(), phpClass); + assertNotNull(phpClass); + assertEquals("\\PhpStormPlugin\\toto\\tata\\TestCustomNamespaceMethodCall", phpClass.getFQN()); + + phpClass = Utils.getFirstClassFromFile((PhpFile) myFixture.configureByFile( + "src/TestNamespaceUppercaseSingular.php" + )); + phpClass = Utils.locateTestClass(this.getProject(), phpClass); + assertNotNull(phpClass); + assertEquals("\\PhpStormPlugin\\test\\UNIT\\TestNamespaceUppercaseSingular", phpClass.getFQN()); + + phpClass = Utils.getFirstClassFromFile((PhpFile) myFixture.configureByFile( + "src/TestClassWithoutTest.php" + )); + phpClass = Utils.locateTestClass(this.getProject(), phpClass); + assertNull(phpClass); + } + + public void testLocateTestedClass() + { + // Load fake code + myFixture.copyDirectoryToProject("src", "src"); + + PhpClass phpClass = Utils.getFirstClassFromFile((PhpFile) myFixture.configureByFile( + "tests/TestSimpleClass.php" + )); + phpClass = Utils.locateTestedClass(this.getProject(), phpClass); + assertNotNull(phpClass); + assertEquals("\\PhpStormPlugin\\TestSimpleClass", phpClass.getFQN()); + + phpClass = Utils.getFirstClassFromFile((PhpFile) myFixture.configureByFile( + "tests/TestCustomNamespaceAnnotation.php" + )); + phpClass = Utils.locateTestedClass(this.getProject(), phpClass); + assertNotNull(phpClass); + assertEquals("\\PhpStormPlugin\\TestCustomNamespaceAnnotation", phpClass.getFQN()); + + phpClass = Utils.getFirstClassFromFile((PhpFile) myFixture.configureByFile( + "tests/TestCustomNamespaceMethodCall.php" + )); + phpClass = Utils.locateTestedClass(this.getProject(), phpClass); + assertNotNull(phpClass); + assertEquals("\\PhpStormPlugin\\TestCustomNamespaceMethodCall", phpClass.getFQN()); + + phpClass = Utils.getFirstClassFromFile((PhpFile) myFixture.configureByFile( + "tests/TestNamespaceUppercaseSingular.php" + )); + phpClass = Utils.locateTestedClass(this.getProject(), phpClass); + assertNotNull(phpClass); + assertEquals("\\PhpStormPlugin\\TestNamespaceUppercaseSingular", phpClass.getFQN()); } } diff --git a/tests/org/atoum/intellij/plugin/atoum/tests/fixtures/src/TestClassWithoutTest.php b/tests/org/atoum/intellij/plugin/atoum/tests/fixtures/src/TestClassWithoutTest.php new file mode 100644 index 0000000..379187f --- /dev/null +++ b/tests/org/atoum/intellij/plugin/atoum/tests/fixtures/src/TestClassWithoutTest.php @@ -0,0 +1,8 @@ +setTestNamespace('\toto\tata'); + } +} diff --git a/tests/org/atoum/intellij/plugin/atoum/tests/fixtures/TestMultipleClassNotFirst.php b/tests/org/atoum/intellij/plugin/atoum/tests/fixtures/tests/TestMultipleClassNotFirst.php similarity index 100% rename from tests/org/atoum/intellij/plugin/atoum/tests/fixtures/TestMultipleClassNotFirst.php rename to tests/org/atoum/intellij/plugin/atoum/tests/fixtures/tests/TestMultipleClassNotFirst.php diff --git a/tests/org/atoum/intellij/plugin/atoum/tests/fixtures/tests/TestNamespaceUppercaseSingular.php b/tests/org/atoum/intellij/plugin/atoum/tests/fixtures/tests/TestNamespaceUppercaseSingular.php new file mode 100644 index 0000000..2ab0c7a --- /dev/null +++ b/tests/org/atoum/intellij/plugin/atoum/tests/fixtures/tests/TestNamespaceUppercaseSingular.php @@ -0,0 +1,10 @@ +