LightCodeInsightFixtureTestCase unable to resolve PsiReferenceExpression

Hello,

I am implementing a plugin, where I am resolving a PsiReferenceExpression related to PsiMethodCallExpression. When I execute the plugin, I get the result back with appropriate element. 

fun isCipherMethodCall(expression: PsiMethodCallExpression): Boolean {
val el = expression.methodExpression.resolve()
if (el != null) {
val classElement = el.getContainingClass()
if (classElement != null && classElement.name.equals("Cipher"))
return true
}
return false
}

I am currently writing a test to evaluate a feature which makes use of the above method. I am writing tests using by extending a LightCodeInsightFixtureTestCase. 

When I execute the test, it returns null for the value of el.

After debugging I found out that expression.methodExpression returns the PsiReferenceExpression and on resolve it is getting the null value in return.

When plugin runs, it gets the appropriate element from MethodCandidateInfo class. 

When test runs, getElement() of JavaResolveResult interface gets executed which returns null. JavaResolveResult extends ResolveResult.

public interface JavaResolveResult extends ResolveResult {
JavaResolveResult[] EMPTY_ARRAY = new JavaResolveResult[0];
JavaResolveResult EMPTY = new JavaResolveResult() {
public PsiElement getElement() {
return null;
}

How to resolve the element and this issue, so that test runs correctly and evaluates the element.

0
12 comments
Official comment

In your test, do you add the appropriate JDK/libraries to the test module dependencies? Does "JavaPsiFacade#findClass(className, allScope)" find the "Cipher" class by its qualified name?

Avatar
Permanently deleted user

Any hints will be appreciated :)

0

Hi,

I guess that you need to setup a corresponding library or mock required classes with methods via `myFixture.addClass()`. If it doesn't help, please attach your test as most probably there is an issue with setup.

Thanks,

Anna

0
Avatar
Permanently deleted user

Hey Anna and Peter,

My test setup looks like:

import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiFile
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import edu.ksu.cs.kila.BlockCipherECB
import edu.ksu.cs.kila.ConstantKey
import junit.framework.TestCase
import shell_plugin.MessageStructure
import java.io.File
import java.nio.file.Paths
import java.util.logging.Logger


class CKTest : LightCodeInsightFixtureTestCase() {

val LOG = Logger.getLogger(CKTest::class.java.name)

@test
fun testSetupForPresenceOfVulnerability() {
File("$testDataPath/ConstantKey/Positive/").walk().forEach {
if (!it.isDirectory && it.path.contains("/ConstantKey/Positive/")) {
myFixture.addClass(File(testDataPath+"/ConstantKeytest/Positive/Cipher.java").readLines().foldRight("", {a, b-> a+b}))
LOG.warning(JavaPsiFacade.getInstance(myFixture.project).findClass("javax.crypto.Cipher", myModule.moduleContentScope).toString())
val psiFile = myFixture.configureByFile(it.path)
LOG.warning(it.name)
val message = it.readLines()[0].substringAfter("// outputMessage:").trim()
testPresenceOfVulnerability(psiFile, message)
}

}
}

override fun getTestDataPath(): String {
return Paths.get("").toAbsolutePath().toString() + "/tests/testData"
}

fun testPresenceOfVulnerability(psi: PsiFile, message: String) {
val constantKey = ConstantKey()
constantKey.allOthers(psi)
val list = constantKey.vulnerableElements
TestCase.assertEquals(1, list.size)
testMessageStructure(psi, list, message)
}

fun testSetupForAbsenceOfVulnerability() {
File(testDataPath).walk().forEach {
if (!it.isDirectory && it.path.contains("/BlockCipherECB/Negative/")) {
val psiFile = myFixture.configureByFile(it.path)
LOG.warning(it.name)
testAbsenceOfVulnerability(psiFile)
}
}
}

@test
fun testAbsenceOfVulnerability(psi: PsiFile) {
TestCase.assertNotNull(psi)
val becb = BlockCipherECB()
becb.allOthers(psi)
TestCase.assertEquals(0, becb.vulnerableElements.size)
}

fun testMessageStructure(psiFile: PsiFile, list: MutableList<MessageStructure>, message: String) {
TestCase.assertEquals(list.first().warning, message)
}

annotation class test

}

1. I have added class using `myFixture.addClass()`.

2. Tested using "JavaPsiFacade#findClass(className, allScope)" and found PsiClass:Cipher.

Test is still evaluating the same way as described.

3. Adding the jar (seems like `jce.jar` is having javax.crypto) using `PsiTestUtil#addLibrary()` gives com.intellij.openapi.util.TraceableDisposable$DisposalException: Virtual pointer hasn't been disposed.

This exception occurs after the completion of test.

0

Please ensure that your test file text has all the necessary imports, and the created file is under some source root (FileIndexFacade#isInSourceContent). If that doesn't help, I'd suggest to debug the resolve. I'd put a breakpoint into com.intellij.psi.impl.source.PsiJavaFileBaseImpl#processDeclarationsNoGuess and see if imports are processed as you expect.

0
Avatar
Permanently deleted user

Thanks for the direction. 

I have debugged the com.intellij.psi.impl.source.PsiJavaFileBaseImpl#processDeclarationsNoGuess and found out that import statement which is related to Cipher class gets correctly resolved with PsiClass:Cipher element. 

But after resolving the element to PsiClass, the getContainingClass() returns null on the element.

0

I guess that `Cipher` is not a nested class (it's located in Cipher.java as stated above) and thus it's expected that it has no containing class.

0
Avatar
Permanently deleted user

That's true. So PsiClass is getting resolved, but not being passed to the original call. I will try to trace it again and see where I am missing the link.

0
Avatar
Permanently deleted user

I ran a small experiment by resolving methodCall java.lang.String#split. TestCase has java.lang imported.

1. Plugin is able to resolve it correctly.

2. LightCodeInsightFixturetestCase unable to resolve the same methodCall.

3. As per documentation, LightCodeInsightFixturetestCase should be used for tests that require the Java PSI or any related functionality. Does that mean LightCodeInsightFixturetestCase adds required dependencies:?

4. After adding class java.lang.String using myFixture#addClass, test is resolving the methodCall correctly.

5. I have marked the testData directory as Test Resources Root. Will this effect the end result? I have marked it as Test Resources Root because I don't want the code in testData to be compile every time I build the Plugin or run the tests.

0

Classes from jdk should be resolved, cause mock jdk is added automatically to the dependencies based on project descriptor of the test. The light tests (e.g. LightCodeInsightFixturetestCase) should not add any libraries or change the jdk during setup so the only possibility to enforce resolve to external classes is to add them as #addClass. If the test data is found, it doesn't mean how your project is configured, the project created during test run won't be affected in any way.

So you still have the class resolved though references on methods from the class are not resolved?

0
Avatar
Permanently deleted user

Yes, I am still facing the issue of resolving methodCallExpressions.

1. I am only overriding getTestDataPath()

2. I am using following VM options in runConfiguration:

<option name="VM_PARAMETERS" value="-ea -Xbootclasspath/p:../out/classes/production/boot
-XX:+HeapDumpOnOutOfMemoryError -Xmx512m
-XX:MaxPermSize=320m
-Didea.system.path=$USER_HOME$/Library/Caches/IntelliJIdea2018.1/plugins-sandbox/system
-Didea.home.path=$USER_HOME$/Library/Caches/IntelliJIdea2018.1/plugins-sandbox
-Didea.config.path=$USER_HOME$/Library/Caches/IntelliJIdea2018.1/plugins-sandbox/config
-Didea.load.plugins.id=org.jetbrains.android
-Didea.load.plugins.id=com.intellij.junit5
-Didea.test.group=ALL_EXCLUDE_DEFINED" />

3. I am adding Cipher class using myFixture#addClass()

0

Why do you need junit 5 plugin in your tests? Anyway, you need to pass required plugin ids in one property, comma (",") separated. And most important,  you need to add your plugin explicitly here, otherwise it would be skipped as are skipped all other plugins.

-Didea.test.group has no influence on local tests and is used in our shared configurations for CI integration. idea.home.path/system/config should be written automatically by devkit plugin, you don't need to specify it explicitly.

0

Please sign in to leave a comment.