mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
test [java]: add tests for JvmBytecodeAnalysis service
GitOrigin-RevId: 189017daa74f34a848df9d8c2f8e83b0e913deb0
This commit is contained in:
committed by
intellij-monorepo-bot
parent
489d7ef270
commit
cc3cf90b10
+151
@@ -0,0 +1,151 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.analysis.bytecode
|
||||
|
||||
import com.intellij.compiler.JavaInMemoryCompiler
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@TestApplication
|
||||
internal class JvmBytecodeReferenceAnalysisTest {
|
||||
@Test
|
||||
fun `test field reference`() {
|
||||
assertReference("public class Main { String field; }", "java/lang/String")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test super class reference`() {
|
||||
assertReference("public class Main extends Exception { }", "java/lang/Exception")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test interface reference`() {
|
||||
assertReference("public abstract class Main implements Runnable { }", "java/lang/Runnable")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test method parameter reference`() {
|
||||
assertReference("public class Main { void method(String p) {} }", "java/lang/String")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test method return type reference`() {
|
||||
assertReference("public class Main { String method() { return null; } }", "java/lang/String")
|
||||
}
|
||||
|
||||
@Suppress("RedundantThrows")
|
||||
@Test
|
||||
fun `test method exception reference`() {
|
||||
assertReference("public class Main { void method() throws Exception {} }", "java/lang/Exception")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test annotation reference`() {
|
||||
assertReference("@Deprecated public class Main { }", "java/lang/Deprecated")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test annotation value reference`() {
|
||||
assertReference(
|
||||
mapOf(
|
||||
"A" to "public @interface A { Class<?> value(); }",
|
||||
"Main" to "@A(String.class) public class Main { }"
|
||||
),
|
||||
"A", "java/lang/String"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test enum annotation value reference`() {
|
||||
assertReference(
|
||||
"""
|
||||
|import java.lang.annotation.Retention;
|
||||
|import java.lang.annotation.RetentionPolicy;
|
||||
|@Retention(RetentionPolicy.RUNTIME)
|
||||
|public @interface Main { }
|
||||
""".trimMargin(),
|
||||
"java/lang/annotation/Annotation", "java/lang/annotation/Retention", "java/lang/annotation/RetentionPolicy"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test constructor reference`() {
|
||||
assertReference("public class Main { Object field = new String(); }", "java/lang/String")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test method call reference`() {
|
||||
assertReference("public class Main { void method() { System.out.println(\"\"); } }",
|
||||
"java/lang/System", "java/io/PrintStream", "java/lang/String")
|
||||
}
|
||||
|
||||
@Suppress("AccessStaticViaInstance")
|
||||
@Test
|
||||
fun `test field access reference`() {
|
||||
assertReference("public class Main { void method() { String s = \"\"; int i = s.CASE_INSENSITIVE_ORDER.hashCode(); } }",
|
||||
"java/lang/String",
|
||||
"java/util/Comparator")
|
||||
}
|
||||
|
||||
@Suppress("ResultOfObjectAllocationIgnored", "CatchMayIgnoreException")
|
||||
@Test
|
||||
fun `test catch reference`() {
|
||||
assertReference("public class Main { void method() { try { new Object(); } catch (Exception e) { } } }",
|
||||
"java/lang/Exception")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test class constant reference`() {
|
||||
assertReference("public class Main { Class<?> c = String.class; }",
|
||||
"java/lang/Class", "java/lang/String")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test generic field reference`() {
|
||||
assertReference("import java.util.List; public class Main { List<String> field; }",
|
||||
"java/util/List", "java/lang/String")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test generic method parameter reference`() {
|
||||
assertReference("import java.util.List; public class Main { void method(List<String> p) {} }",
|
||||
"java/util/List", "java/lang/String")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test generic method return type reference`() {
|
||||
assertReference("import java.util.List; public class Main { List<String> method() { return null; } }",
|
||||
"java/util/List", "java/lang/String")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test type annotation reference`() {
|
||||
assertReference("import java.lang.annotation.*; @Target(ElementType.TYPE_USE) @interface A {} public class Main { @A String field; }",
|
||||
"java/lang/String",
|
||||
"A")
|
||||
}
|
||||
|
||||
private fun assertReference(@Language("JAVA") source: String, vararg expectedTargets: String) {
|
||||
assertReference(mapOf("Main" to source), *expectedTargets)
|
||||
}
|
||||
|
||||
private fun assertReference(sources: Map<String, String>, vararg expectedTargets: String) {
|
||||
val compiler = JavaInMemoryCompiler()
|
||||
val compiledClasses = compiler.compile(sources)
|
||||
val mainClassBytes = compiledClasses["Main"] ?: throw IllegalStateException("Main class not found")
|
||||
|
||||
val analysis = JvmBytecodeAnalysis.getInstance()
|
||||
val references = mutableSetOf<String>()
|
||||
val referenceProcessor = object : JvmBytecodeReferenceProcessor {
|
||||
override fun processClassReference(targetClass: JvmClassBytecodeDeclaration, sourceClass: JvmClassBytecodeDeclaration) {
|
||||
references.add(targetClass.binaryClassName)
|
||||
}
|
||||
}
|
||||
|
||||
val analyzer = analysis.createReferenceAnalyzer(referenceProcessor)
|
||||
analyzer.processFileContent(mainClassBytes)
|
||||
references.remove("java/lang/Object") //always referenced as the superclass
|
||||
assertThat(references).containsExactlyInAnyOrder(*expectedTargets)
|
||||
}
|
||||
}
|
||||
@@ -45,8 +45,20 @@ public final class JavaInMemoryCompiler {
|
||||
* @return the compiled classes as a map (a class Name -> the class compiled content)
|
||||
*/
|
||||
public Map<String, byte[]> compile(String className, @Language("JAVA") String code) {
|
||||
return compile(Collections.singletonMap(className, code));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the given Java source classes.
|
||||
* @param sources mapping from dot-separated class names to their source code
|
||||
* @return the compiled classes as a map (a class Name -> the class compiled content)
|
||||
*/
|
||||
public Map<String, byte[]> compile(Map<String, String> sources) {
|
||||
final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
|
||||
final Iterable<? extends JavaFileObject> compilationUnits = Collections.singletonList(new JavaSourceFromString(className, code));
|
||||
final List<JavaSourceFromString> compilationUnits = new ArrayList<>();
|
||||
for (Map.Entry<String, String> entry : sources.entrySet()) {
|
||||
compilationUnits.add(new JavaSourceFromString(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
final Iterable<String> options = Collections.singletonList("-g"); // generate debugging info.
|
||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
|
||||
final OutputStreamWriter out = new OutputStreamWriter(System.err, StandardCharsets.UTF_8);
|
||||
@@ -62,7 +74,7 @@ public final class JavaInMemoryCompiler {
|
||||
throw new RuntimeException(writer.toString());
|
||||
}
|
||||
final List<InMemoryClassFile> classFiles = myFileManager.getClassFiles();
|
||||
final Map<String, byte[]> result = new HashMap<>(2);
|
||||
final Map<String, byte[]> result = new HashMap<>(classFiles.size());
|
||||
for (InMemoryClassFile classFile : classFiles) {
|
||||
result.put(classFile.getClassName(), classFile.getBytes());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user