Compare commits

...

2 Commits

Author SHA1 Message Date
Shadowfacts b30f149dc9 Use mutex on iOS 15 instead of os_unfair_lock
See #178
2022-10-08 10:57:59 -04:00
Shadowfacts 9b83566482 Fix TuskerTests not compiling 2022-10-08 10:55:55 -04:00
10 changed files with 223 additions and 158 deletions

View File

@ -33,6 +33,7 @@
D6114E1727F8BB210080E273 /* VersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1627F8BB210080E273 /* VersionTests.swift */; }; D6114E1727F8BB210080E273 /* VersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1627F8BB210080E273 /* VersionTests.swift */; };
D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */; }; D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */; };
D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */; }; D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */; };
D61ABEFC28F105DE00B29151 /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D61ABEFB28F105DE00B29151 /* Pachyderm */; };
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; }; D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; };
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; }; D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; }; D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; };
@ -208,8 +209,6 @@
D6A6C11525B62E9700298D0F /* CacheExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A6C11425B62E9700298D0F /* CacheExpiry.swift */; }; D6A6C11525B62E9700298D0F /* CacheExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A6C11425B62E9700298D0F /* CacheExpiry.swift */; };
D6A6C11B25B63CEE00298D0F /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A6C11A25B63CEE00298D0F /* MemoryCache.swift */; }; D6A6C11B25B63CEE00298D0F /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A6C11A25B63CEE00298D0F /* MemoryCache.swift */; };
D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */; }; D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */; };
D6ACE1AC240C3BAD004EA8E2 /* Ambassador.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65F613023AE99E000F3CFD3 /* Ambassador.framework */; };
D6ACE1AD240C3BAD004EA8E2 /* Embassy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65F612D23AE990C00F3CFD3 /* Embassy.framework */; };
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; }; D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; };
D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */; }; D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */; };
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */; }; D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */; };
@ -673,8 +672,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D6ACE1AC240C3BAD004EA8E2 /* Ambassador.framework in Frameworks */, D61ABEFC28F105DE00B29151 /* Pachyderm in Frameworks */,
D6ACE1AD240C3BAD004EA8E2 /* Embassy.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -1513,6 +1511,7 @@
); );
name = TuskerUITests; name = TuskerUITests;
packageProductDependencies = ( packageProductDependencies = (
D61ABEFB28F105DE00B29151 /* Pachyderm */,
); );
productName = TuskerUITests; productName = TuskerUITests;
productReference = D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */; productReference = D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */;
@ -2218,14 +2217,15 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = HGYVAQA9FW; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = TuskerTests/Info.plist; INFOPLIST_FILE = TuskerTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.TuskerTests; PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.TuskerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@ -2239,14 +2239,15 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = HGYVAQA9FW; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = TuskerTests/Info.plist; INFOPLIST_FILE = TuskerTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.TuskerTests; PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.TuskerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@ -2259,14 +2260,14 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = HGYVAQA9FW; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = TuskerUITests/Info.plist; INFOPLIST_FILE = TuskerUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.TuskerUITests; PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.TuskerUITests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@ -2279,14 +2280,14 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = HGYVAQA9FW; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = TuskerUITests/Info.plist; INFOPLIST_FILE = TuskerUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@loader_path/Frameworks", "@loader_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.TuskerUITests; PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.TuskerUITests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@ -2439,6 +2440,10 @@
package = D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */; package = D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
productName = SwiftSoup; productName = SwiftSoup;
}; };
D61ABEFB28F105DE00B29151 /* Pachyderm */ = {
isa = XCSwiftPackageProductDependency;
productName = Pachyderm;
};
D6552366289870790048A653 /* ScreenCorners */ = { D6552366289870790048A653 /* ScreenCorners */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = D6552365289870790048A653 /* XCRemoteSwiftPackageReference "ScreenCorners" */; package = D6552365289870790048A653 /* XCRemoteSwiftPackageReference "ScreenCorners" */;

View File

@ -19,7 +19,7 @@ class MultiThreadDictionary<Key: Hashable & Sendable, Value: Sendable> {
if #available(iOS 16.0, *) { if #available(iOS 16.0, *) {
self.lock = OSAllocatedUnfairLock(initialState: [:]) self.lock = OSAllocatedUnfairLock(initialState: [:])
} else { } else {
self.lock = UnfairLock(initialState: [:]) self.lock = MutexLock(initialState: [:])
} }
} }
@ -65,21 +65,41 @@ fileprivate protocol Lock<State> {
extension OSAllocatedUnfairLock: Lock { extension OSAllocatedUnfairLock: Lock {
} }
// from http://www.russbishop.net/the-law // something is wrong with the UnfairLock impl and it results in segv_accerrs
fileprivate class UnfairLock<State>: Lock { fileprivate class MutexLock<State>: Lock {
private var lock: UnsafeMutablePointer<os_unfair_lock>
private var state: State private var state: State
private var lock = NSLock()
init(initialState: State) { init(initialState: State) {
self.state = initialState self.state = initialState
self.lock = .allocate(capacity: 1)
self.lock.initialize(to: os_unfair_lock())
} }
deinit {
self.lock.deallocate() func withLock<R>(_ body: @Sendable (inout State) throws -> R) rethrows -> R where R : Sendable {
} if !lock.lock(before: Date(timeIntervalSinceNow: 1)) {
func withLock<R>(_ body: (inout State) throws -> R) rethrows -> R where R: Sendable { // if we can't acquire the lock after 1 second, something has gone catastrophically wrong
os_unfair_lock_lock(lock) fatalError()
defer { os_unfair_lock_unlock(lock) } }
defer { lock.unlock() }
return try body(&state) return try body(&state)
} }
} }
//// from http://www.russbishop.net/the-law
//fileprivate class UnfairLock<State>: Lock {
// private var lock: UnsafeMutablePointer<os_unfair_lock>
// private var state: State
// init(initialState: State) {
// self.state = initialState
// self.lock = .allocate(capacity: 1)
// self.lock.initialize(to: os_unfair_lock())
// }
// deinit {
// self.lock.deinitialize(count: 1)
// self.lock.deallocate()
// }
// func withLock<R>(_ body: (inout State) throws -> R) rethrows -> R where R: Sendable {
// os_unfair_lock_lock(lock)
// defer { os_unfair_lock_unlock(lock) }
// return try body(&state)
// }
//}

View File

@ -30,5 +30,45 @@ class TuskerTests: XCTestCase {
// Put the code you want to measure the time of here. // Put the code you want to measure the time of here.
} }
} }
func testFuckingLock() {
let lock = MutexLock<[Int: Bool]>(initialState: [:])
for i in 0..<100 {
Thread.detachNewThread {
for j in 0..<50_000 {
lock.withLock {
$0[i * 50_000 + j] = true
}
}
}
}
while true {
if lock.withLock({ $0.count }) == 5_000_000 {
break
}
}
lock.withLock({ _ in
print("WHAT THE FUUUUUUUUUUUUCK")
})
}
} }
fileprivate class MutexLock<State> {
private var state: State
private var lock = NSLock()
init(initialState: State) {
self.state = initialState
}
func withLock<R>(_ body: @Sendable (inout State) throws -> R) rethrows -> R where R : Sendable {
if !lock.lock(before: Date(timeIntervalSinceNow: 1)) {
// if we can't acquire the lock after 1 second, something has gone catastrophically wrong
fatalError()
}
defer { lock.unlock() }
return try body(&state)
}
}

View File

@ -7,70 +7,70 @@
// //
import Foundation import Foundation
import Ambassador //import Ambassador
//
fileprivate let notFound = ["error": "Record not found"] //fileprivate let notFound = ["error": "Record not found"]
//
extension Router { //extension Router {
func allRoutes() { // func allRoutes() {
instanceRoutes() // instanceRoutes()
accountRoutes() // accountRoutes()
timelineRoutes() // timelineRoutes()
notificationRoutes() // notificationRoutes()
} // }
//
func instanceRoutes() { // func instanceRoutes() {
self["/api/v1/instance"] = JSONResponse(handler: { (_) in // self["/api/v1/instance"] = JSONResponse(handler: { (_) in
return [ // return [
"description": "An instance description", // "description": "An instance description",
"max_toot_chars": 500, // "max_toot_chars": 500,
"thumbnail": "http://localhost:8080/thumbnail.png", // "thumbnail": "http://localhost:8080/thumbnail.png",
"title": "Localhost", // "title": "Localhost",
"uri": "http://localhost:8080", // "uri": "http://localhost:8080",
"version": "2.7.2", // "version": "2.7.2",
"urls": [:] // "urls": [:]
] // ]
}) // })
} // }
//
func accountRoutes() { // func accountRoutes() {
let selfAccount: [String: Any] = [ // let selfAccount: [String: Any] = [
"id": "1", // "id": "1",
"username": "admin", // "username": "admin",
"acct": "admin", // "acct": "admin",
"display_name": "Admin Account", // "display_name": "Admin Account",
"locked": false, // "locked": false,
"created_at": "2019-12-31T11:13:42.0Z", // "created_at": "2019-12-31T11:13:42.0Z",
"followers_count": 0, // "followers_count": 0,
"following_count": 0, // "following_count": 0,
"statuses_count": 0, // "statuses_count": 0,
"note": "My profile description.", // "note": "My profile description.",
"url": "http://localhost:8080/users/admin", // "url": "http://localhost:8080/users/admin",
"avatar": "http://localhost:8080/avatar/admin.jpg", // "avatar": "http://localhost:8080/avatar/admin.jpg",
"avatar_static": "http://localhost:8080/avatar/admin.jpg", // "avatar_static": "http://localhost:8080/avatar/admin.jpg",
"header": "http://localhost:8080/header/admin.jpg", // "header": "http://localhost:8080/header/admin.jpg",
"header_static": "http://localhost:8080/header/admin.jpg", // "header_static": "http://localhost:8080/header/admin.jpg",
"emojis": [] // "emojis": []
] // ]
self["/api/v1/accounts/verify_credentials"] = JSONResponse(result: selfAccount) // self["/api/v1/accounts/verify_credentials"] = JSONResponse(result: selfAccount)
self["/api/v1/accounts/\\d+/statuses"] = JSONResponse(result: []) // self["/api/v1/accounts/\\d+/statuses"] = JSONResponse(result: [])
self["/api/v1/accounts/(\\d+)"] = DelegatingResponse { (ctx) in // self["/api/v1/accounts/(\\d+)"] = DelegatingResponse { (ctx) in
if ctx.captures[0] == "1" { // if ctx.captures[0] == "1" {
return JSONResponse(result: selfAccount) // return JSONResponse(result: selfAccount)
} else { // } else {
return JSONResponse(statusCode: 404, statusMessage: "Not Found", result: notFound) // return JSONResponse(statusCode: 404, statusMessage: "Not Found", result: notFound)
} // }
} // }
} // }
//
func timelineRoutes() { // func timelineRoutes() {
let emptyTimeline: [Any] = [] // let emptyTimeline: [Any] = []
self["/api/v1/timelines/home"] = JSONResponse(result: emptyTimeline) // self["/api/v1/timelines/home"] = JSONResponse(result: emptyTimeline)
self["/api/v1/timelines/public"] = JSONResponse(result: emptyTimeline) // self["/api/v1/timelines/public"] = JSONResponse(result: emptyTimeline)
} // }
//
func notificationRoutes() { // func notificationRoutes() {
let emptyTimeline: [Any] = [] // let emptyTimeline: [Any] = []
self["/api/v1/notifications"] = JSONResponse(result: emptyTimeline) // self["/api/v1/notifications"] = JSONResponse(result: emptyTimeline)
} // }
} //}

View File

@ -7,23 +7,23 @@
// //
import Foundation import Foundation
import Ambassador //import Ambassador
//
struct DelegatingResponse: WebApp { //struct DelegatingResponse: WebApp {
let handler: (_ ctx: Context) -> WebApp // let handler: (_ ctx: Context) -> WebApp
//
func app(_ environ: [String : Any], startResponse: @escaping ((String, [(String, String)]) -> Void), sendBody: @escaping ((Data) -> Void)) { // func app(_ environ: [String : Any], startResponse: @escaping ((String, [(String, String)]) -> Void), sendBody: @escaping ((Data) -> Void)) {
let ctx = Context(environ: environ) // let ctx = Context(environ: environ)
handler(ctx).app(environ, startResponse: startResponse, sendBody: sendBody) // handler(ctx).app(environ, startResponse: startResponse, sendBody: sendBody)
} // }
} //}
//
extension DelegatingResponse { //extension DelegatingResponse {
struct Context { // struct Context {
let environ: [String: Any] // let environ: [String: Any]
//
var captures: [String] { // var captures: [String] {
environ["ambassador.router_captures"] as? [String] ?? [] // environ["ambassador.router_captures"] as? [String] ?? []
} // }
} // }
} //}

View File

@ -7,12 +7,12 @@
// //
import Foundation import Foundation
import Ambassador //import Ambassador
//
extension JSONResponse { //extension JSONResponse {
init(statusCode: Int = 200, statusMessage: String = "OK", result: Any) { // init(statusCode: Int = 200, statusMessage: String = "OK", result: Any) {
self.init(statusCode: statusCode, statusMessage: statusMessage, handler: { (_) in // self.init(statusCode: statusCode, statusMessage: statusMessage, handler: { (_) in
return result // return result
}) // })
} // }
} //}

View File

@ -14,7 +14,7 @@ class ComposeTests: TuskerUITests {
override func setUp() { override func setUp() {
super.setUp() super.setUp()
router.allRoutes() // router.allRoutes()
app.launchEnvironment["UI_TESTING_LOGIN"] = "true" app.launchEnvironment["UI_TESTING_LOGIN"] = "true"
app.launch() app.launch()

View File

@ -13,7 +13,7 @@ class MyProfileTests: TuskerUITests {
override func setUp() { override func setUp() {
super.setUp() super.setUp()
router.allRoutes() // router.allRoutes()
app.launchEnvironment["UI_TESTING_LOGIN"] = "true" app.launchEnvironment["UI_TESTING_LOGIN"] = "true"
app.launch() app.launch()

View File

@ -7,14 +7,14 @@
// //
import XCTest import XCTest
import Ambassador //import Ambassador
class OnboardingTests: TuskerUITests { class OnboardingTests: TuskerUITests {
override func setUp() { override func setUp() {
super.setUp() super.setUp()
router.instanceRoutes() // router.instanceRoutes()
app.launch() app.launch()
} }

View File

@ -7,40 +7,40 @@
// //
import XCTest import XCTest
import Embassy //import Embassy
import Ambassador //import Ambassador
class TuskerUITests: XCTestCase { class TuskerUITests: XCTestCase {
var eventLoop: EventLoop! // var eventLoop: EventLoop!
var router: Router! // var router: Router!
var server: HTTPServer! // var server: HTTPServer!
var eventLoopThreadCondition: NSCondition! // var eventLoopThreadCondition: NSCondition!
var eventLoopThread: Thread! // var eventLoopThread: Thread!
var app: XCUIApplication! var app: XCUIApplication!
private func setupWebServer() { // private func setupWebServer() {
eventLoop = try! SelectorEventLoop(selector: try! KqueueSelector()) // eventLoop = try! SelectorEventLoop(selector: try! KqueueSelector())
router = Router() // router = Router()
server = DefaultHTTPServer(eventLoop: eventLoop, port: 8080, app: router.app) // server = DefaultHTTPServer(eventLoop: eventLoop, port: 8080, app: router.app)
router["/hello"] = JSONResponse(handler: { (_) in // router["/hello"] = JSONResponse(handler: { (_) in
return ["Hello", "World"] // return ["Hello", "World"]
}) // })
try! server.start() // try! server.start()
//
eventLoopThreadCondition = NSCondition() // eventLoopThreadCondition = NSCondition()
eventLoopThread = Thread(block: { // eventLoopThread = Thread(block: {
self.eventLoop.runForever() // self.eventLoop.runForever()
self.eventLoopThreadCondition.lock() // self.eventLoopThreadCondition.lock()
self.eventLoopThreadCondition.signal() // self.eventLoopThreadCondition.signal()
self.eventLoopThreadCondition.unlock() // self.eventLoopThreadCondition.unlock()
}) // })
eventLoopThread.start() // eventLoopThread.start()
} // }
override func setUp() { override func setUp() {
setupWebServer() // setupWebServer()
continueAfterFailure = false continueAfterFailure = false
app = XCUIApplication() app = XCUIApplication()
@ -48,14 +48,14 @@ class TuskerUITests: XCTestCase {
} }
override func tearDown() { override func tearDown() {
server.stopAndWait() // server.stopAndWait()
eventLoopThreadCondition.lock() // eventLoopThreadCondition.lock()
eventLoop.stop() // eventLoop.stop()
while eventLoop.running { // while eventLoop.running {
if !eventLoopThreadCondition.wait(until: Date(timeIntervalSinceNow: 10)) { // if !eventLoopThreadCondition.wait(until: Date(timeIntervalSinceNow: 10)) {
fatalError("Join eventLoopThread timeout") // fatalError("Join eventLoopThread timeout")
} // }
} // }
} }
} }