Add tests for the Swift API
Covers the conversions between the Swift and Ffi types, the model list reported by Rust, chunk counts at other sample rates, and the errors from separating with no model loaded or mismatched channels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seto committed
Sep 26, 2026 at 14:42 UTC
090f4e846faaecff1b0ac26e565e149bd5e465cf
2 files changed
+55
Package.swift
+5
@@ -58,6 +58,11 @@ let package = Package(
58
.linkedFramework("Security"),
59
]
60
),
61
+ .testTarget(
62
+ name: "DemucsTests",
63
+ dependencies: ["Demucs"],
64
+ path: "Tests/DemucsTests"
65
+ ),
66
],
67
// UniFFI 0.31's generated bindings predate Swift 6's region isolation
68
// checks. The hand-written Demucs API is concurrency-safe; the generated
Tests/DemucsTests/DemucsPublicTypesTests.swift
new
+50
@@ -0,0 +1,50 @@
1
+import Testing
2
+@testable import Demucs
3
+
4
+@Test func modelsRoundTripThroughTheBridge() {
5
+ let models: [DemucsModel] = [.fourStem, .sixStem, .fineTunedAll, .fineTuned(stems: [.vocals])]
6
+ for model in models {
7
+ #expect(model.ffi.swift == model)
8
+ }
9
+}
10
+
11
+@Test func stemKindsRoundTripThroughTheBridge() {
12
+ for kind in StemKind.allCases {
13
+ #expect(kind.ffi.swift == kind)
14
+ }
15
+}
16
+
17
+@Test func bridgeErrorsMapToPublicErrors() {
18
+ #expect(DemucsError(.Cancelled) == .cancelled)
19
+ #expect(DemucsError(.NotLoaded) == .notLoaded)
20
+ #expect(DemucsError(.NotCached(modelId: "htdemucs")) == .notDownloaded(modelID: "htdemucs"))
21
+ #expect(DemucsError(.InvalidInput(reason: "empty")) == .invalidInput("empty"))
22
+}
23
+
24
+@Test func modelCatalogMatchesTheRustSide() {
25
+ let models = DemucsEngine.models
26
+ #expect(models.map(\.id) == ["htdemucs", "htdemucs_6s", "htdemucs_ft"])
27
+ #expect(DemucsEngine.info(for: .sixStem).stems.contains(.guitar))
28
+ #expect(DemucsEngine.info(for: .fineTunedAll).sizeMB > DemucsEngine.info(for: .fourStem).sizeMB)
29
+}
30
+
31
+@Test func chunkCountAccountsForSampleRate() {
32
+ let oneChunk = 343_980
33
+ #expect(DemucsEngine.chunkCount(sampleCount: oneChunk, sampleRate: 44_100) == 1)
34
+ #expect(DemucsEngine.chunkCount(sampleCount: oneChunk, sampleRate: 22_050) > 1)
35
+}
36
+
37
+@Test func separatingWithoutAModelFails() async {
38
+ let engine = DemucsEngine()
39
+ #expect(!engine.isLoaded)
40
+ await #expect(throws: DemucsError.notLoaded) {
41
+ try await engine.separate(mono: [0, 0, 0, 0], sampleRate: 44_100)
42
+ }
43
+}
44
+
45
+@Test func mismatchedChannelsAreRejected() async {
46
+ let engine = DemucsEngine()
47
+ await #expect(throws: DemucsError.self) {
48
+ try await engine.separate(left: [0, 0], right: [0], sampleRate: 44_100)
49
+ }
50
+}