Refactor Python tests

- move PyResolveTest to common tests
- support common test fixture for both platform and analysis tool

GitOrigin-RevId: dafe7a942b1c9d4d927dc5e2f412a0941e0b0dd1
This commit is contained in:
Stanislav Utikeev
2019-12-03 15:11:09 +00:00
committed by intellij-monorepo-bot
parent 606f7c9d61
commit e871a56af0
9 changed files with 2059 additions and 1462 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python;
/**
* @author yole
*/
public class PythonTestUtil {
private PythonTestUtil() {
}
public static String getTestDataPath() {
return PythonHelpersLocator.getPythonCommunityPath() + "/testData";
}
}
@@ -0,0 +1,47 @@
package com.jetbrains.python.fixture
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.util.ThrowableRunnable
import com.jetbrains.python.PythonFileType
interface CommonPythonCodeInsightTestFixture {
val tempDirRoot: VirtualFile
val project: Project
val module: Module
val file: PsiFile?
val testDataRoot: VirtualFile?
val psiManager: PsiManager
fun setUp() {
}
fun tearDown() {
}
@Throws(Exception::class)
fun runTest(test: ThrowableRunnable<Throwable>) {
test.run()
}
fun addSuppressedException(e: Throwable) {
}
fun configureByFile(filePath: String): PsiFile?
fun configureByText(fileType: PythonFileType, text: String): PsiFile?
fun configureByText(fileName: String, text: String): PsiFile?
fun copyDirectoryToProject(sourceFilePath: String, targetPath: String): VirtualFile?
fun addFileToProject(relativePath: String, fileText: String): PsiFile?
}
@@ -0,0 +1,260 @@
package com.jetbrains.python.fixture;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.jetbrains.python.documentation.PyDocumentationSettings;
import com.jetbrains.python.documentation.docstrings.DocStringFormat;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.impl.PyFileImpl;
import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.sdk.PythonSdkUtil;
import gnu.trove.Equality;
import gnu.trove.THashSet;
import junit.framework.TestCase;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Assert;
import java.util.*;
public abstract class CommonPythonTestCase extends TestCase {
protected CommonPythonCodeInsightTestFixture myFixture;
protected abstract CommonPythonCodeInsightTestFixture getFixture();
@Override
protected void setUp() throws Exception {
super.setUp();
myFixture = getFixture();
myFixture.setUp();
}
@Override
protected void runTest() throws Throwable {
myFixture.runTest(() -> {
super.runTest();
});
}
@Override
protected void tearDown() throws Exception {
try {
myFixture.tearDown();
}
catch (Throwable e) {
myFixture.addSuppressedException(e);
}
finally {
super.tearDown();
}
}
@NotNull
protected String getTestName(boolean lowercaseFirstLetter) {
return getTestName(getName(), lowercaseFirstLetter);
}
protected void setLanguageLevel(@Nullable LanguageLevel languageLevel) {
PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), languageLevel);
}
protected void runWithLanguageLevel(@NotNull LanguageLevel languageLevel, @NotNull Runnable runnable) {
setLanguageLevel(languageLevel);
try {
runnable.run();
}
finally {
setLanguageLevel(null);
}
}
protected void runWithDocStringFormat(@NotNull DocStringFormat format, @NotNull Runnable runnable) {
final PyDocumentationSettings settings = PyDocumentationSettings.getInstance(myFixture.getModule());
final DocStringFormat oldFormat = settings.getFormat();
settings.setFormat(format);
try {
runnable.run();
}
finally {
settings.setFormat(oldFormat);
}
}
protected void assertProjectFilesNotParsed(@NotNull PsiFile currentFile) {
assertRootNotParsed(currentFile, myFixture.getTempDirRoot(), null);
}
protected void assertProjectFilesNotParsed(@NotNull TypeEvalContext context) {
assertRootNotParsed(context.getOrigin(), myFixture.getTempDirRoot(), null);
}
protected void assertSdkRootsNotParsed(@NotNull PsiFile currentFile) {
final Sdk testSdk = PythonSdkUtil.findPythonSdk(currentFile);
for (VirtualFile root : testSdk.getRootProvider().getFiles(OrderRootType.CLASSES)) {
assertRootNotParsed(currentFile, root, null);
}
}
private void assertRootNotParsed(@NotNull PsiFile currentFile, @NotNull VirtualFile root, @Nullable TypeEvalContext context) {
for (VirtualFile file : VfsUtil.collectChildrenRecursively(root)) {
final PyFile pyFile = PyUtil.as(myFixture.getPsiManager().findFile(file), PyFile.class);
if (pyFile != null && !pyFile.equals(currentFile) && (context == null || !context.maySwitchToAST(pyFile))) {
assertNotParsed(pyFile);
}
}
}
@Contract("null, _ -> fail")
@NotNull
public static <T> T assertInstanceOf(Object o, @NotNull Class<T> aClass) {
Assert.assertNotNull("Expected instance of: " + aClass.getName() + " actual: " + null, o);
Assert.assertTrue("Expected instance of: " + aClass.getName() + " actual: " + o.getClass().getName(), aClass.isInstance(o));
@SuppressWarnings("unchecked") T t = (T)o;
return t;
}
@NotNull
public static String getTestName(@NotNull String name, boolean lowercaseFirstLetter) {
name = StringUtil.trimStart(name, "test");
return StringUtil.isEmpty(name) ? "" : lowercaseFirstLetter(name, lowercaseFirstLetter);
}
@NotNull
public static String lowercaseFirstLetter(@NotNull String name, boolean lowercaseFirstLetter) {
if (lowercaseFirstLetter && !isAllUppercaseName(name)) {
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
}
return name;
}
public static boolean isAllUppercaseName(@NotNull String name) {
int uppercaseChars = 0;
for (int i = 0; i < name.length(); i++) {
if (Character.isLowerCase(name.charAt(i))) {
return false;
}
if (Character.isUpperCase(name.charAt(i))) {
uppercaseChars++;
}
}
return uppercaseChars >= 3;
}
public static <T> void assertEmpty(@NotNull String errorMsg, @NotNull Collection<? extends T> collection) {
assertOrderedEquals(errorMsg, collection, Collections.emptyList());
}
public static void assertEmpty(@NotNull Collection<?> collection) {
assertEmpty(collection.toString(), collection);
}
@NotNull
public static String toString(@NotNull Iterable<?> collection) {
if (!collection.iterator().hasNext()) {
return "<empty>";
}
final StringBuilder builder = new StringBuilder();
for (final Object o : collection) {
if (o instanceof THashSet) {
builder.append(new TreeSet<>((THashSet<?>)o));
}
else {
builder.append(o);
}
builder.append('\n');
}
return builder.toString();
}
private static <T> boolean equals(@NotNull Iterable<? extends T> a1,
@NotNull Iterable<? extends T> a2,
@NotNull Equality<? super T> comparator) {
Iterator<? extends T> it1 = a1.iterator();
Iterator<? extends T> it2 = a2.iterator();
while (it1.hasNext() || it2.hasNext()) {
if (!it1.hasNext() || !it2.hasNext()) return false;
if (!comparator.equals(it1.next(), it2.next())) return false;
}
return true;
}
@SafeVarargs
public static <T> void assertOrderedEquals(@NotNull T[] actual, @NotNull T... expected) {
assertOrderedEquals(Arrays.asList(actual), expected);
}
@SafeVarargs
public static <T> void assertOrderedEquals(@NotNull Iterable<? extends T> actual, @NotNull T... expected) {
assertOrderedEquals("", actual, expected);
}
public static void assertOrderedEquals(@NotNull byte[] actual, @NotNull byte[] expected) {
assertEquals(expected.length, actual.length);
for (int i = 0; i < actual.length; i++) {
byte a = actual[i];
byte e = expected[i];
assertEquals("not equals at index: " + i, e, a);
}
}
public static void assertOrderedEquals(@NotNull int[] actual, @NotNull int[] expected) {
if (actual.length != expected.length) {
fail("Expected size: " +
expected.length +
"; actual: " +
actual.length +
"\nexpected: " +
Arrays.toString(expected) +
"\nactual : " +
Arrays.toString(actual));
}
for (int i = 0; i < actual.length; i++) {
int a = actual[i];
int e = expected[i];
assertEquals("not equals at index: " + i, e, a);
}
}
@SafeVarargs
public static <T> void assertOrderedEquals(@NotNull String errorMsg, @NotNull Iterable<? extends T> actual, @NotNull T... expected) {
assertOrderedEquals(errorMsg, actual, Arrays.asList(expected));
}
public static <T> void assertOrderedEquals(@NotNull Iterable<? extends T> actual, @NotNull Iterable<? extends T> expected) {
assertOrderedEquals("", actual, expected);
}
public static <T> void assertOrderedEquals(@NotNull String errorMsg,
@NotNull Iterable<? extends T> actual,
@NotNull Iterable<? extends T> expected) {
//noinspection unchecked
assertOrderedEquals(errorMsg, actual, expected, Equality.CANONICAL);
}
public static <T> void assertOrderedEquals(@NotNull String errorMsg,
@NotNull Iterable<? extends T> actual,
@NotNull Iterable<? extends T> expected,
@NotNull Equality<? super T> comparator) {
if (!equals(actual, expected, comparator)) {
String expectedString = toString(expected);
String actualString = toString(actual);
Assert.assertEquals(errorMsg, expectedString, actualString);
Assert.fail("Warning! 'toString' does not reflect the difference.\nExpected: " + expectedString + "\nActual: " + actualString);
}
}
protected static void assertNotParsed(PsiFile file) {
assertInstanceOf(file, PyFileImpl.class);
assertNull("Operations should have been performed on stubs but caused file to be parsed: " + file.getVirtualFile().getPath(),
((PyFileImpl)file).getTreeElement());
}
}
@@ -0,0 +1,138 @@
package com.jetbrains.python.fixture;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
public abstract class PyResolveTestCase extends CommonPythonTestCase {
@NonNls protected static final String MARKER = "<ref>";
protected PsiReference configureByFile(final String filePath) {
VirtualFile testDataRoot = myFixture.getTestDataRoot();
assertNotNull(testDataRoot);
final VirtualFile file = testDataRoot.findFileByRelativePath(filePath);
assertNotNull(file);
String fileText;
try {
fileText = StringUtil.convertLineSeparators(VfsUtilCore.loadText(file));
}
catch (IOException e) {
throw new RuntimeException(e);
}
int offset = fileText.indexOf(MARKER);
assertTrue(offset >= 0);
fileText = fileText.substring(0, offset) + fileText.substring(offset + MARKER.length());
final String finalFileText = fileText;
myFixture.configureByText(new File(filePath).getName(), finalFileText);
return myFixture.getFile().findReferenceAt(offset);
}
protected abstract PsiElement doResolve();
protected <T extends PsiElement> T assertResolvesTo(final LanguageLevel langLevel, final Class<T> aClass, final String name) {
final Ref<T> result = new Ref<>();
runWithLanguageLevel(
langLevel,
() -> result.set(assertResolvesTo(aClass, name, null))
);
return result.get();
}
protected <T extends PsiElement> T assertResolvesTo(final Class<T> aClass, final String name) {
return assertResolvesTo(aClass, name, null);
}
protected <T extends PsiElement> T assertResolvesTo(final Class<T> aClass,
final String name,
String containingFilePath) {
final PsiElement element;
try {
element = doResolve();
}
catch (Exception e) {
throw new RuntimeException(e);
}
return assertResolveResult(element, aClass, name, containingFilePath);
}
protected void assertUnresolved() {
final PsiElement element;
try {
element = doResolve();
}
catch (Exception e) {
throw new RuntimeException(e);
}
assertNull(element);
}
public static <T extends PsiElement> T assertResolveResult(PsiElement element,
Class<T> aClass,
String name) {
return assertResolveResult(element, aClass, name, null);
}
public static <T extends PsiElement> T assertResolveResult(PsiElement element,
Class<T> aClass,
String name,
@Nullable String containingFilePath) {
assertInstanceOf(element, aClass);
assertEquals(name, ((PsiNamedElement)element).getName());
if (containingFilePath != null) {
VirtualFile virtualFile = element.getContainingFile().getVirtualFile();
// TODO: find places where it is crucial [utikeev] and support in analysis tool
//if (virtualFile.getFileSystem() instanceof TempFileSystem) {
// assertEquals(containingFilePath, virtualFile.getPath());
//}
//else {
// assertEquals(containingFilePath, virtualFile.getName());
//}
assertEquals(containingFilePath, virtualFile.getName());
}
return (T)element;
}
public static int findMarkerOffset(final PsiFile psiFile) {
// TODO: harmonize with CythonResolveTest synax
// TODO: check and fix work with single letter identifiers
Document document = PsiDocumentManager.getInstance(psiFile.getProject()).getDocument(psiFile);
assert document != null;
int offset = -1;
for (int i = 1; i < document.getLineCount(); i++) {
int lineStart = document.getLineStartOffset(i);
int lineEnd = document.getLineEndOffset(i);
final int index = document.getCharsSequence().subSequence(lineStart, lineEnd).toString().indexOf("<ref>");
if (index > 0) {
offset = document.getLineStartOffset(i - 1) + index;
}
}
assertTrue("<ref> in test file not found", offset >= 0);
return offset;
}
@NotNull
public static PsiReference findReferenceByMarker(PsiFile psiFile) {
final PsiReference ref = psiFile.findReferenceAt(findMarkerOffset(psiFile));
assertNotNull("No reference found at <ref> position", ref);
return ref;
}
protected static void assertIsBuiltin(@Nullable PsiElement element) {
assertNotNull(element);
assertTrue(PyBuiltinCache.getInstance(element).isBuiltin(element));
}
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
<orderEntry type="module" module-name="intellij.python.psi" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.core.impl" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.psi.impl" scope="TEST" />
<orderEntry type="library" scope="TEST" name="kotlin-stdlib-jdk8" level="project" />
<orderEntry type="module" module-name="intellij.platform.analysis" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.projectModel" scope="TEST" />
</component>
</module>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,103 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.fixtures
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.testFramework.TestLoggerFactory
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import com.intellij.util.ThrowableRunnable
import com.jetbrains.python.PythonFileType
import com.jetbrains.python.PythonTestUtil
import com.jetbrains.python.fixture.CommonPythonCodeInsightTestFixture
import java.io.File
import java.lang.reflect.InvocationTargetException
class PlatformPythonCodeInsightTestFixture : CommonPythonCodeInsightTestFixture {
private val myDelegateTestCase = PyDelegateTestCase()
private val myDelegateFixture: CodeInsightTestFixture
get() = myDelegateTestCase.myFixture
override val project: Project
get() = myDelegateFixture.project
override val module: Module
get() = myDelegateFixture.module
override val file: PsiFile?
get() = myDelegateFixture.file
override val testDataRoot: VirtualFile?
get() = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(File(PythonTestUtil.getTestDataPath()))
override val psiManager: PsiManager
get() = myDelegateFixture.psiManager
override val tempDirRoot: VirtualFile
get() = requireNotNull(myDelegateFixture.tempDirFixture.getFile("."))
override fun setUp() {
super.setUp()
myDelegateTestCase.setUp()
}
override fun tearDown() {
myDelegateTestCase.tearDown()
super.tearDown()
}
@Throws(Exception::class)
override fun runTest(test: ThrowableRunnable<Throwable>) {
myDelegateTestCase.runTest(test)
}
override fun addSuppressedException(e: Throwable) {
myDelegateTestCase.addSuppressedException(e)
}
override fun configureByFile(filePath: String): PsiFile? = myDelegateFixture.configureByFile(filePath)
override fun configureByText(fileType: PythonFileType, text: String): PsiFile? = myDelegateFixture.configureByText(fileType, text)
override fun configureByText(fileName: String, text: String): PsiFile? = myDelegateFixture.configureByText(fileName, text)
override fun copyDirectoryToProject(sourceFilePath: String, targetPath: String): VirtualFile? =
myDelegateFixture.copyDirectoryToProject(sourceFilePath, targetPath)
override fun addFileToProject(relativePath: String, fileText: String): PsiFile? = myDelegateFixture.addFileToProject(relativePath,
fileText)
}
class PyDelegateTestCase : PyTestCase() {
public override fun addSuppressedException(e: Throwable) {
super.addSuppressedException(e)
}
@Throws(Exception::class)
fun runTest(test: ThrowableRunnable<Throwable>) {
val throwables = arrayOfNulls<Throwable>(1)
invokeTestRunnable {
try {
TestLoggerFactory.onTestStarted()
test.run()
TestLoggerFactory.onTestFinished(true)
}
catch (e: InvocationTargetException) {
TestLoggerFactory.onTestFinished(false)
e.fillInStackTrace()
throwables[0] = e.targetException
}
catch (e: IllegalAccessException) {
TestLoggerFactory.onTestFinished(false)
e.fillInStackTrace()
throwables[0] = e
}
catch (e: Throwable) {
TestLoggerFactory.onTestFinished(false)
throwables[0] = e
}
}
val throwable = throwables[0]
throwable?.let { throw it }
}
}
@@ -30,7 +30,8 @@
<orderEntry type="module" module-name="intellij.python.community.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.debugger.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.psi.impl" />
<orderEntry type="module" module-name="intellij.python.commonTests" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.community.plugin.impl" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.pycharm.community" scope="RUNTIME" />
</component>
</module>
</module>