XCTest+AsyncAwait.swift 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. /*
  2. * Copyright 2021, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import XCTest
  17. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
  18. internal func XCTAssertThrowsError<T>(
  19. _ expression: @autoclosure () async throws -> T,
  20. verify: (Error) -> Void = { _ in },
  21. file: StaticString = #filePath,
  22. line: UInt = #line
  23. ) async {
  24. do {
  25. _ = try await expression()
  26. XCTFail("Expression did not throw error", file: file, line: line)
  27. } catch {
  28. verify(error)
  29. }
  30. }
  31. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
  32. internal func XCTAssertNoThrowAsync<T>(
  33. _ expression: @autoclosure () async throws -> T,
  34. file: StaticString = #filePath,
  35. line: UInt = #line
  36. ) async {
  37. do {
  38. _ = try await expression()
  39. } catch {
  40. XCTFail("Expression throw error '\(error)'", file: file, line: line)
  41. }
  42. }
  43. private enum TaskResult<Result> {
  44. case operation(Result)
  45. case cancellation
  46. }
  47. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
  48. func withTaskCancelledAfter<Result>(
  49. nanoseconds: UInt64,
  50. operation: @escaping @Sendable () async -> Result
  51. ) async throws {
  52. try await withThrowingTaskGroup(of: TaskResult<Result>.self) { group in
  53. group.addTask {
  54. return .operation(await operation())
  55. }
  56. group.addTask {
  57. try await Task.sleep(nanoseconds: nanoseconds)
  58. return .cancellation
  59. }
  60. // Only the sleeping task can throw if it's cancelled, in which case we want to throw.
  61. let firstResult = try await group.next()
  62. // A task completed, cancel the rest.
  63. group.cancelAll()
  64. // Check which task completed.
  65. switch firstResult {
  66. case .cancellation:
  67. () // Fine, what we expect.
  68. case .operation:
  69. XCTFail("Operation completed before cancellation")
  70. case .none:
  71. XCTFail("No tasks completed")
  72. }
  73. // Wait for the other task. The operation cannot, only the sleeping task can.
  74. try await group.waitForAll()
  75. }
  76. }