XCTestExpectation on Linux

What is the proper way to use XCTestExpectation on Linux? I'm working on a project that supports both macOS and Linux and I have an asynchronous test that compiles and runs on Mac, but fails to compile on Linux.

class BindingTests: XCTestCase {
      func testAccepting() {
          let socket = SocketPath("/tmp/com.trailblazer.sock")!
  
          let binding: Binding
          do {
              binding = try socket.bind()
          } catch {
              XCTFail("Failed to bind to socket with error \(error)")
              return
          }
  
          XCTAssertNoThrow(try binding.listen(maxQueued: 1))
  
          let acceptConnection = XCTestExpectation(description: "Ensure connection is properly accepted")
  
          DispatchQueue.global(qos: .background).async {
              do {
                  try binding.accept() { _ in
                      acceptConnection.fulfill()
                  }
              } catch {}
          }
  
          XCTAssertNoThrow(try socket.connect(type: TCPSocket.self))
  
          XCTAssertEqual(XCTWaiter.wait(for: [acceptConnection], timeout: 5.0), .completed)
      }
  }

I get the following compilation errors on Linux:

error: cannot invoke initializer for type 'XCTestExpectation' with an argument list of type '(description: String)'
       expected an argument list of type '(description: String, file: StaticString, line: Int, testCase: XCTestCase)'

and

error: use of unresolved identifier 'XCTWaiter'

If I change XCTestExpectation to initialize like this:

let acceptConnection = XCTestExpectation(description: "Ensure connection is properly accepted", file: #file, line: #line, testCase: self)

It fails to compile with this error instead:

error: 'XCTestExpectation' initializer is inaccessible due to 'internal' protection level

I apparently can't trust the online documentation on XCTestExpectation since it must only be valid for Apple platforms?

I dug through a bit of Swift/SwiftPM source code but I'm not really sure where is the "correct" place to look at and all the places where I did find XCTestExpectation had the initializer declared public (with default values) so I'm really confused as to why it works on my Mac but not Linux. There were also some additional helper methods for adding expectation to an XCTestCase, but none of them compiled on my mac.

I don't think we have XCTWaiter on Linux. /cc @Honza_Dvorsky

As of a couple of weeks ago, we do: [SR-7615] Implement XCTWaiter and missing XCTestExpectation APIs by stmontgomery · Pull Request #228 · apple/swift-corelibs-xctest · GitHub

1 Like

So does this basically mean I just can't use XCTestExpectation on linux until the next swift release?

I have a few projects shared between Mac and Linux that use the XCTestCase.expectation(description:) and XCTestCase.wait(for:timeout:) methods to do what XCTestExpectation and XCTWaiter offer.

1 Like

@zwaldowski would you happen to have an example of how to use the XCTestCase.wait? Using both XCTestCase.wait(for: ..., timeout: ...) and self.wait(for: ..., timeout...) result in a has no member 'wait' error.

I did manager to get the expectation created on Linux though, so thank you for that pointer! :slight_smile:

would you mind sharing how to use them on Linux? I have the following code

import Foundation
import XCTest

class Test : XCTestCase{
  static public  func test_taskError() {
        let urlString = "http://127.0.0.1:-1/Nepal"
        let url = URL(string: urlString)!
        let session = URLSession(configuration: URLSessionConfiguration.default,
                                 delegate: nil,
                                 delegateQueue: nil)
        var completionExpectation = expectation(description: "GET \(urlString): Bad URL error")
        let task = session.dataTask(with: url) { (_, _, result) in
            let error = result as? URLError
            XCTAssertNotNil(error)
            XCTAssertEqual(error?.code, .badURL)
            completionExpectation.fulfill()
        }
        //should result in Bad URL error
        task.resume()

        waitForExpectations(timeout: 5) { error in
            XCTAssertNil(error)

            XCTAssertNotNil(task.error)
            XCTAssertEqual((task.error as? URLError)?.code, .badURL)
        }
  }
}

Test.test_taskError()

However it's giving me the following error:

simple.swift:12:37: error: instance member 'expectation' cannot be used on type 'Test'
var completionExpectation = expectation(description: "GET (urlString): Bad URL error")
^~~~~~~~~~~
simple.swift:22:9: error: instance member 'waitForExpectations' cannot be used on type 'Test'
waitForExpectations(timeout: 5) { error in

Not sure if it helps, but Nimble has built-in support for async expectations. We use it in some places and seem to have no problems with it on Linux.