KaneCheshire / Peasy

A pure Swift mock server for embedding and running directly within iOS/macOS UI tests. Easy peasy.
MIT License
38 stars 10 forks source link

Throw errors during start instead of using fatalError #20

Closed Jon889 closed 4 years ago

Jon889 commented 4 years ago

We run our UITests in parallel simulators so having it fatally error when the port is already in use doesn't work. Instead if we can catch the error then we can try on the next port. eg:

public static func start() {
    for i in 0..<10 {
        do {
            try server.start(port: 8090 + i)
            break
        } catch {}
    }
    guard let port = server.port else {
        fatalError("Couldn't start UI Test server")
    }
    print("Mock server started on port \(port)")
}

Also storing the port allows it to be sent to the app being tested through an environment variable.

If you have a better or existing way of using it with parallel simulators that would be great :)

KaneCheshire commented 4 years ago

Btw we also use parallel tests, what we do for now is to just use the clone count number from the simulator name to decide what port to use (clone 1 is 8881 etc).

Not quite as robust as this but meant we didn't have to worry too much for now while this gets figured out

KaneCheshire commented 4 years ago

I wonder if returning a Result<Int, Error> from start() would be a good idea? Then we could mark start() with @discardableResult for people who don't care about it, but also don't force people to try!. The only downside really is that if it fails, it'll silently fail

Jon889 commented 4 years ago

Btw we also use parallel tests, what we do for now is to just use the clone count number from the simulator name to decide what port to use (clone 1 is 8881 etc)

Ah that is better I think, I didn't even think the device name would have the "clone #1" bit in it. I was looking for an environment variable or something :)

KaneCheshire commented 4 years ago

You can do something like this in the interim:

private extension ProcessInfo {

    static let simulatorName: String = ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] ?? ""
    static let cloneNumber: Int = {
        guard let rangeOfFirstDigit = simulatorName.rangeOfCharacter(from: .decimalDigits) else { return 0 }
        let digitString = simulatorName[rangeOfFirstDigit]
        return Int(digitString) ?? 0
    }()

}

private extension UInt16 {

    static let port: UInt16 = {
        return 8880 + UInt16(ProcessInfo.cloneNumber)
    }()

}
Jon889 commented 4 years ago

Thanks for the interim code :)

I wonder if returning a Result<Int, Error> from start() would be a good idea? Then we could mark start() with @discardableResult for people who don't care about it, but also don't force people to try!. The only downside really is that if it fails, it'll silently fail

~Instead of @discardableResult there could be a second function that has void return type and then if the user ignores the return result it will fatalError.~ Apparently not, that is ambiguous :(

Jon889 commented 4 years ago

Another option that more specifically solves this issue would be to be able to pass in a range of ports and Peasy will go through them. It would keep the API small, but would mean the port needs to be returned from start

I've added a commit to the PR to show what it could like. I kept the port: parameter name singular to indicate only on of the passed in ports will be used, and used Collection so that any finite range or array can be passed in.

KaneCheshire commented 4 years ago

Closing in favour of #28