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
68 changes: 68 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: CI

# Every pull request runs the unit tests, the builder checks and the server game tests
# (`./gradlew check`), then the client GUI smoke test with and without JEI.
on:
pull_request:
push:
branches: [master-2.0-1.21.1-neo]

jobs:
check:
name: Unit tests, checks and game tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v6

- name: Check
run: ./gradlew check --stacktrace

- name: Upload test reports and the game test log
if: always()
uses: actions/upload-artifact@v7
with:
name: check-reports
path: |
build/reports/tests/
build/gametest/logs/
if-no-files-found: ignore

gui-smoke:
name: Client GUI smoke test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v6

# No display: Xvfb and Mesa's software OpenGL render the client off screen.
- name: Install Xvfb and Mesa
run: |
sudo apt-get update
sudo apt-get install -y xvfb libgl1-mesa-dri

- name: GUI smoke test with JEI
timeout-minutes: 20
run: xvfb-run -a ./gradlew runClient -I scripts/gui-smoke.init.gradle

- name: GUI smoke test without JEI
timeout-minutes: 20
run: xvfb-run -a ./gradlew runClient -PwithoutJei -I scripts/gui-smoke.init.gradle

- name: Upload the client logs
if: always()
uses: actions/upload-artifact@v7
with:
name: gui-smoke-logs
path: |
build/gui-smoke/logs/
build/gui-smoke-without-jei/logs/
if-no-files-found: ignore
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ tasks.configureEach { task ->
|| task.name.startsWith('test')
|| task.name in ['compileTestJava', 'compileBuilderTestJava', 'compileRuntimeTestJava',
'writeMinecraftClasspathJunit', 'writeMinecraftClasspathClient', 'writeMinecraftClasspathServer',
'runClient', 'runServer']) {
'runClient', 'runServer', 'runGameTestServer']) {
task.mustRunAfter(refreshDataResources)
}
}
Expand Down
19 changes: 8 additions & 11 deletions docs/vanilla-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,21 +141,18 @@ checks every planned item/block has its packaged resources, verifies no unwanted
vanilla tool/armor duplicates, and round-trips the runtime references through
JSON/NBT and MaterialBuilder.

An opt-in, self-stopping server smoke test exercises real registration, block
placement, ammunition stack recovery after entity save/load, fluid placement and
bucket recovery, actual mining tags, and packaged armor texture references:
Server game tests exercise real registration, block placement, ammunition stack
recovery after entity save/load, fluid placement and bucket recovery, actual mining
tags, and packaged armor texture references. They are part of `./gradlew check`, or
run them alone:

```sh
./gradlew runServer --offline --stacktrace -I scripts/runtime-smoke.init.gradle
./gradlew build check --offline --stacktrace
./gradlew runGameTestServer
```

The smoke run uses `build/runtime-smoke/server`, loopback networking, and a random
port. It stops itself and the Gradle task fails if the success marker is absent.
Its entrypoint lives in `src/runtimeTest/java`, outside release sources; `./gradlew check`
compiles it to prevent API drift. The second command restores regular
main class output after the opt-in test run. The release JAR must contain no
`zone/moddev/mc/lib/smoke` classes.
Each check is its own game test in `MMDLibGameTests` (`src/runtimeTest/java`, outside
release sources), and the task fails naming every test that failed. The release JAR
must contain no `zone/moddev/mc/lib/smoke` classes.

Verified on a dedicated development server: all 50 block items placed, all twelve
arrow/bolt types preserved the correct pickup item and components across entity
Expand Down
32 changes: 29 additions & 3 deletions gradle/verification.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,32 @@ sourceSets {
}
tasks.named('check') { dependsOn tasks.named('compileRuntimeTestJava') }

// Server game tests in src/runtimeTest (MMDLibGameTests): `./gradlew runGameTestServer`, part of
// `check`. The run loads main and runtimeTest as one mod, in a fresh world each time, and exits
// with the number of failed tests. Release jars never contain runtimeTest (verifyReleaseClasses).
def gameTestDir = layout.buildDirectory.dir('gametest')
runs {
gameTestServer {
workingDirectory gameTestDir
modSource project.sourceSets.runtimeTest
systemProperty 'neoforge.enabledGameTestNamespaces', 'mmdlib'
}
}
// NeoGradle creates the run task late, so it is found by name.
def gameTestTask = tasks.matching { it.name == 'runGameTestServer' }
gameTestTask.configureEach { it.doFirst { project.delete(gameTestDir); gameTestDir.get().asFile.mkdirs() } }
tasks.named('check') { dependsOn gameTestTask }

// Unit tests in src/test: JUnit 5, no running game. `./gradlew test` (part of `check`).
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.12.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test', Test) {
useJUnitPlatform()
testLogging { events 'failed'; exceptionFormat 'full' }
}

// Standalone checks in src/builderTest, run as plain Java programs: task name -> main class
// (in zone.moddev.mc.lib.common.builders), plus any system properties the check reads.
def resources = file('src/main/resources').absolutePath
Expand All @@ -32,7 +58,6 @@ def builderChecks = [
verifyMethodEvents : ['MethodEventsTest'],
verifyDefinitionLoader : ['DefinitionLoaderTest'],
verifyScreens : ['zone.moddev.mc.lib.gui.ScreenLoaderTest'],
verifyMachineCore : ['zone.moddev.mc.lib.machine.MachineCoreTest'],
verifyVanillaExamples : ['VanillaExamplesTest', ['mmdlib.resources': resources,
'mmdlib.generated': file('src/generated/resources').absolutePath]],
]
Expand All @@ -52,8 +77,9 @@ def verifyReleaseClasses = tasks.register('verifyReleaseClasses') {
dependsOn tasks.named('jar')
doLast {
new java.util.zip.ZipFile(tasks.named('jar').get().archiveFile.get().asFile).withCloseable { archive ->
def leaked = archive.entries().findAll { entry -> entry.name.startsWith('zone/moddev/mc/lib/smoke/') }
if (!leaked.isEmpty()) throw new GradleException('Runtime smoke-test classes must not be packaged: ' + leaked)
def testOnly = ['zone/moddev/mc/lib/smoke/', 'data/mmdlib_smoke/', 'data/mmdlib/structure/']
def leaked = archive.entries().findAll { entry -> testOnly.any { entry.name.startsWith(it) } }
if (!leaked.isEmpty()) throw new GradleException('Test classes and data must not be packaged: ' + leaked.collect { it.name })
}
}
}
Expand Down
36 changes: 0 additions & 36 deletions scripts/runtime-smoke.init.gradle

This file was deleted.

Loading
Loading