Compare commits

...

4 Commits

Author SHA1 Message Date
Shadowfacts 46db70d58b Fix building in release mode
When handleEvent dispatches to the other methods, it crashes the compiler
during an optimization pass. Seems to be related to:
https://github.com/apple/swift/issues/61350
2022-10-08 11:45:02 -04:00
Shadowfacts 21958eb77f Merge branch 'develop' into collection-timelines 2022-10-08 11:01:19 -04:00
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
12 changed files with 249 additions and 183 deletions

View File

@ -37,6 +37,7 @@
D61A45E828DF477D002BE511 /* LoadingCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61A45E728DF477D002BE511 /* LoadingCollectionViewCell.swift */; }; D61A45E828DF477D002BE511 /* LoadingCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61A45E728DF477D002BE511 /* LoadingCollectionViewCell.swift */; };
D61A45EA28DF51EE002BE511 /* TimelineLikeCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61A45E928DF51EE002BE511 /* TimelineLikeCollectionViewController.swift */; }; D61A45EA28DF51EE002BE511 /* TimelineLikeCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61A45E928DF51EE002BE511 /* TimelineLikeCollectionViewController.swift */; };
D61ABEF628EE74D400B29151 /* StatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61ABEF528EE74D400B29151 /* StatusCollectionViewCell.swift */; }; D61ABEF628EE74D400B29151 /* StatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61ABEF528EE74D400B29151 /* StatusCollectionViewCell.swift */; };
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 */; };
@ -214,8 +215,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 */; };
D6ADB6E828E8C878009924AB /* PublicTimelineDescriptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ADB6E728E8C878009924AB /* PublicTimelineDescriptionCollectionViewCell.swift */; }; D6ADB6E828E8C878009924AB /* PublicTimelineDescriptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ADB6E728E8C878009924AB /* PublicTimelineDescriptionCollectionViewCell.swift */; };
D6ADB6EA28E91C30009924AB /* TimelineStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ADB6E928E91C30009924AB /* TimelineStatusCollectionViewCell.swift */; }; D6ADB6EA28E91C30009924AB /* TimelineStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ADB6E928E91C30009924AB /* TimelineStatusCollectionViewCell.swift */; };
D6ADB6EC28EA73CB009924AB /* StatusContentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ADB6EB28EA73CB009924AB /* StatusContentContainer.swift */; }; D6ADB6EC28EA73CB009924AB /* StatusContentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ADB6EB28EA73CB009924AB /* StatusContentContainer.swift */; };
@ -695,8 +694,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;
}; };
@ -1546,6 +1544,7 @@
); );
name = TuskerUITests; name = TuskerUITests;
packageProductDependencies = ( packageProductDependencies = (
D61ABEFB28F105DE00B29151 /* Pachyderm */,
); );
productName = TuskerUITests; productName = TuskerUITests;
productReference = D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */; productReference = D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */;
@ -2217,7 +2216,6 @@
MARKETING_VERSION = 2022.1; MARKETING_VERSION = 2022.1;
OTHER_CODE_SIGN_FLAGS = ""; OTHER_CODE_SIGN_FLAGS = "";
OTHER_LDFLAGS = ""; OTHER_LDFLAGS = "";
"OTHER_SWIFT_FLAGS[sdk=iphone*14*]" = "$(inherited) -D SDK_IOS_14";
PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker; PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -2246,7 +2244,6 @@
); );
MARKETING_VERSION = 2022.1; MARKETING_VERSION = 2022.1;
OTHER_CODE_SIGN_FLAGS = ""; OTHER_CODE_SIGN_FLAGS = "";
"OTHER_SWIFT_FLAGS[sdk=iphone*14*]" = "$(inherited) -D SDK_IOS_14";
PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker; PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -2262,14 +2259,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";
@ -2283,14 +2281,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";
@ -2303,14 +2302,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";
@ -2323,14 +2322,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";
@ -2483,6 +2482,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)) {
// if we can't acquire the lock after 1 second, something has gone catastrophically wrong
fatalError()
} }
func withLock<R>(_ body: (inout State) throws -> R) rethrows -> R where R: Sendable { defer { lock.unlock() }
os_unfair_lock_lock(lock)
defer { os_unfair_lock_unlock(lock) }
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

@ -63,27 +63,6 @@ extension TimelineLikeCollectionViewController {
} }
} }
func handleEvent(_ event: TimelineLikeController<TimelineItem>.Event) async {
switch event {
case .addLoadingIndicator:
await handleAddLoadingIndicator()
case .removeLoadingIndicator:
await handleRemoveLoadingIndicator()
case .loadAllError(let error, _):
await handleLoadAllError(error)
case .replaceAllItems(let items, _):
await handleReplaceAllItems(items)
case .loadNewerError(let error, _):
await handleLoadNewerError(error)
case .prependItems(let items, _):
await handlePrependItems(items)
case .loadOlderError(let error, _):
await handleLoadOlderError(error)
case .appendItems(let items, _):
await handleAppendItems(items)
}
}
func handleAddLoadingIndicator() async { func handleAddLoadingIndicator() async {
var snapshot = dataSource.snapshot() var snapshot = dataSource.snapshot()
if !snapshot.sectionIdentifiers.contains(.footer) { if !snapshot.sectionIdentifiers.contains(.footer) {

View File

@ -20,7 +20,14 @@ protocol TimelineLikeControllerDelegate<TimelineItem>: AnyObject {
func canLoadOlder() async -> Bool func canLoadOlder() async -> Bool
func handleEvent(_ event: TimelineLikeController<TimelineItem>.Event) async func handleAddLoadingIndicator() async
func handleRemoveLoadingIndicator() async
func handleLoadAllError(_ error: Swift.Error) async
func handleReplaceAllItems(_ timelineItems: [TimelineItem]) async
func handleLoadNewerError(_ error: Swift.Error) async
func handlePrependItems(_ timelineItems: [TimelineItem]) async
func handleLoadOlderError(_ error: Swift.Error) async
func handleAppendItems(_ timelineItems: [TimelineItem]) async
} }
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "TimelineLikeController") private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "TimelineLikeController")
@ -112,7 +119,24 @@ actor TimelineLikeController<Item> {
private func emit(event: Event) async { private func emit(event: Event) async {
precondition(state.canEmit(event: event)) precondition(state.canEmit(event: event))
await delegate.handleEvent(event) switch event {
case .addLoadingIndicator:
await delegate.handleAddLoadingIndicator()
case .removeLoadingIndicator:
await delegate.handleRemoveLoadingIndicator()
case .loadAllError(let error, _):
await delegate.handleLoadAllError(error)
case .replaceAllItems(let items, _):
await delegate.handleReplaceAllItems(items)
case .loadNewerError(let error, _):
await delegate.handleLoadNewerError(error)
case .prependItems(let items, _):
await delegate.handlePrependItems(items)
case .loadOlderError(let error, _):
await delegate.handleLoadOlderError(error)
case .appendItems(let items, _):
await delegate.handleAppendItems(items)
}
} }
enum State: Equatable, CustomDebugStringConvertible { enum State: Equatable, CustomDebugStringConvertible {

View File

@ -31,4 +31,44 @@ class TuskerTests: XCTestCase {
} }
} }
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")
} // }
} // }
} }
} }