Zum Inhalt

apple/PhysarumEngine/Tests/PhysarumEngineTests/RestoreReleaseRegressionTests.swift

import XCTest
import Metal
@testable import PhysarumEngine

/// Regression guard for the `SimEngine.restore()` release-mode miscompile (2026-06-13):
/// calling the ObjC method `MTLBuffer.contents()` inside a `withUnsafeBytes` noescape closure
/// within a `throws` function corrupted the error register under -O, so `restore` appeared to
/// throw a garbage error and callers crashed (EXC_BAD_ACCESS, masked by ASan/Debug).
/// NOTE: only reproduces in RELEASE (`swift test -c release`); Debug optimizes differently.
final class RestoreReleaseRegressionTests: XCTestCase {
    func testRestoreDoesNotCorruptThrows() throws {
        guard let device = MTLCreateSystemDefaultDevice() else { throw XCTSkip("no gpu") }
        let lib = ShaderLibrary(device: device)
        let preset = try TestFixtures.minimalPreset().params
        let src = try SimEngine(device: device, library: lib, width: 256, height: 256, preset: preset)
        for _ in 0..<10 { src.step() }
        let state = src.snapshot()
        let dst = try SimEngine(device: device, library: lib, width: 256, height: 256, preset: preset)
        try dst.restore(state)               // crashed pre-fix: garbage error on the throws return
        XCTAssertFalse(dst.pheromoneStats().hasNaN)
    }

    func testRestoreThenRenderOffscreen() throws {
        guard let device = MTLCreateSystemDefaultDevice() else { throw XCTSkip("no gpu") }
        let lib = ShaderLibrary(device: device)
        let preset = try TestFixtures.minimalPreset().params
        let src = try SimEngine(device: device, library: lib, width: 256, height: 256, preset: preset)
        for _ in 0..<10 { src.step() }
        let state = src.snapshot()
        let r = try Renderer(device: device, library: lib, width: 256, height: 256)
        let dst = try SimEngine(device: device, library: lib, width: 256, height: 256, preset: preset)
        try dst.restore(state)
        r.resetFeedback()
        let d = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgra8Unorm, width: 512, height: 512, mipmapped: false)
        d.usage = [.renderTarget, .shaderRead]; d.storageMode = .shared
        let target = try XCTUnwrap(device.makeTexture(descriptor: d))
        try r.renderOffscreen(sim: dst, preset: preset, into: target, cover: true)
        XCTAssertNotNil(r.cgImage(target))
    }
}