mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
421 lines
12 KiB
Swift
421 lines
12 KiB
Swift
import Foundation
|
|
import Testing
|
|
|
|
@Suite("Performance and Stress Tests", .tags(.critical))
|
|
struct PerformanceTests {
|
|
// MARK: - String Performance
|
|
|
|
@Test("Large string concatenation performance")
|
|
func stringConcatenation() {
|
|
let iterations = 1_000
|
|
|
|
// Test inefficient concatenation
|
|
func inefficientConcat() -> String {
|
|
var result = ""
|
|
for i in 0..<iterations {
|
|
result += "Line \(i)\n"
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Test efficient concatenation
|
|
func efficientConcat() -> String {
|
|
var parts: [String] = []
|
|
parts.reserveCapacity(iterations)
|
|
for i in 0..<iterations {
|
|
parts.append("Line \(i)\n")
|
|
}
|
|
return parts.joined()
|
|
}
|
|
|
|
// Measure approximate performance difference
|
|
let start1 = Date()
|
|
let result1 = inefficientConcat()
|
|
let time1 = Date().timeIntervalSince(start1)
|
|
|
|
let start2 = Date()
|
|
let result2 = efficientConcat()
|
|
let time2 = Date().timeIntervalSince(start2)
|
|
|
|
#expect(!result1.isEmpty)
|
|
#expect(!result2.isEmpty)
|
|
// Allow some variance in timing - just verify both methods work
|
|
#expect(time1 >= 0)
|
|
#expect(time2 >= 0)
|
|
}
|
|
|
|
// MARK: - Collection Performance
|
|
|
|
@Test("Array vs Set lookup performance")
|
|
func collectionLookup() {
|
|
let size = 10_000
|
|
let searchValues = Array(0..<100)
|
|
|
|
// Create collections
|
|
let array = Array(0..<size)
|
|
let set = Set(array)
|
|
|
|
// Test array contains (O(n))
|
|
var arrayHits = 0
|
|
for value in searchValues {
|
|
if array.contains(value) {
|
|
arrayHits += 1
|
|
}
|
|
}
|
|
|
|
// Test set contains (O(1))
|
|
var setHits = 0
|
|
for value in searchValues {
|
|
if set.contains(value) {
|
|
setHits += 1
|
|
}
|
|
}
|
|
|
|
#expect(arrayHits == setHits)
|
|
#expect(arrayHits == searchValues.count)
|
|
}
|
|
|
|
@Test("Dictionary performance with collision-prone keys")
|
|
func dictionaryCollisions() {
|
|
// Create keys that might have hash collisions
|
|
struct PoorHashKey: Hashable {
|
|
let value: Int
|
|
|
|
func hash(into hasher: inout Hasher) {
|
|
// Poor hash function that causes collisions
|
|
hasher.combine(value % 10)
|
|
}
|
|
}
|
|
|
|
var dict: [PoorHashKey: String] = [:]
|
|
let count = 1_000
|
|
|
|
// Insert values
|
|
for i in 0..<count {
|
|
dict[PoorHashKey(value: i)] = "Value \(i)"
|
|
}
|
|
|
|
#expect(dict.count == count)
|
|
|
|
// Lookup values
|
|
var found = 0
|
|
for i in 0..<count {
|
|
if dict[PoorHashKey(value: i)] != nil {
|
|
found += 1
|
|
}
|
|
}
|
|
|
|
#expect(found == count)
|
|
}
|
|
|
|
// MARK: - Memory Stress Tests
|
|
|
|
@Test("Memory allocation stress test")
|
|
func memoryAllocation() {
|
|
let allocationSize = 1_024 * 1_024 // 1 MB
|
|
let iterations = 10
|
|
|
|
var allocations: [Data] = []
|
|
allocations.reserveCapacity(iterations)
|
|
|
|
// Allocate multiple chunks
|
|
for _ in 0..<iterations {
|
|
let data = Data(count: allocationSize)
|
|
allocations.append(data)
|
|
}
|
|
|
|
#expect(allocations.count == iterations)
|
|
|
|
// Verify all allocations
|
|
for data in allocations {
|
|
#expect(data.count == allocationSize)
|
|
}
|
|
|
|
// Clear to free memory
|
|
allocations.removeAll()
|
|
}
|
|
|
|
@Test("Autorelease pool stress test")
|
|
func autoreleasePool() {
|
|
let iterations = 10_000
|
|
|
|
// Without autorelease pool
|
|
var withoutPool: [NSString] = []
|
|
for i in 0..<iterations {
|
|
let str = NSString(format: "String %d with some additional text", i)
|
|
withoutPool.append(str)
|
|
}
|
|
|
|
// With autorelease pool
|
|
var withPool: [NSString] = []
|
|
for batch in 0..<10 {
|
|
autoreleasepool {
|
|
for i in 0..<(iterations / 10) {
|
|
let str = NSString(format: "String %d with some additional text", batch * (iterations / 10) + i)
|
|
withPool.append(str)
|
|
}
|
|
}
|
|
}
|
|
|
|
#expect(withoutPool.count == iterations)
|
|
#expect(withPool.count == iterations)
|
|
}
|
|
|
|
// MARK: - Concurrent Operations
|
|
|
|
@Test("Concurrent queue stress test")
|
|
func concurrentQueues() {
|
|
let queue = DispatchQueue(label: "test.concurrent", attributes: .concurrent)
|
|
let iterations = 100
|
|
let group = DispatchGroup()
|
|
|
|
actor ResultsActor {
|
|
private var results: [Int]
|
|
|
|
init(count: Int) {
|
|
self.results = [Int](repeating: 0, count: count)
|
|
}
|
|
|
|
func set(_ value: Int, at index: Int) {
|
|
results[index] = value
|
|
}
|
|
|
|
func getResults() -> [Int] {
|
|
results
|
|
}
|
|
}
|
|
|
|
let resultsActor = ResultsActor(count: iterations)
|
|
|
|
// Perform concurrent operations
|
|
for i in 0..<iterations {
|
|
group.enter()
|
|
queue.async {
|
|
// Simulate work
|
|
let value = i * i
|
|
|
|
// Thread-safe write
|
|
Task {
|
|
await resultsActor.set(value, at: i)
|
|
group.leave()
|
|
}
|
|
}
|
|
}
|
|
|
|
group.wait()
|
|
|
|
// Verify all operations completed
|
|
Task { @MainActor in
|
|
let results = await resultsActor.getResults()
|
|
for i in 0..<iterations {
|
|
#expect(results[i] == i * i)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test("Lock contention stress test")
|
|
func lockContention() {
|
|
actor SharedCounter {
|
|
private var value = 0
|
|
|
|
func increment() {
|
|
value += 1
|
|
}
|
|
|
|
func getValue() -> Int {
|
|
value
|
|
}
|
|
}
|
|
|
|
let sharedCounter = SharedCounter()
|
|
let iterations = 1_000
|
|
let queues = 4
|
|
let group = DispatchGroup()
|
|
|
|
// Create contention with multiple queues
|
|
for _ in 0..<queues {
|
|
group.enter()
|
|
DispatchQueue.global().async {
|
|
Task {
|
|
for _ in 0..<iterations {
|
|
await sharedCounter.increment()
|
|
}
|
|
group.leave()
|
|
}
|
|
}
|
|
}
|
|
|
|
group.wait()
|
|
|
|
Task { @MainActor in
|
|
let finalValue = await sharedCounter.getValue()
|
|
#expect(finalValue == iterations * queues)
|
|
}
|
|
}
|
|
|
|
// MARK: - I/O Performance
|
|
|
|
@Test("File I/O stress test")
|
|
func fileIO() {
|
|
let tempDir = FileManager.default.temporaryDirectory
|
|
let testFile = tempDir.appendingPathComponent("stress_test_\(UUID().uuidString).txt")
|
|
|
|
defer {
|
|
try? FileManager.default.removeItem(at: testFile)
|
|
}
|
|
|
|
let content = String(repeating: "Test data line\n", count: 1_000)
|
|
let data = content.data(using: .utf8)!
|
|
|
|
// Write test
|
|
do {
|
|
try data.write(to: testFile)
|
|
|
|
// Read test
|
|
let readData = try Data(contentsOf: testFile)
|
|
#expect(readData.count == data.count)
|
|
|
|
// Append test
|
|
if let handle = try? FileHandle(forWritingTo: testFile) {
|
|
handle.seekToEndOfFile()
|
|
handle.write(data)
|
|
handle.closeFile()
|
|
}
|
|
|
|
// Verify doubled size
|
|
let finalData = try Data(contentsOf: testFile)
|
|
#expect(finalData.count == data.count * 2)
|
|
} catch {
|
|
#expect(Bool(false), "File I/O failed: \(error)")
|
|
}
|
|
}
|
|
|
|
// MARK: - Network Simulation
|
|
|
|
@Test("URL session task stress test")
|
|
func uRLSessionStress() {
|
|
let session = URLSession(configuration: .ephemeral)
|
|
let iterations = 10
|
|
let group = DispatchGroup()
|
|
|
|
actor SuccessCounter {
|
|
private var count = 0
|
|
|
|
func increment() {
|
|
count += 1
|
|
}
|
|
|
|
func getCount() -> Int {
|
|
count
|
|
}
|
|
}
|
|
|
|
let successCounter = SuccessCounter()
|
|
|
|
for i in 0..<iterations {
|
|
group.enter()
|
|
|
|
// Create a data task with invalid URL to test error handling
|
|
let url = URL(string: "https://invalid-domain-\(i).test")!
|
|
let task = session.dataTask(with: url) { _, _, error in
|
|
if error != nil {
|
|
Task {
|
|
await successCounter.increment() // We expect errors for invalid domains
|
|
}
|
|
}
|
|
group.leave()
|
|
}
|
|
|
|
task.resume()
|
|
}
|
|
|
|
group.wait()
|
|
|
|
Task { @MainActor in
|
|
let finalCount = await successCounter.getCount()
|
|
#expect(finalCount == iterations) // All should fail with invalid domains
|
|
}
|
|
}
|
|
|
|
// MARK: - Algorithm Performance
|
|
|
|
@Test("Sorting algorithm performance")
|
|
func sortingPerformance() {
|
|
let size = 10_000
|
|
let randomArray = (0..<size).shuffled()
|
|
|
|
// Test built-in sort
|
|
var array1 = randomArray
|
|
let start1 = Date()
|
|
array1.sort()
|
|
let time1 = Date().timeIntervalSince(start1)
|
|
|
|
// Test sort with custom comparator
|
|
var array2 = randomArray
|
|
let start2 = Date()
|
|
array2.sort { $0 < $1 }
|
|
let time2 = Date().timeIntervalSince(start2)
|
|
|
|
// Verify both sorted correctly
|
|
#expect(array1 == Array(0..<size))
|
|
#expect(array2 == Array(0..<size))
|
|
|
|
// Built-in should be faster or similar
|
|
#expect(time1 <= time2 * 2) // Allow some variance
|
|
}
|
|
|
|
@Test("Hash table resize performance")
|
|
func hashTableResize() {
|
|
var dictionary: [Int: String] = [:]
|
|
let iterations = 10_000
|
|
|
|
// Pre-size vs dynamic resize
|
|
var preSized: [Int: String] = [:]
|
|
preSized.reserveCapacity(iterations)
|
|
|
|
let start1 = Date()
|
|
for i in 0..<iterations {
|
|
dictionary[i] = "Value \(i)"
|
|
}
|
|
let time1 = Date().timeIntervalSince(start1)
|
|
|
|
let start2 = Date()
|
|
for i in 0..<iterations {
|
|
preSized[i] = "Value \(i)"
|
|
}
|
|
let time2 = Date().timeIntervalSince(start2)
|
|
|
|
#expect(dictionary.count == iterations)
|
|
#expect(preSized.count == iterations)
|
|
|
|
// Pre-sized should be faster or similar
|
|
#expect(time2 <= time1 * 1.5) // Allow some variance
|
|
}
|
|
|
|
// MARK: - WebSocket Message Processing
|
|
|
|
@Test("Binary message parsing performance")
|
|
func binaryMessageParsing() {
|
|
// Simulate parsing many binary messages
|
|
let messageCount = 1_000
|
|
let messageSize = 1_024
|
|
|
|
var parsedCount = 0
|
|
|
|
for _ in 0..<messageCount {
|
|
// Create a mock binary message
|
|
var data = Data()
|
|
data.append(0x01) // Magic byte
|
|
data.append(contentsOf: withUnsafeBytes(of: Int32(80).littleEndian) { Array($0) })
|
|
data.append(contentsOf: withUnsafeBytes(of: Int32(24).littleEndian) { Array($0) })
|
|
data.append(Data(count: messageSize))
|
|
|
|
// Parse the message
|
|
if data[0] == 0x01 && data.count >= 9 {
|
|
parsedCount += 1
|
|
}
|
|
}
|
|
|
|
#expect(parsedCount == messageCount)
|
|
}
|
|
}
|