mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
397 lines
13 KiB
Swift
397 lines
13 KiB
Swift
import Foundation
|
|
import Testing
|
|
|
|
@Suite("Edge Case and Boundary Tests", .tags(.critical))
|
|
struct EdgeCaseTests {
|
|
// MARK: - String and Buffer Boundaries
|
|
|
|
@Test("Empty and nil string handling")
|
|
func emptyStrings() {
|
|
// Test various empty string scenarios
|
|
let emptyString = ""
|
|
let whitespaceString = " "
|
|
let newlineString = "\n\n\n"
|
|
|
|
#expect(emptyString.isEmpty)
|
|
#expect(whitespaceString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
|
|
#expect(newlineString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
|
|
|
|
// Test optional string handling
|
|
let nilString: String? = nil
|
|
let emptyOptional: String? = ""
|
|
|
|
#expect(nilString?.isEmpty ?? true)
|
|
#expect(emptyOptional?.isEmpty == true)
|
|
}
|
|
|
|
@Test("Maximum string length boundaries")
|
|
func stringBoundaries() {
|
|
// Test very long strings
|
|
let maxReasonableLength = 1_000_000
|
|
let longString = String(repeating: "a", count: maxReasonableLength)
|
|
|
|
#expect(longString.count == maxReasonableLength)
|
|
|
|
// Test string truncation
|
|
func truncate(_ string: String, to maxLength: Int) -> String {
|
|
if string.count <= maxLength {
|
|
return string
|
|
}
|
|
let endIndex = string.index(string.startIndex, offsetBy: maxLength)
|
|
return String(string[..<endIndex]) + "..."
|
|
}
|
|
|
|
let truncated = truncate(longString, to: 100)
|
|
#expect(truncated.count == 103) // 100 + "..."
|
|
#expect(truncated.hasSuffix("..."))
|
|
}
|
|
|
|
// MARK: - Numeric Boundaries
|
|
|
|
@Test("Integer overflow and underflow")
|
|
func integerBoundaries() {
|
|
// Test boundaries
|
|
let maxInt = Int.max
|
|
let minInt = Int.min
|
|
|
|
// Safe addition with overflow check
|
|
func safeAdd(_ a: Int, _ b: Int) -> Int? {
|
|
let (result, overflow) = a.addingReportingOverflow(b)
|
|
return overflow ? nil : result
|
|
}
|
|
|
|
#expect(safeAdd(maxInt, 1) == nil)
|
|
#expect(safeAdd(minInt, -1) == nil)
|
|
#expect(safeAdd(100, 200) == 300)
|
|
|
|
// Test conversion boundaries
|
|
let uint32Max = UInt32.max
|
|
let int32Max = Int32.max
|
|
|
|
// On 64-bit systems, Int can hold UInt32.max
|
|
#if arch(i386) || arch(arm)
|
|
#expect(Int(exactly: uint32Max) == nil) // Can't fit in 32-bit Int
|
|
#else
|
|
#expect(Int(exactly: uint32Max) != nil) // Can fit in 64-bit Int
|
|
#endif
|
|
#expect(Int32(exactly: int32Max) == int32Max)
|
|
}
|
|
|
|
@Test("Floating point edge cases")
|
|
func floatingPointEdgeCases() {
|
|
let infinity = Double.infinity
|
|
let negInfinity = -Double.infinity
|
|
let nan = Double.nan
|
|
|
|
#expect(infinity.isInfinite)
|
|
#expect(negInfinity.isInfinite)
|
|
#expect(nan.isNaN)
|
|
|
|
// Test comparisons with special values
|
|
#expect(nan.isNaN) // NaN is not equal to itself
|
|
#expect(infinity > 1_000_000)
|
|
#expect(negInfinity < -1_000_000)
|
|
|
|
// Test safe division
|
|
func safeDivide(_ a: Double, by b: Double) -> Double? {
|
|
guard b != 0 && !b.isNaN else { return nil }
|
|
let result = a / b
|
|
return result.isFinite ? result : nil
|
|
}
|
|
|
|
#expect(safeDivide(10, by: 0) == nil)
|
|
#expect(safeDivide(10, by: 2) == 5)
|
|
#expect(safeDivide(infinity, by: 2) == nil)
|
|
}
|
|
|
|
// MARK: - Collection Boundaries
|
|
|
|
@Test("Empty collection handling")
|
|
func emptyCollections() {
|
|
let emptyArray: [Int] = []
|
|
let emptyDict: [String: Any] = [:]
|
|
let emptySet: Set<String> = []
|
|
|
|
#expect(emptyArray.first == nil)
|
|
#expect(emptyArray.last == nil)
|
|
#expect(emptyDict.isEmpty)
|
|
#expect(emptySet.isEmpty)
|
|
|
|
// Safe array access
|
|
func safeAccess<T>(_ array: [T], at index: Int) -> T? {
|
|
guard index >= 0 && index < array.count else { return nil }
|
|
return array[index]
|
|
}
|
|
|
|
#expect(safeAccess(emptyArray, at: 0) == nil)
|
|
#expect(safeAccess([1, 2, 3], at: 1) == 2)
|
|
#expect(safeAccess([1, 2, 3], at: 10) == nil)
|
|
#expect(safeAccess([1, 2, 3], at: -1) == nil)
|
|
}
|
|
|
|
@Test("Large collection performance boundaries")
|
|
func largeCollections() {
|
|
// Test with moderately large collections
|
|
let largeSize = 10_000
|
|
let largeArray = Array(0..<largeSize)
|
|
let largeSet = Set(0..<largeSize)
|
|
let largeDict = Dictionary(uniqueKeysWithValues: (0..<largeSize).map { ($0, "value\($0)") })
|
|
|
|
#expect(largeArray.count == largeSize)
|
|
#expect(largeSet.count == largeSize)
|
|
#expect(largeDict.count == largeSize)
|
|
|
|
// Test contains performance
|
|
#expect(largeSet.contains(5_000))
|
|
#expect(!largeSet.contains(largeSize))
|
|
|
|
// Test dictionary access
|
|
#expect(largeDict[5_000] == "value5000")
|
|
#expect(largeDict[largeSize] == nil)
|
|
}
|
|
|
|
// MARK: - Date and Time Boundaries
|
|
|
|
@Test("Date boundary conditions")
|
|
func dateBoundaries() {
|
|
// Test distant dates
|
|
let distantPast = Date.distantPast
|
|
let distantFuture = Date.distantFuture
|
|
let now = Date()
|
|
|
|
#expect(distantPast < now)
|
|
#expect(distantFuture > now)
|
|
|
|
// Test date calculations near boundaries
|
|
let oneDay: TimeInterval = 86_400
|
|
let farFuture = distantFuture.addingTimeInterval(-oneDay)
|
|
#expect(farFuture < distantFuture)
|
|
|
|
// Test date component validation
|
|
var components = DateComponents()
|
|
components.year = 2_024
|
|
components.month = 13 // Invalid month
|
|
components.day = 32 // Invalid day
|
|
|
|
let calendar = Calendar.current
|
|
let date = calendar.date(from: components)
|
|
|
|
// Calendar may adjust invalid dates rather than return nil
|
|
if let date {
|
|
let adjustedComponents = calendar.dateComponents([.year, .month, .day], from: date)
|
|
// Should have adjusted the invalid values
|
|
#expect(adjustedComponents.month != 13)
|
|
#expect(adjustedComponents.day != 32)
|
|
}
|
|
}
|
|
|
|
// MARK: - URL and Network Boundaries
|
|
|
|
@Test("URL edge cases")
|
|
func uRLEdgeCases() {
|
|
// Test various URL formats
|
|
let validURLs = [
|
|
"https://example.com",
|
|
"http://localhost:8080",
|
|
"ftp://files.example.com",
|
|
"file:///Users/test/file.txt",
|
|
"https://example.com/path%20with%20spaces"
|
|
]
|
|
|
|
for urlString in validURLs {
|
|
let url = URL(string: urlString)
|
|
#expect(url != nil)
|
|
}
|
|
|
|
// Test URLs that should be invalid or have issues
|
|
let problematicURLs = [
|
|
("", false), // Empty string
|
|
("not a url", true), // Might be parsed as relative URL
|
|
("http://", true), // Has scheme but no host
|
|
("://missing-scheme", true), // Invalid format
|
|
("http://[invalid-ipv6", false), // Malformed IPv6
|
|
("https://example.com/\u{0000}", true) // Null character
|
|
]
|
|
|
|
for (urlString, mightBeValid) in problematicURLs {
|
|
let url = URL(string: urlString)
|
|
if mightBeValid && url != nil {
|
|
// Check if it's actually a useful URL
|
|
#expect(url?.scheme != nil || url?.host != nil || url?.path != nil)
|
|
} else {
|
|
#expect(url == nil)
|
|
}
|
|
}
|
|
|
|
// Test extremely long URLs
|
|
let longPath = String(repeating: "a", count: 2_000)
|
|
let longURL = "https://example.com/\(longPath)"
|
|
let url = URL(string: longURL)
|
|
#expect(url != nil)
|
|
#expect(url?.absoluteString.count ?? 0 > 2_000)
|
|
}
|
|
|
|
// MARK: - Thread Safety Boundaries
|
|
|
|
@Test("Concurrent access boundaries")
|
|
func concurrentAccess() {
|
|
// Test thread-safe counter
|
|
final class ThreadSafeCounter: @unchecked Sendable {
|
|
private var value = 0
|
|
private let queue = DispatchQueue(label: "counter", attributes: .concurrent)
|
|
|
|
func increment() {
|
|
queue.async(flags: .barrier) {
|
|
self.value += 1
|
|
}
|
|
}
|
|
|
|
func read() -> Int {
|
|
queue.sync { value }
|
|
}
|
|
}
|
|
|
|
let counter = ThreadSafeCounter()
|
|
let iterations = 100
|
|
let group = DispatchGroup()
|
|
|
|
// Simulate concurrent increments
|
|
for _ in 0..<iterations {
|
|
group.enter()
|
|
DispatchQueue.global().async {
|
|
counter.increment()
|
|
group.leave()
|
|
}
|
|
}
|
|
|
|
group.wait()
|
|
|
|
// Value should be exactly iterations (no race conditions)
|
|
#expect(counter.read() == iterations)
|
|
}
|
|
|
|
// MARK: - Memory Boundaries
|
|
|
|
@Test("Memory allocation boundaries")
|
|
func memoryBoundaries() {
|
|
// Test large data allocation
|
|
let megabyte = 1_024 * 1_024
|
|
let size = 10 * megabyte // 10 MB
|
|
|
|
// Safely allocate memory
|
|
func safeAllocate(bytes: Int) -> Data? {
|
|
guard bytes > 0 && bytes < Int.max / 2 else { return nil }
|
|
return Data(count: bytes)
|
|
}
|
|
|
|
let data = safeAllocate(bytes: size)
|
|
#expect(data?.count == size)
|
|
|
|
// Test zero allocation
|
|
let zeroData = safeAllocate(bytes: 0)
|
|
#expect(zeroData == nil)
|
|
|
|
// Test negative allocation (caught by guard)
|
|
let negativeData = safeAllocate(bytes: -1)
|
|
#expect(negativeData == nil)
|
|
}
|
|
|
|
// MARK: - Encoding Edge Cases
|
|
|
|
@Test("Character encoding boundaries")
|
|
func encodingBoundaries() {
|
|
// Test various Unicode scenarios
|
|
let testCases = [
|
|
"Hello", // ASCII
|
|
"你好", // Chinese
|
|
"🇺🇸🇬🇧", // Flag emojis
|
|
"👨👩👧👦", // Family emoji
|
|
"\u{0000}", // Null character
|
|
"\u{FFFF}", // Maximum BMP character
|
|
"A\u{0301}" // Combining character (A + accent)
|
|
]
|
|
|
|
for testString in testCases {
|
|
// Test UTF-8 encoding
|
|
let utf8Data = testString.data(using: .utf8)
|
|
#expect(utf8Data != nil)
|
|
|
|
// Test round-trip
|
|
if let data = utf8Data {
|
|
let decoded = String(data: data, encoding: .utf8)
|
|
#expect(decoded == testString)
|
|
}
|
|
}
|
|
|
|
// Test invalid UTF-8 sequences
|
|
let invalidUTF8 = Data([0xFF, 0xFE, 0xFD])
|
|
let decoded = String(data: invalidUTF8, encoding: .utf8)
|
|
#expect(decoded == nil)
|
|
}
|
|
|
|
// MARK: - JSON Edge Cases
|
|
|
|
@Test("JSON encoding special cases")
|
|
func jSONEdgeCases() {
|
|
struct TestModel: Codable {
|
|
let value: Any?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case value
|
|
}
|
|
|
|
init(value: Any?) {
|
|
self.value = value
|
|
}
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
if let intValue = try? container.decode(Int.self, forKey: .value) {
|
|
value = intValue
|
|
} else if let doubleValue = try? container.decode(Double.self, forKey: .value) {
|
|
value = doubleValue
|
|
} else if let stringValue = try? container.decode(String.self, forKey: .value) {
|
|
value = stringValue
|
|
} else {
|
|
value = nil
|
|
}
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
if let intValue = value as? Int {
|
|
try container.encode(intValue, forKey: .value)
|
|
} else if let doubleValue = value as? Double {
|
|
try container.encode(doubleValue, forKey: .value)
|
|
} else if let stringValue = value as? String {
|
|
try container.encode(stringValue, forKey: .value)
|
|
} else {
|
|
try container.encodeNil(forKey: .value)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test edge case values
|
|
let edgeCases: [(String, Bool)] = [
|
|
(#"{"value": null}"#, true),
|
|
(#"{"value": 9223372036854775807}"#, true), // Int.max
|
|
(#"{"value": -9223372036854775808}"#, true), // Int.min
|
|
(#"{"value": 1.7976931348623157e+308}"#, true), // Near Double.max
|
|
(#"{"value": "string with \"quotes\""}"#, true),
|
|
(#"{"value": "\u0000"}"#, true), // Null character
|
|
(#"{invalid json}"#, false),
|
|
(#"{"value": undefined}"#, false)
|
|
]
|
|
|
|
for (json, shouldSucceed) in edgeCases {
|
|
let data = json.data(using: .utf8)!
|
|
let decoded = try? JSONDecoder().decode(TestModel.self, from: data)
|
|
|
|
if shouldSucceed {
|
|
#expect(decoded != nil)
|
|
} else {
|
|
#expect(decoded == nil)
|
|
}
|
|
}
|
|
}
|
|
}
|