@@ -108,12 +108,12 @@ runs {
108108 }
109109
110110 [' Fresh' , ' Reload' ]. each { String phase ->
111- register(" biomeIntegration ${ phase} " ) {
111+ register(" surfaceIntegration ${ phase} " ) {
112112 runType ' gameTestServer'
113- workingDirectory layout. buildDirectory. dir(' biome -integration-run' )
114- systemProperty ' neoforge.enabledGameTestNamespaces' , ' cakeworldprobe '
113+ workingDirectory layout. buildDirectory. dir(' surface -integration-run' )
114+ systemProperty ' neoforge.enabledGameTestNamespaces' , ' surfaceprobe '
115115 systemProperty ' forge.logging.console.level' , ' info'
116- systemProperty ' cakeworld.biomeIntegrationPhase ' , phase. toLowerCase(Locale . ROOT )
116+ systemProperty ' surfaceprobe.integrationPhase ' , phase. toLowerCase(Locale . ROOT )
117117 modSource project. sourceSets. main
118118 }
119119 }
@@ -201,79 +201,249 @@ tasks.withType(JavaCompile).configureEach {
201201tasks. named(' javadoc' , Javadoc ). configure {
202202 options. encoding = ' UTF-8'
203203 options. addStringOption(' Xdoclint:none' , ' -quiet' )
204+ options. addBooleanOption(' -no-fonts' , true )
204205}
205206
206207tasks. named(' test' , Test ). configure {
207208 useJUnitPlatform()
209+ // Unit tests inspect target files relative to the checkout, but they do
210+ // not need NeoForge's rolling runtime files. A console-only logger keeps
211+ // them from contending with Eclipse/client logs in this working directory.
212+ systemProperty ' log4j.configurationFile' , file(' src/test/resources/log4j2-test.xml' ). absolutePath
213+ // Loaded only through an isolated URLClassLoader by the parity test. This
214+ // is deliberately not a Gradle dependency and cannot leak into Eclipse or
215+ // a published OreSpawn jar.
216+ File mineralogy5Oracle = file(' ../../MinecraftMineralogy 118/MinecraftMineralogy/build/libs/Mineralogy-1.18.2-5.4.0.jar' )
217+ if (mineralogy5Oracle. isFile()) {
218+ systemProperty ' orespawn.mineralogy5Oracle' , mineralogy5Oracle. absolutePath
219+ }
220+ }
221+
222+ // Several registry-focused tests initialize the real global config singleton.
223+ // Keep that target-native coverage without creating or changing a developer's
224+ // checkout config as a side effect of `test` or `build`.
225+ def unitTestWorldgenConfig = file(' config/orespawn-worldgen.json' )
226+ def unitTestWorldgenConfigWasPresent = false
227+ byte [] unitTestWorldgenConfigBytes = null
228+ tasks. named(' test' , Test ). configure {
229+ doFirst {
230+ unitTestWorldgenConfigWasPresent = unitTestWorldgenConfig. isFile()
231+ unitTestWorldgenConfigBytes = unitTestWorldgenConfigWasPresent
232+ ? unitTestWorldgenConfig. bytes : null
233+ }
234+ }
235+ def preserveDeveloperWorldgenConfig = tasks. register(' preserveDeveloperWorldgenConfig' ) {
236+ doLast {
237+ if (unitTestWorldgenConfigWasPresent) {
238+ byte [] after = unitTestWorldgenConfig. isFile() ? unitTestWorldgenConfig. bytes : null
239+ if (after == null || ! java.util.Arrays . equals(unitTestWorldgenConfigBytes, after)) {
240+ unitTestWorldgenConfig. parentFile. mkdirs()
241+ unitTestWorldgenConfig. bytes = unitTestWorldgenConfigBytes
242+ throw new GradleException (' Unit tests changed config/orespawn-worldgen.json; the original was restored' )
243+ }
244+ } else if (unitTestWorldgenConfig. isFile()) {
245+ delete unitTestWorldgenConfig
246+ }
247+ }
248+ }
249+ tasks. named(' test' ) {
250+ finalizedBy preserveDeveloperWorldgenConfig
251+ }
252+
253+ // A NeoForge process is not green merely because it returns exit code zero.
254+ // The loader can log a worldgen/linkage failure and still shut down normally.
255+ // No ERROR/FATAL line has been accepted as harmless on NeoForge 26.2; add an
256+ // exception only after reproducing it in the matching loader control.
257+ def acceptedNeoForge262LogNoise = []
258+
259+ def runtimeCrashSnapshot = { File runDirectory ->
260+ File crashDirectory = new File (runDirectory, ' crash-reports' )
261+ if (! crashDirectory. isDirectory()) return [] as Set
262+ return fileTree(crashDirectory) { include ' **/*' }. files
263+ .findAll { it. isFile() }. collect { it. absolutePath } as Set
264+ }
265+
266+ def assertRuntimeLogsClean = { File runDirectory , String context , Set priorCrashes ->
267+ File crashDirectory = new File (runDirectory, ' crash-reports' )
268+ if (crashDirectory. isDirectory()) {
269+ def crashes = fileTree(crashDirectory) { include ' **/*' }. files
270+ .findAll { it. isFile() && ! priorCrashes. contains(it. absolutePath) }
271+ if (! crashes. isEmpty()) {
272+ throw new GradleException (" ${ context} produced crash report ${ crashes.first()} " )
273+ }
274+ }
275+
276+ File logsDirectory = new File (runDirectory, ' logs' )
277+ if (! logsDirectory. isDirectory()) return
278+ def failures = []
279+ [new File (logsDirectory, ' latest.log' ), new File (logsDirectory, ' debug.log' )]
280+ .findAll { it. isFile() }. each { File log ->
281+ int lineNumber = 0
282+ log. eachLine(' UTF-8' ) { String line ->
283+ lineNumber++
284+ boolean unexpectedSeverity = line ==~ / .*\/ (?:ERROR|FATAL)\] .*/
285+ boolean knownNoise = acceptedNeoForge262LogNoise. any { line =~ it }
286+ boolean fatalText = line. contains(' Encountered an unexpected exception' ) ||
287+ line. contains(' Exception stopping the server' ) ||
288+ line. contains(' Migration audit failed' ) ||
289+ line. contains(' java.lang.Error:' ) ||
290+ line. contains(' NoSuchMethodError' ) ||
291+ line. contains(' NoClassDefFoundError' ) ||
292+ line. contains(' ExceptionInInitializerError' ) ||
293+ line. contains(' Tried to assign a mutable BlockPos' ) ||
294+ line. contains(' causing cascading worldgen lag' )
295+ if ((unexpectedSeverity && ! knownNoise) || fatalText) {
296+ failures. add(" ${ log.name} :${ lineNumber} : ${ line} " )
297+ }
298+ }
299+ }
300+ if (! failures. isEmpty()) {
301+ throw new GradleException (" ${ context} logged unexpected errors:\n "
302+ + failures. take(20 ). join(' \n ' ))
303+ }
304+ }
305+
306+ tasks. register(' runtimeLogScannerTest' ) {
307+ group = ' verification'
308+ description = ' Proves runtime log validation rejects real NeoForge failures.'
309+ doLast {
310+ File probe = file(" ${ buildDir} /runtime-log-scanner-test" )
311+ delete probe
312+ File logs = new File (probe, ' logs' ); logs. mkdirs()
313+ new File (logs, ' latest.log' ). setText(
314+ ' [Server thread/INFO] [neoforge]: Done\n ' , ' UTF-8' )
315+ assertRuntimeLogsClean(probe, ' scanner-clean-log-probe' , [] as Set )
316+ new File (logs, ' latest.log' ). setText(
317+ ' [Server thread/WARN]: Tried to assign a mutable BlockPos to tick data...\n ' , ' UTF-8' )
318+ boolean rejected = false
319+ try { assertRuntimeLogsClean(probe, ' scanner-mutable-position-probe' , [] as Set ) }
320+ catch (GradleException expected) { rejected = true }
321+ if (! rejected) throw new GradleException (' Runtime log scanner accepted a mutable BlockPos leak' )
322+ new File (logs, ' latest.log' ). setText(
323+ ' [Server thread/DEBUG] [neoforge]: Minecraft loaded a new chunk while populating another, causing cascading worldgen lag.\n ' , ' UTF-8' )
324+ rejected = false
325+ try { assertRuntimeLogsClean(probe, ' scanner-cascading-probe' , [] as Set ) }
326+ catch (GradleException expected) { rejected = true }
327+ if (! rejected) throw new GradleException (' Runtime log scanner accepted cascading worldgen' )
328+ new File (logs, ' latest.log' ). setText(
329+ ' [Server thread/ERROR] [example]: Unexpected fixture failure\n ' , ' UTF-8' )
330+ rejected = false
331+ try { assertRuntimeLogsClean(probe, ' scanner-severity-probe' , [] as Set ) }
332+ catch (GradleException expected) { rejected = true }
333+ if (! rejected) throw new GradleException (' Runtime log scanner accepted an unexpected ERROR line' )
334+ delete probe
335+ }
336+ }
337+
338+ tasks. named(' check' ) {
339+ dependsOn ' runtimeLogScannerTest'
340+ }
341+
342+ tasks. register(' verifyMineralogyOracleIsolation' ) {
343+ group = ' verification'
344+ description = ' Prevents published Mineralogy engines from leaking into Gradle configurations or ordinary Eclipse launches.'
345+ doLast {
346+ configurations. each { configuration ->
347+ if (configuration. canBeResolved &&
348+ configuration. files. any { it. name ==~ / Mineralogy-.*\. jar/ }) {
349+ throw new GradleException (" Mineralogy oracle leaked into Gradle configuration ${ configuration.name} " )
350+ }
351+ }
352+ }
353+ }
354+
355+ tasks. named(' check' ) {
356+ dependsOn ' verifyMineralogyOracleIsolation'
357+ }
358+
359+ [' runClient' , ' runServer' , ' runGameTestServer' , ' runClientData' ]. each { String taskName ->
360+ tasks. matching { it. name == taskName }. configureEach { JavaExec runTask ->
361+ doFirst {
362+ new File (runTask. workingDir, ' mods' ). mkdirs()
363+ runTask. ext. oreSpawnCrashSnapshot = runtimeCrashSnapshot(runTask. workingDir)
364+ }
365+ doLast {
366+ assertRuntimeLogsClean(runTask. workingDir, taskName,
367+ runTask. ext. oreSpawnCrashSnapshot as Set )
368+ }
369+ }
208370}
209371
210- def biomeIntegrationClasses = layout. buildDirectory. dir(' biome -integration-fixture/classes' )
211- def compileBiomeIntegrationTestMod = tasks. register(' compileBiomeIntegrationTestMod ' , JavaCompile ) {
372+ def surfaceIntegrationClasses = layout. buildDirectory. dir(' surface -integration-fixture/classes' )
373+ def compileSurfaceIntegrationTestMod = tasks. register(' compileSurfaceIntegrationTestMod ' , JavaCompile ) {
212374 dependsOn tasks. named(' classes' )
213375 source fileTree(' src/biomeIntegrationTest/java' )
214376 classpath = files(sourceSets. main. output, sourceSets. main. compileClasspath)
215- destinationDirectory. set(biomeIntegrationClasses )
377+ destinationDirectory. set(surfaceIntegrationClasses )
216378 javaCompiler. set(javaToolchains. compilerFor {
217379 languageVersion = JavaLanguageVersion . of(25 )
218380 })
219- options. release = 16
381+ options. release = 25
220382 options. encoding = ' UTF-8'
221383}
222384
223- def biomeIntegrationTestModJar = tasks. register(' biomeIntegrationTestModJar ' , Jar ) {
224- dependsOn compileBiomeIntegrationTestMod
225- archiveFileName = ' cakeworldprobe .jar'
226- destinationDirectory = layout. buildDirectory. dir(' biome -integration-fixture' )
385+ def surfaceIntegrationTestModJar = tasks. register(' surfaceIntegrationTestModJar ' , Jar ) {
386+ dependsOn compileSurfaceIntegrationTestMod
387+ archiveFileName = ' surfaceprobe .jar'
388+ destinationDirectory = layout. buildDirectory. dir(' surface -integration-fixture' )
227389 manifest {
228- attributes ' MixinConfigs' : ' cakeworldprobe .mixins.json'
390+ attributes ' MixinConfigs' : ' surfaceprobe .mixins.json'
229391 }
230- from biomeIntegrationClasses
392+ from surfaceIntegrationClasses
231393 from ' src/biomeIntegrationTest/resources'
232394}
233395
234- def biomeIntegrationRunDirectory = layout. buildDirectory. dir(' biome -integration-run' )
235- def prepareBiomeIntegrationTest = tasks. register(' prepareBiomeIntegrationTest ' ) {
236- dependsOn biomeIntegrationTestModJar
396+ def surfaceIntegrationRunDirectory = layout. buildDirectory. dir(' surface -integration-run' )
397+ def prepareSurfaceIntegrationTest = tasks. register(' prepareSurfaceIntegrationTest ' ) {
398+ dependsOn surfaceIntegrationTestModJar
237399 doLast {
238- delete biomeIntegrationRunDirectory
400+ delete surfaceIntegrationRunDirectory
239401 copy {
240- from biomeIntegrationTestModJar . flatMap { it. archiveFile }
241- into biomeIntegrationRunDirectory . map { it. dir(' mods' ) }
402+ from surfaceIntegrationTestModJar . flatMap { it. archiveFile }
403+ into surfaceIntegrationRunDirectory . map { it. dir(' mods' ) }
242404 }
243405 }
244406}
245407
246408tasks. configureEach {
247- if (name == ' runBiomeIntegrationFresh' ) {
248- dependsOn prepareBiomeIntegrationTest
249- } else if (name == ' runBiomeIntegrationReload' ) {
250- dependsOn ' runBiomeIntegrationFresh'
409+ if (name == ' runSurfaceIntegrationFresh' ) {
410+ dependsOn prepareSurfaceIntegrationTest
411+ } else if (name == ' runSurfaceIntegrationReload' ) {
412+ dependsOn ' runSurfaceIntegrationFresh'
413+ }
414+ if (name == ' runSurfaceIntegrationFresh' || name == ' runSurfaceIntegrationReload' ) {
415+ doFirst {
416+ ext. oreSpawnCrashSnapshot = runtimeCrashSnapshot(workingDir)
417+ }
418+ doLast {
419+ assertRuntimeLogsClean(workingDir, " NeoForge 26.2 ${ name} " ,
420+ ext. oreSpawnCrashSnapshot as Set )
421+ }
251422 }
252423}
253424
254- def biomeIntegrationTest = tasks. register(' biomeIntegrationTest ' ) {
425+ def surfaceIntegrationTest = tasks. register(' surfaceIntegrationTest ' ) {
255426 group = ' verification'
256- description = ' Verifies a provider-owned custom biome in fresh and reloaded normal terrain.'
257- dependsOn ' runBiomeIntegrationReload '
427+ description = ' Verifies provider surfaces and dynamic- biome geology across fresh and reloaded normal terrain.'
428+ dependsOn ' runSurfaceIntegrationReload '
258429 doLast {
259- File marker = biomeIntegrationRunDirectory . get(). file(
260- ' gametestserver/gametestworld/cakeworld-biome -integration.properties' ). asFile
430+ File marker = surfaceIntegrationRunDirectory . get(). file(
431+ ' gametestserver/gametestworld/surfaceprobe -integration.properties' ). asFile
261432 if (! marker. isFile()) {
262- throw new GradleException (" Biome integration completion marker is missing: ${ marker} " )
433+ throw new GradleException (" Surface integration completion marker is missing: ${ marker} " )
263434 }
264435 Properties result = new Properties ()
265436 marker. withInputStream { result. load(it) }
266437 if (result. getProperty(' reload_verified' ) != ' true' ) {
267- throw new GradleException (" Biome integration reload was not verified: ${ marker} " )
438+ throw new GradleException (" Surface integration reload was not verified: ${ marker} " )
268439 }
269- logger. lifecycle(' Custom-biome integration verified: {} chunks, {} top blocks, {} filler blocks, fresh + reload' ,
270- result. getProperty(' matching_chunks' ), result. getProperty(' pink_surface' ),
271- result. getProperty(' white_filler' ))
440+ logger. lifecycle(' Provider surfaces and dynamic-biome geology verified: {} dimensions, {} columns each, fresh + reload' ,
441+ result. getProperty(' dimensions' ), result. getProperty(' columns_per_dimension' ))
272442 }
273443}
274444
275445tasks. named(' check' ) {
276- dependsOn biomeIntegrationTest
446+ dependsOn surfaceIntegrationTest
277447}
278448
279449idea {
0 commit comments