Subito-it / SBTUITestTunnel

Enable network mocks and more in UI Tests

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

SBTProxyURLProtocol calls completionHandler in the main thread

fermoya opened this issue · comments

Hi, I've noticed that SBTProxyURLProtocol.startLoading returns the response for the subbed request in the main thread:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(stubbingResponseTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    ....
}

Can we use dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) or any other queue that's not the main thread instead (or at least allow customization)? There are scenarios in my suite where I mock HW by using mocks within the main target whose values are customized from a UI-Test. For this, I use dummy http requests. The mocked functions can be async or sync. In order for the sync methods to work I need to block the main thread. However the test hangs indefinitely because the main thread is blocked and SBTProxyURLProtocol tries to return the data into the main queue.

I don't think the change I suggest breaks anything and I think is more accurate as you'd expect the response to come from a background thread instead of the main thread.

Modifying that queue would likely lead to thread-safety issues, so I would recommend against such changes. Instead of blocking the main thread with a semaphore, consider using the run loop (specifically, [NSRunLoop.mainRunLoop runUntilDate:]). While this approach isn't ideal, it could serve as a viable workaround.

Modifying that queue would likely lead to thread-safety issues
startLoading is already called in a different thread than it's then dispatching the response (in the main queue now) so I don't understand why changing to a different queue would introduce any issue. At the very least I think it should use dispatch_get_current_queue.

consider using the run loop
how would you do that? I do something like this:

func foo<Output>(
        for call: @escaping (@escaping (Output) -> Void) -> Any
    ) -> Output {
        let completionQueue = DispatchQueue(label: "com.test.UITest", qos: .background)

        var output: Output?
        completionQueue.async {
            call { opResult in
                output = opResult
            }
        }

        RunLoop.main.run(until: Date(timeIntervalSinceNow: 10))

        guard let output else {
            fatalError("Shouldn't time out")
        }

        return output
    }

But same difference, UI hangs for 10 seconds until it fails. Main thread is blocked so SBTProxyURLProtocol doesn't get called

You need to invoke the RunLoop.main.run repeatedly until you detect that your operation is completed. There are a few examples in the tunnel codebase to take a look at.

@tcamin from what I see, it's really easy to enter a deadlock (see here), which I think it's my case and it's not something very difficult to achieve.

I still don't understand why SBTProxyURLProtocol.startLoading has to be dispatched in the main queue. I've tried replacing the source code myself and it works just fine if a different queue is used instead