Skip to content

Commit 0ea3166

Browse files
committed
Migrate to Swift 6 with Swift Testing support.
1 parent 4d928a0 commit 0ea3166

File tree

6 files changed

+527
-236
lines changed

6 files changed

+527
-236
lines changed

Package.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version: 5.7
1+
// swift-tools-version: 6.2
22
import PackageDescription
33

44
let package = Package(
@@ -18,10 +18,6 @@ let package = Package(
1818
.testTarget(
1919
name: "TekkonTests",
2020
dependencies: ["Tekkon"],
21-
linkerSettings: [
22-
.linkedFramework("XCTest", .when(platforms: [.macOS])),
23-
.linkedFramework("Foundation", .when(platforms: [.macOS])),
24-
]
2521
),
2622
]
2723
)

Package@swift-5.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// swift-tools-version: 5.7
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "Tekkon",
6+
products: [
7+
.library(
8+
name: "Tekkon",
9+
targets: ["Tekkon"]
10+
),
11+
],
12+
dependencies: [],
13+
targets: [
14+
.target(
15+
name: "Tekkon",
16+
dependencies: []
17+
),
18+
]
19+
)

Tests/TekkonTests/TekkonTests_Arrangements.swift

Lines changed: 133 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
// This code is released under the SPDX-License-Identifier: `LGPL-3.0-or-later`.
44

55
import Foundation
6+
import Testing
7+
68
@testable import Tekkon
7-
import XCTest
89

910
// MARK: - Something Else
1011

@@ -67,11 +68,107 @@ final class SubTestCase: Sendable {
6768
print(strError)
6869
return false
6970
}
71+
72+
/// 優化版驗證函數,重複使用已建立的 composer 副本
73+
func verify(using composer: inout Tekkon.Composer) -> Bool {
74+
composer.clear()
75+
composer.ensureParser(arrange: parser)
76+
let strResult = composer.receiveSequence(typing)
77+
guard strResult != expected else { return true }
78+
let parserTag = composer.parser.nameTag
79+
let strError =
80+
"MISMATCH (\(parserTag)): \"\(typing)\" -> \"\(strResult)\" != \"\(expected)\""
81+
print(strError)
82+
return false
83+
}
84+
}
85+
86+
// MARK: - TestCaseBatch
87+
88+
/// 批量測試案例處理器,用於減少記憶體分配
89+
actor TestCaseBatch {
90+
// MARK: Lifecycle
91+
92+
init(parser: Tekkon.MandarinParser, rawData: String) {
93+
self.parser = parser
94+
var cases: [(String, String)] = []
95+
var isTitleLine = true
96+
let parserIndex = Self.getParserIndex(parser)
97+
98+
rawData.parse(splitee: "\n") { theRange in
99+
guard !isTitleLine else {
100+
isTitleLine = false
101+
return
102+
}
103+
let cells = rawData[theRange].split(separator: " ")
104+
guard cells.count > parserIndex else { return }
105+
let expected = cells[0].replacingOccurrences(of: "_", with: " ")
106+
let typing = cells[parserIndex].description.replacingOccurrences(of: "_", with: " ")
107+
guard typing.first != "`" else { return }
108+
cases.append((typing: typing, expected: expected))
109+
}
110+
self.testData = cases
111+
}
112+
113+
// MARK: Internal
114+
115+
let parser: Tekkon.MandarinParser
116+
let testData: [(typing: String, expected: String)]
117+
118+
func runTests() async -> Int {
119+
let indices = testData.indices
120+
let chunkSize = 350
121+
let failedCount = await withTaskGroup(of: Int.self, returning: Int.self) { group in
122+
var failures = 0
123+
for chunkStartPoint in stride(from: 0, to: indices.upperBound, by: chunkSize) {
124+
let subIndiceMax = Swift.min(indices.upperBound, chunkStartPoint + chunkSize)
125+
let subIndices = chunkStartPoint ..< subIndiceMax
126+
for i in subIndices {
127+
group.addTask {
128+
var subFailures = 0
129+
var composer = Tekkon.Composer(arrange: self.parser)
130+
let strResult = composer.receiveSequence(self.testData[i].typing)
131+
if strResult != self.testData[i].expected {
132+
let parserTag = composer.parser.nameTag
133+
let typingStr = self.testData[i].typing
134+
let expectedStr = self.testData[i].expected
135+
let strError =
136+
"MISMATCH (\(parserTag)): \"\(typingStr)\" -> \"\(strResult)\" != \"\(expectedStr)\""
137+
print(strError)
138+
subFailures += 1
139+
}
140+
return subFailures
141+
}
142+
}
143+
for await subFailures in group {
144+
// Set operation name as key and operation result as value
145+
failures += subFailures
146+
}
147+
}
148+
return failures
149+
}
150+
return failedCount
151+
}
152+
153+
// MARK: Private
154+
155+
private static func getParserIndex(_ parser: Tekkon.MandarinParser) -> Int {
156+
switch parser {
157+
case .ofDachen26: return 1
158+
case .ofETen26: return 2
159+
case .ofHsu: return 3
160+
case .ofStarlight: return 4
161+
case .ofAlvinLiu: return 5
162+
default: return 1
163+
}
164+
}
70165
}
71166

72167
// MARK: - TekkonTestsKeyboardArrangmentsStatic
73168

74-
final class TekkonTestsKeyboardArrangmentsStatic: XCTestCase {
169+
@MainActor
170+
@Suite(.serialized)
171+
struct TekkonTestsKeyboardArrangmentsStatic {
75172
static func checkEq(
76173
_ counter: inout Int,
77174
_ composer: inout Tekkon.Composer,
@@ -87,7 +184,8 @@ final class TekkonTestsKeyboardArrangmentsStatic: XCTestCase {
87184
counter += 1
88185
}
89186

90-
func testQwertyDachenKeys() {
187+
@Test("[Tekkon] StaticKeyLayout_Dachen")
188+
func testQwertyDachenKeys() async throws {
91189
// Testing Dachen Traditional Mapping (QWERTY)
92190
var c = Tekkon.Composer(arrange: .ofDachen)
93191
var counter = 0
@@ -106,51 +204,44 @@ final class TekkonTestsKeyboardArrangmentsStatic: XCTestCase {
106204
Self.checkEq(&counter, &c, "hl3", "ㄘㄠˇ")
107205
Self.checkEq(&counter, &c, "5 ", "")
108206
Self.checkEq(&counter, &c, "193", "ㄅㄞˇ")
109-
XCTAssertEqual(counter, 0)
207+
#expect(counter == 0)
110208
}
111209
}
112210

113211
// MARK: - TekkonTestsKeyboardArrangmentsDynamic
114212

115-
final class TekkonTestsKeyboardArrangmentsDynamic: XCTestCase {
213+
@MainActor
214+
@Suite(.serialized)
215+
struct TekkonTestsKeyboardArrangmentsDynamic {
116216
typealias Parser = Tekkon.MandarinParser
117217

118-
func testDynamicKeyLayouts() {
119-
// 遍歷所有動態鍵盤配置
120-
for (idxRaw, parser) in Parser.allCases.filter(\.isDynamic).enumerated() {
121-
var cases = [SubTestCase?]()
122-
print(" -> [Tekkon] Preparing tests for dynamic keyboard handling...")
123-
var isTitleLine = true
124-
testTable4DynamicLayouts.parse(splitee: "\n") { theRange in
125-
guard !isTitleLine else {
126-
isTitleLine = false
127-
return
128-
}
129-
let cells = testTable4DynamicLayouts[theRange].split(separator: " ")
130-
let expected = cells[0]
131-
let idx = idxRaw + 1
132-
let typing = cells[idx]
133-
let newTestCase = SubTestCase(
134-
parser: parser,
135-
typing: typing.description,
136-
expected: expected.description
137-
)
138-
cases.append(newTestCase)
139-
}
140-
let timeTag = Date.now
141-
print(" -> [Tekkon][(\(parser.nameTag))] Starting dynamic keyboard handling test ...")
142-
let results = cases.compactMap { testCase in
143-
(testCase?.verify() ?? true) ? 0 : 1
144-
}.reduce(0, +)
145-
XCTAssertEqual(
146-
results, 0,
147-
"[Failure] \(parser.nameTag) failed from being handled correctly with \(results) bad results."
148-
)
149-
let timeDelta = Date.now.timeIntervalSince1970 - timeTag.timeIntervalSince1970
150-
let timeDeltaStr = String(format: "%.4f", timeDelta)
151-
print(
152-
" -> [Tekkon][(\(parser.nameTag))] Finished within \(timeDeltaStr) seconds."
153-
)
154-
}
218+
@Test(
219+
"[Tekkon] DynamicKeyLayouts",
220+
arguments: Array(Parser.allCases.filter(\.isDynamic).enumerated())
221+
)
222+
func testDynamicKeyLayouts(
223+
_ parserEnumerated: EnumeratedSequence<[Parser]>
224+
.Element
225+
) async throws {
226+
let parser = parserEnumerated.element
227+
print(" -> [Tekkon] Preparing tests for dynamic keyboard handling...")
228+
229+
// 使用批量處理器以提升效能
230+
let testBatch = TestCaseBatch(parser: parser, rawData: testTable4DynamicLayouts)
231+
232+
let timeTag = Date.now
233+
print(" -> [Tekkon][(\(parser.nameTag))] Starting dynamic keyboard handling test ...")
234+
235+
let failures = await testBatch.runTests()
236+
237+
#expect(
238+
failures == 0,
239+
"[Failure] \(parser.nameTag) failed from being handled correctly with \(failures) bad results."
240+
)
241+
let timeDelta = Date.now.timeIntervalSince1970 - timeTag.timeIntervalSince1970
242+
let timeDeltaStr = String(format: "%.4f", timeDelta)
243+
print(
244+
" -> [Tekkon][(\(parser.nameTag))] Finished within \(timeDeltaStr) seconds."
245+
)
155246
}
156247
}

0 commit comments

Comments
 (0)