Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/scripts/run-graalvm-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail

# Run from the verified release checkout. It may predate the split test compilers.
available_tasks="$(./gradlew :openai-java-core:tasks --all --console=plain)"
skip_optional_compilers=()
for task in compileBetaModelTestKotlin compileAdminModelTestKotlin; do
if grep -Eq "^${task}([[:space:]]|$)" <<< "$available_tasks"; then
skip_optional_compilers+=(-x ":openai-java-core:$task")
fi
done

# Classes were compiled before switching to GraalVM. Only run the tracing tests.
./gradlew :openai-java-core:test \
-x compileJava -x compileTestJava -x compileKotlin -x compileTestKotlin \
"${skip_optional_compilers[@]}" -PgraalvmAgent
34 changes: 33 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,33 @@ jobs:
- name: Run lints
run: ./scripts/lint

build_logic:
name: CI / build logic
runs-on: ${{ vars.SDK_GHA_RUNNER || 'ubuntu-24.04' }}
timeout-minutes: 15

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
persist-credentials: false

- name: Set up Java
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
java-version: |
8
21

- name: Set up Gradle
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
cache-provider: basic
cache-read-only: true

- name: Test build logic
run: ./scripts/gradle :buildSrc:test

build:
name: CI / build
runs-on: ${{ vars.SDK_GHA_RUNNER || 'ubuntu-24.04' }}
Expand Down Expand Up @@ -128,7 +155,9 @@ jobs:
env:
GRADLE_OPTS: -Dkotlin.compiler.execution.strategy=in-process
# Release compiled outputs before running the older-Jackson suite in its own job.
run: ./scripts/build -x :openai-java-core:testJacksonCompatibility
run: >-
./scripts/gradle build testClasses verifyVersionSupportPolicy
-x test -x :openai-java-core:testJacksonCompatibility

- name: Collect exact-run Gradle build cache
env:
Expand Down Expand Up @@ -398,6 +427,7 @@ jobs:
needs:
- lint
- build
- build_logic
- test
- jackson_compatibility
- api_compatibility
Expand All @@ -411,6 +441,7 @@ jobs:
EVENT_NAME: ${{ github.event_name }}
LINT_RESULT: ${{ needs.lint.result }}
BUILD_RESULT: ${{ needs.build.result }}
BUILD_LOGIC_RESULT: ${{ needs.build_logic.result }}
TEST_RESULT: ${{ needs.test.result }}
JACKSON_COMPATIBILITY_RESULT: ${{ needs.jackson_compatibility.result }}
API_COMPATIBILITY_RESULT: ${{ needs.api_compatibility.result }}
Expand All @@ -421,6 +452,7 @@ jobs:
failed_jobs=()
[[ "$LINT_RESULT" == "success" ]] || failed_jobs+=("lint: $LINT_RESULT")
[[ "$BUILD_RESULT" == "success" ]] || failed_jobs+=("build: $BUILD_RESULT")
[[ "$BUILD_LOGIC_RESULT" == "success" ]] || failed_jobs+=("build logic: $BUILD_LOGIC_RESULT")
[[ "$TEST_RESULT" == "success" ]] || failed_jobs+=("test: $TEST_RESULT")
[[ "$JACKSON_COMPATIBILITY_RESULT" == "success" ]] ||
failed_jobs+=("Jackson compatibility: $JACKSON_COMPATIBILITY_RESULT")
Expand Down
19 changes: 11 additions & 8 deletions .github/workflows/create-releases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,15 @@ jobs:
environment: publish

steps:
- name: Check out workflow scripts
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
persist-credentials: false
ref: ${{ github.workflow_sha }}

- name: Preserve GraalVM test script for older release retries
run: install -m 700 .github/scripts/run-graalvm-tests.sh "$RUNNER_TEMP/run-graalvm-tests.sh"

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
persist-credentials: false
Expand Down Expand Up @@ -296,7 +305,7 @@ jobs:
cache-disabled: true

- name: Compile the openai-java-core project
run: ./gradlew :openai-java-core:compileJava :openai-java-core:compileTestJava -x test
run: ./gradlew :openai-java-core:classes :openai-java-core:testClasses -x test

- name: Stop pre-GraalVM Gradle daemon
run: ./gradlew --stop
Expand All @@ -311,13 +320,7 @@ jobs:
distribution: graalvm-community

- name: Run GraalVM native-image agent tests
run: >-
./gradlew :openai-java-core:test
-x compileJava
-x compileTestJava
-x compileKotlin
-x compileTestKotlin
-PgraalvmAgent
run: bash "$RUNNER_TEMP/run-graalvm-tests.sh"

- name: Check generated GraalVM files
run: |
Expand Down
3 changes: 3 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ tasks.test {
inputs
.file(layout.projectDirectory.file("../.github/workflows/create-releases.yml"))
.withPathSensitivity(PathSensitivity.RELATIVE)
inputs
.file(layout.projectDirectory.file("../.github/scripts/run-graalvm-tests.sh"))
.withPathSensitivity(PathSensitivity.RELATIVE)
inputs
.file(layout.projectDirectory.file("../SECURITY.md"))
.withPathSensitivity(PathSensitivity.RELATIVE)
Expand Down
9 changes: 9 additions & 0 deletions buildSrc/src/main/kotlin/openai.kotlin.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import com.openai.gradle.CoreCompilationShards
import com.openai.gradle.VersionSupportPolicy
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id("openai.java")
Expand Down Expand Up @@ -38,6 +39,14 @@ kotlin {
}
}

// Kotlin fingerprints friend paths by name, not by class content. Its default includes the
// versioned main JAR, so a release alone invalidates every test compilation. The compiler already
// fingerprints the test compile classpath; use the stable main classes for internal visibility.
// In core this is the canonical aggregate, including its internal compilation shards.
tasks.named<KotlinCompile>("compileTestKotlin") {
friendPaths.setFrom(sourceSets.main.map { it.output.classesDirs })
}

tasks.withType<Test>().configureEach {
systemProperty("junit.jupiter.execution.parallel.enabled", true)
systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,98 @@ import org.yaml.snakeyaml.constructor.SafeConstructor
class GradleCacheTrustPolicyTest {
@TempDir lateinit var temporaryDirectory: Path

@Test
fun `Graal release test step supports current and older source tasks and stops on query failure`() {
val workflow = Path.of("../.github/workflows/create-releases.yml").readText()
val script =
requireNotNull(
parseWorkflow(workflow)
.job("publish")
.steps
.single { it.name == "Run GraalVM native-image agent tests" }
.run
)
Files.copy(
Comment thread
dpiet-oai marked this conversation as resolved.
Path.of("../.github/scripts/run-graalvm-tests.sh"),
temporaryDirectory.resolve("run-graalvm-tests.sh"),
)
val wrapper = temporaryDirectory.resolve("gradlew")
wrapper.writeText(
"""
|#!/usr/bin/env bash
|set -euo pipefail
|case "${'$'}1" in
| :openai-java-core:tasks)
| printf '%s\n' "${'$'}AVAILABLE_TASKS"
| exit "${'$'}QUERY_STATUS"
| ;;
| :openai-java-core:test)
| printf '%s\n' "${'$'}@" > "${'$'}RUNNER_TEMP/test-arguments"
| ;;
| *) exit 93 ;;
|esac
"""
.trimMargin() + "\n"
)
assertTrue(wrapper.toFile().setExecutable(true))
val testArguments = temporaryDirectory.resolve("test-arguments")
val baseArguments =
listOf(
":openai-java-core:test",
"-x",
"compileJava",
"-x",
"compileTestJava",
"-x",
"compileKotlin",
"-x",
"compileTestKotlin",
)
val shards = listOf("compileBetaModelTestKotlin", "compileAdminModelTestKotlin")
val currentListing = shards.joinToString("\n") { "$it - Compiles the test sources." }
// An old source or unrelated task prefix must not produce an unknown exclusion.
val oldListing =
"compileTestKotlin - Compiles tests.\ncompileBetaModelTestKotlinOther - Unrelated."
for ((listing, status, expectedShards) in
listOf(
Triple(currentListing, "0", shards),
Triple(oldListing, "0", emptyList()),
Triple(currentListing, "19", emptyList()),
)) {
Files.deleteIfExists(testArguments)
val builder =
ProcessBuilder("bash", "-euo", "pipefail", "-c", script)
.directory(temporaryDirectory.toFile())
.redirectErrorStream(true)
builder
.environment()
.putAll(
mapOf(
"RUNNER_TEMP" to temporaryDirectory.toString(),
"AVAILABLE_TASKS" to listing,
"QUERY_STATUS" to status,
)
)
val process = builder.start()
val output = process.inputStream.bufferedReader().use { it.readText() }
assertEquals(status.toInt(), process.waitFor(), output)
if (status == "0") {
assertEquals(
baseArguments +
expectedShards.flatMap { listOf("-x", ":openai-java-core:$it") } +
"-PgraalvmAgent",
testArguments.readText().lines().filter { it.isNotEmpty() },
output,
)
} else {
assertFalse(
Files.exists(testArguments),
"Failed task query must stop before tests.",
)
}
}
}

@Test
fun `all pull request Gradle jobs keep cross-run caches read-only`() {
val workflow = Path.of("../.github/workflows/ci.yml").readText()
Expand Down Expand Up @@ -984,6 +1076,40 @@ class GradleCacheTrustPolicyTest {
)
}

val workflowCheckout =
publishJob.steps.indexOfFirst { it.name == "Check out workflow scripts" }
val preserveHelper =
publishJob.steps.indexOfFirst {
it.name == "Preserve GraalVM test script for older release retries"
}
val releaseCheckout =
publishJob.steps.indexOfFirst {
it.action?.repository == "actions/checkout" &&
it.action.inputs["ref"] == "\${{ needs.release.outputs.source_sha }}"
}
assertTrue(
workflowCheckout >= 0 &&
preserveHelper > workflowCheckout &&
releaseCheckout > preserveHelper,
"Copy the helper from the pinned workflow before checking out the verified release.",
)
assertEquals(
mapOf("persist-credentials" to "false", "ref" to "\${{ github.workflow_sha }}"),
requireNotNull(publishJob.steps[workflowCheckout].action).inputs,
)
assertEquals(
mapOf(
"persist-credentials" to "false",
"ref" to "\${{ needs.release.outputs.source_sha }}",
),
requireNotNull(publishJob.steps[releaseCheckout].action).inputs,
)
assertEquals(
"install -m 700 .github/scripts/run-graalvm-tests.sh " +
"\"\$RUNNER_TEMP/run-graalvm-tests.sh\"",
publishJob.steps[preserveHelper].run,
)

val isolation =
publishJob.steps.indexOfFirst { it.name == "Create isolated release Gradle User Home" }
val initializers =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class MergeQueuePolicyTest {
setOf(
"lint",
"build",
"build_logic",
"test",
"jackson_compatibility",
"api_compatibility",
Expand All @@ -67,6 +68,7 @@ class MergeQueuePolicyTest {
mapOf(
"LINT_RESULT" to "lint",
"BUILD_RESULT" to "build",
"BUILD_LOGIC_RESULT" to "build_logic",
"TEST_RESULT" to "test",
"JACKSON_COMPATIBILITY_RESULT" to "jackson_compatibility",
"API_COMPATIBILITY_RESULT" to "api_compatibility",
Expand Down Expand Up @@ -100,7 +102,20 @@ class MergeQueuePolicyTest {
val workflow = workflow("ci")
val build = job(workflow, "build")
val buildCommand = steps(build).single { it["name"] == "Build SDK" }["run"]
assertEquals("./scripts/build -x :openai-java-core:testJacksonCompatibility", buildCommand)
assertEquals(
"./scripts/gradle build testClasses verifyVersionSupportPolicy " +
"-x test -x :openai-java-core:testJacksonCompatibility",
(buildCommand as String).trim(),
)
val buildLogic = job(workflow, "build_logic")
assertFalse(
buildLogic.containsKey("needs"),
"Build logic must not wait for SDK compilation",
)
assertEquals(
"./scripts/gradle :buildSrc:test",
steps(buildLogic).single { it["name"] == "Test build logic" }["run"],
)

val jackson = job(workflow, "jackson_compatibility")
assertEquals("build", jackson["needs"])
Expand Down
26 changes: 26 additions & 0 deletions openai-java-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,32 @@ mainClassesDirectories.setFrom(combinedClassesDirectory)
mainClassesDirectories.builtBy(assembleCoreClasses)
tasks.named("classes") { dependsOn(assembleCoreClasses) }

// These generated model tests do not use the shared fixtures needed by services and core tests.
// Compile them independently, without moving generated sources or changing test discovery.
val independentModelTests =
listOf(
"betaModelTest" to "com/openai/models/beta/**",
"adminModelTest" to "com/openai/models/admin/**",
)
val coreTestSourceSet = sourceSets.test.get()
val coreTestClassesDirectories = coreTestSourceSet.output.classesDirs as ConfigurableFileCollection
independentModelTests.forEach { (name, pattern) ->
val modelTests = sourceSets.create(name)
// Resolve exactly the dependencies/Java 17 attributes already used to compile core tests.
modelTests.compileClasspath = coreTestSourceSet.compileClasspath
kotlin.sourceSets.named(name) {
kotlin.setSrcDirs(listOf(layout.projectDirectory.dir("src/test/kotlin")))
kotlin.include(pattern)
}
kotlin.sourceSets.named("test") { kotlin.exclude(pattern) }

val compileModelTests = tasks.named<KotlinCompile>(modelTests.getCompileTaskName("kotlin")) {
friendPaths.setFrom(sourceSets.main.map { it.output.classesDirs })
}
coreTestClassesDirectories.from(compileModelTests.flatMap { it.destinationDirectory })
tasks.named("testClasses") { dependsOn(modelTests.classesTaskName) }
}

val coreJar = tasks.named<Jar>("jar") {
// The Kotlin plugin captures compileKotlin's destination before it is relocated above. Publish
// those client-layer classes through the canonical aggregate instead of the staging directory.
Expand Down
Loading