1+ import groovy.transform.Field
2+
3+ @Field
4+ String javaSource = """
5+ package io.micronaut.reproduce;
6+
7+ import io.micronaut.context.ApplicationContext;
8+ import io.micronaut.context.annotation.Bean;
9+ import jakarta.inject.Inject;
10+
11+ @Bean
12+ public class Parent {
13+ @Inject ApplicationContext context;
14+
15+ public ApplicationContext getContext() {
16+ return context;
17+ }
18+ }
19+ """
20+
21+ @Field
22+ String kotlinSource = """
23+ package io.micronaut.reproduce
24+
25+ import io.micronaut.context.annotation.Bean
26+ import io.micronaut.runtime.Micronaut
27+ import jakarta.annotation.PostConstruct
28+ import io.micronaut.context.ApplicationContext // Import ApplicationContext
29+
30+ @Bean
31+ open class Child : Parent() { // Use 'open' for inheritance
32+ lateinit var injectedContext: ApplicationContext // A separate field to hold context from Parent if needed for verification in this class
33+
34+ @PostConstruct
35+ fun init() {
36+ // Here, we check if the context from the Java parent is injected
37+ // The issue states 'check(context != null)' from the original report
38+ // So we will try to access the context from the parent and ensure it's not null.
39+ if (context == null) {
40+ println("ERROR: context from Java Parent is NULL! Issue reproduced.")
41+ throw IllegalStateException("Context from Java Parent was not injected.")
42+ }
43+ this.injectedContext = context
44+ }
45+ }
46+ """
47+
48+ @Field
49+ String testSource = """
50+ package io.micronaut.reproduce
51+
52+ import io.micronaut.context.ApplicationContext
53+ import io.micronaut.runtime.Micronaut
54+ import spock.lang.Specification
55+ import spock.lang.AutoCleanup
56+
57+ class ReproduceIssueSpec extends Specification {
58+
59+ @AutoCleanup
60+ ApplicationContext applicationContext
61+
62+ void "test Kotlin child inherits injected field from Java parent with KSP"() {
63+ given:
64+ applicationContext = Micronaut.run()
65+
66+ when:
67+ Child child = applicationContext.getBean(Child)
68+
69+ then:
70+ // The @PostConstruct in Child should have thrown an exception if context is null
71+ // If it reaches here, it means @PostConstruct passed, but we'll re-check to be safe.
72+ child.context != null
73+
74+ // This assertion directly reflects the check in the Kotlin class's @PostConstruct
75+ // If the @PostConstruct didn't throw, this should pass.
76+ // If the bug is reproduced, the @PostConstruct *should* throw, causing the test to fail.
77+ // So a test failure means the bug is reproduced.
78+ }
79+ }
80+ """
81+
82+ // THIS IS WHERE THE YOUR TEST SOURCE WILL BE PLACED
83+ def testSuiteDir = new File (" test-suite/src/test/groovy/io/micronaut/reproduce" )
84+ def testSuiteKotlinJavaTestDir = new File (" test-suite-kotlin/src/test/java/io/micronaut/reproduce" )
85+ def testSuiteKotlinKotlinTestDir = new File (" test-suite-kotlin/src/test/kotlin/io/micronaut/reproduce" )
86+
87+ try {
88+ // Create directories
89+ testSuiteDir. mkdirs()
90+ testSuiteKotlinJavaTestDir. mkdirs()
91+ testSuiteKotlinKotlinTestDir. mkdirs()
92+
93+ // Write source files
94+ new File (testSuiteKotlinJavaTestDir, " Parent.java" ). write(javaSource)
95+ new File (testSuiteKotlinKotlinTestDir, " Child.kt" ). write(kotlinSource)
96+ def testFile = new File (testSuiteDir, " ReproduceIssueSpec.groovy" )
97+ testFile. write(testSource)
98+
99+ // Command to invoke the test script
100+ // We need to build and test test-suite-kotlin first to ensure KSP processing
101+ // Then run the test-suite test which depends on it
102+ def command = " ./gradlew :test-suite-kotlin:test :test-suite:test --tests io.micronaut.reproduce.ReproduceIssueSpec"
103+ println " Executing command: $command "
104+ def process = command. execute()
105+ process. waitForProcessOutput(System . out, System . err)
106+ def gradleExitCode = process. exitValue()
107+
108+ // Analyze Gradle exit code
109+ if (gradleExitCode == 0 ) {
110+ // If Gradle exited with 0, the test passed. This means the bug was NOT reproduced.
111+ println " Test passed, issue NOT reproduced."
112+ System . exit(0 )
113+ } else {
114+ // If Gradle exited with non-zero, the test failed. This indicates the bug was reproduced.
115+ // The Child's @PostConstruct should throw an exception if 'context' is null,
116+ // which would cause the test to fail.
117+ println " Test failed (Gradle exit code: $gradleExitCode ), issue REPRODUCED."
118+ System . exit(129 )
119+ }
120+ } catch (Exception e) {
121+ e. printStackTrace()
122+ System . exit(1 ) // Script error
123+ } finally {
124+ // Clean up created files and directories
125+ println " Cleaning up temporary files..."
126+ new File (testSuiteKotlinJavaTestDir, " Parent.java" ). delete()
127+ new File (testSuiteKotlinKotlinTestDir, " Child.kt" ). delete()
128+ new File (testSuiteDir, " ReproduceIssueSpec.groovy" ). delete()
129+
130+ // Attempt to delete parent directories, they might not be empty
131+ testSuiteDir. deleteDir()
132+ testSuiteKotlinJavaTestDir. deleteDir()
133+ testSuiteKotlinKotlinTestDir. deleteDir()
134+ }
0 commit comments