| import ASRKit |
| import CryptoKit |
| import Foundation |
| import SpeechCore |
|
|
| enum CheckError: Error, CustomStringConvertible { |
| case failed(String) |
|
|
| var description: String { |
| switch self { |
| case .failed(let message): return message |
| } |
| } |
| } |
|
|
| func check(_ condition: @autoclosure () throws -> Bool, _ message: String) throws { |
| if try !condition() { |
| throw CheckError.failed(message) |
| } |
| } |
|
|
| func makeBundle(schema: Int, corrupt: Bool = false) throws -> URL { |
| let dir = FileManager.default.temporaryDirectory |
| .appendingPathComponent("spktest-\(UUID().uuidString)") |
| try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) |
| let payload = Data("hello speechkit".utf8) |
| try payload.write(to: dir.appendingPathComponent("a.bin")) |
| var sha = SHA256.hash(data: payload).map { String(format: "%02x", $0) }.joined() |
| if corrupt { sha = String(sha.reversed()) } |
| let manifest: [String: Any] = [ |
| "schemaVersion": schema, |
| "bundleVersion": "0.0.1", |
| "assets": ["a": ["file": "a.bin", "sha256": sha, "sizeBytes": payload.count]], |
| ] |
| let data = try JSONSerialization.data(withJSONObject: manifest) |
| try data.write(to: dir.appendingPathComponent("manifest.json")) |
| return dir |
| } |
|
|
| func expectSpeechError(_ label: String, _ body: () throws -> Void) throws { |
| do { |
| try body() |
| throw CheckError.failed("\(label): expected SpeechError") |
| } catch is SpeechError { |
| return |
| } |
| } |
|
|
| do { |
| let validURL = try makeBundle(schema: 2) |
| let bundle = try AssetBundle(at: validURL, verifyHashes: true) |
| try check(try bundle.url("a").lastPathComponent == "a.bin", "asset URL should resolve") |
| try expectSpeechError("wrong schema") { _ = try AssetBundle(at: makeBundle(schema: 99)) } |
| try expectSpeechError("corrupted asset") { |
| _ = try AssetBundle(at: makeBundle(schema: 2, corrupt: true), verifyHashes: true) |
| } |
| try expectSpeechError("missing asset") { _ = try bundle.url("nonexistent") } |
|
|
| try check(MelExtractor.bucket(forFrames: 100) == 500, "100 frames should use 500 bucket") |
| try check(MelExtractor.bucket(forFrames: 500) == 500, "500 frames should use 500 bucket") |
| try check(MelExtractor.bucket(forFrames: 501) == 1000, "501 frames should use 1000 bucket") |
| try check(MelExtractor.bucket(forFrames: 2999) == 3000, "2999 frames should use 3000 bucket") |
| try check(MelExtractor.bucket(forFrames: 9999) == 3000, "long audio should clamp to 3000 bucket") |
| try check(AudioTower.arkAudioTokenCount(sampleCount: 67_263) == 52, |
| "4.2s reference should produce 52 audio tokens") |
|
|
| print("dev-check PASS") |
| } catch { |
| fputs("dev-check FAIL: \(error)\n", stderr) |
| exit(1) |
| } |
|
|