fix(native): bound cross-platform lifecycle ownership
This commit is contained in:
@@ -540,7 +540,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApplication1.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/plezy.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/plezy";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Plezy.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Plezy";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -555,7 +555,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApplication1.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/plezy.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/plezy";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Plezy.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Plezy";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -570,7 +570,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApplication1.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/plezy.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/plezy";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Plezy.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Plezy";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Libmpv
|
||||
import FlutterMacOS
|
||||
import XCTest
|
||||
|
||||
@@ -47,6 +48,14 @@ final class RecordingMpvPlugin: MpvPluginShared {
|
||||
}
|
||||
}
|
||||
|
||||
final class RecordingLifecycleDelegate: MpvPlayerDelegate {
|
||||
private(set) var events: [String] = []
|
||||
private(set) var properties: [String] = []
|
||||
|
||||
func onPropertyChange(name: String, value: Any?) { properties.append(name) }
|
||||
func onEvent(name: String, data: [String: Any]?) { events.append(name) }
|
||||
}
|
||||
|
||||
final class MpvPlayerContractTests: XCTestCase {
|
||||
private let failure = NSError(
|
||||
domain: "MpvPlayerContractTests",
|
||||
@@ -108,6 +117,35 @@ final class MpvPlayerContractTests: XCTestCase {
|
||||
XCTAssertFalse(core.isPaused, "The accepted pause write must commit before completion")
|
||||
}
|
||||
|
||||
func testPauseIntentUpdatesCacheBeforeAsyncWriteCompletes() {
|
||||
let core = MpvAudioPlayerCore()
|
||||
XCTAssertTrue(core.initialize())
|
||||
defer {
|
||||
core.dispose()
|
||||
core.queue.sync {}
|
||||
}
|
||||
|
||||
let queueEntered = expectation(description: "mpv queue blocked")
|
||||
let releaseQueue = DispatchSemaphore(value: 0)
|
||||
core.queue.async {
|
||||
queueEntered.fulfill()
|
||||
releaseQueue.wait()
|
||||
}
|
||||
wait(for: [queueEntered], timeout: 2)
|
||||
|
||||
let completion = expectation(description: "pause write completed")
|
||||
core.setPropertyAsync("pause", value: "no") { result in
|
||||
if case .failure(let error) = result {
|
||||
XCTFail("Pause write failed: \(error)")
|
||||
}
|
||||
completion.fulfill()
|
||||
}
|
||||
|
||||
XCTAssertFalse(core.isPaused, "The public pause intent must be visible before the native write completes")
|
||||
releaseQueue.signal()
|
||||
wait(for: [completion], timeout: 2)
|
||||
}
|
||||
|
||||
func testPendingSetPropertyIsCancelledExactlyOnceOnDispose() {
|
||||
let core = MpvAudioPlayerCore()
|
||||
XCTAssertTrue(core.initialize())
|
||||
@@ -152,6 +190,94 @@ final class MpvPlayerContractTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
func testQueuedDelegateDeliveryIsDroppedAfterTerminalTransition() {
|
||||
let core = MpvPlayerCoreBase()
|
||||
let delegate = RecordingLifecycleDelegate()
|
||||
core.delegate = delegate
|
||||
core.dispatchDelegateEvent(name: "file-loaded", data: nil)
|
||||
core.dispatchDelegateProperty(name: "time-pos", value: 1.0)
|
||||
XCTAssertTrue(core.beginDisposal())
|
||||
|
||||
let drained = expectation(description: "main delivery drained")
|
||||
DispatchQueue.main.async { drained.fulfill() }
|
||||
wait(for: [drained], timeout: 2)
|
||||
XCTAssertTrue(delegate.events.isEmpty)
|
||||
XCTAssertTrue(delegate.properties.isEmpty)
|
||||
}
|
||||
|
||||
func testUnavailablePropertyCompletionRunsExactlyOnceOnMainThread() {
|
||||
let core = MpvAudioPlayerCore()
|
||||
XCTAssertTrue(core.initialize())
|
||||
core.dispose()
|
||||
core.queue.sync {}
|
||||
|
||||
let completed = expectation(description: "unavailable property completed")
|
||||
completed.assertForOverFulfill = true
|
||||
var completionCount = 0
|
||||
DispatchQueue.global().async {
|
||||
core.getPropertyAsync("volume") { result in
|
||||
XCTAssertTrue(Thread.isMainThread)
|
||||
if case .success = result { XCTFail("Expected unavailable property failure") }
|
||||
completionCount += 1
|
||||
completed.fulfill()
|
||||
}
|
||||
}
|
||||
wait(for: [completed], timeout: 2)
|
||||
XCTAssertEqual(completionCount, 1)
|
||||
}
|
||||
|
||||
func testNormalizedPlaybackDelayStringsPassThroughUnchanged() {
|
||||
let core = ControllablePropertyCore()
|
||||
let plugin = RecordingMpvPlugin(core: core)
|
||||
let values = ["0.25", "-0.5", "0", "0.25"]
|
||||
|
||||
for value in values {
|
||||
core.nextResult = .success(())
|
||||
let result = invokeSetProperty(plugin, name: "audio-delay", value: value)
|
||||
XCTAssertEqual(result.count, 1)
|
||||
XCTAssertNil(result[0])
|
||||
}
|
||||
XCTAssertEqual(core.propertyCalls.map(\.1), values)
|
||||
}
|
||||
|
||||
func testNodeConversionBoundsAndDiscardsMalformedSiblings() {
|
||||
let core = MpvPlayerCoreBase()
|
||||
var valid = mpv_node()
|
||||
valid.format = MPV_FORMAT_INT64
|
||||
valid.u.int64 = 7
|
||||
var malformed = mpv_node()
|
||||
malformed.format = MPV_FORMAT_NONE
|
||||
var values = [valid, malformed, valid]
|
||||
var decoded: Any?
|
||||
|
||||
let valueCount = values.count
|
||||
values.withUnsafeMutableBufferPointer { valuesPointer in
|
||||
var list = mpv_node_list()
|
||||
list.num = Int32(valueCount)
|
||||
list.values = valuesPointer.baseAddress
|
||||
withUnsafeMutablePointer(to: &list) { listPointer in
|
||||
var root = mpv_node()
|
||||
root.format = MPV_FORMAT_NODE_ARRAY
|
||||
root.u.list = listPointer
|
||||
decoded = core.convertNode(root)
|
||||
}
|
||||
}
|
||||
XCTAssertEqual(decoded as? [Int64], [7, 7])
|
||||
|
||||
var oversizedBytes = mpv_byte_array()
|
||||
oversizedBytes.size = 16 * 1_024 * 1_024 + 1
|
||||
withUnsafeMutablePointer(to: &oversizedBytes) { bytePointer in
|
||||
var root = mpv_node()
|
||||
root.format = MPV_FORMAT_BYTE_ARRAY
|
||||
root.u.ba = bytePointer
|
||||
XCTAssertNil(core.convertNode(root))
|
||||
}
|
||||
XCTAssertTrue(core.validateSideDataDimensions(width: 3_840, height: 2_160))
|
||||
XCTAssertFalse(core.validateSideDataDimensions(width: 0, height: 2_160))
|
||||
XCTAssertFalse(core.validateSideDataDimensions(width: 65_536, height: 2_160))
|
||||
XCTAssertFalse(core.validateSideDataDimensions(width: 16_384, height: 16_384))
|
||||
}
|
||||
|
||||
private func invokeSetProperty(
|
||||
_ plugin: RecordingMpvPlugin,
|
||||
name: String,
|
||||
|
||||
Reference in New Issue
Block a user