ProtobufCodeGeneratorTests.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. /*
  2. * Copyright 2024, 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. #if os(macOS) || os(Linux) // swift-format doesn't like canImport(Foundation.Process)
  17. import GRPCCodeGen
  18. import GRPCProtobufCodeGen
  19. import SwiftProtobuf
  20. import SwiftProtobufPluginLibrary
  21. import XCTest
  22. final class ProtobufCodeGeneratorTests: XCTestCase {
  23. func testProtobufCodeGenerator() throws {
  24. try testCodeGeneration(
  25. proto: Google_Protobuf_FileDescriptorProto.helloWorldNestedPackage,
  26. indentation: 4,
  27. visibility: .internal,
  28. client: true,
  29. server: false,
  30. expectedCode: """
  31. // Copyright 2015 gRPC authors.
  32. //
  33. // Licensed under the Apache License, Version 2.0 (the "License");
  34. // you may not use this file except in compliance with the License.
  35. // You may obtain a copy of the License at
  36. //
  37. // http://www.apache.org/licenses/LICENSE-2.0
  38. //
  39. // Unless required by applicable law or agreed to in writing, software
  40. // distributed under the License is distributed on an "AS IS" BASIS,
  41. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  42. // See the License for the specific language governing permissions and
  43. // limitations under the License.
  44. // DO NOT EDIT.
  45. // swift-format-ignore-file
  46. //
  47. // Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
  48. // Source: helloworld.proto
  49. //
  50. // For information on using the generated types, please see the documentation:
  51. // https://github.com/grpc/grpc-swift
  52. internal import GRPCCore
  53. internal import GRPCProtobuf
  54. internal import DifferentModule
  55. internal import ExtraModule
  56. internal enum Hello_World_Greeter {
  57. internal static let descriptor = GRPCCore.ServiceDescriptor.hello_world_Greeter
  58. internal enum Method {
  59. internal enum SayHello {
  60. internal typealias Input = Hello_World_HelloRequest
  61. internal typealias Output = Hello_World_HelloReply
  62. internal static let descriptor = GRPCCore.MethodDescriptor(
  63. service: Hello_World_Greeter.descriptor.fullyQualifiedService,
  64. method: "SayHello"
  65. )
  66. }
  67. internal static let descriptors: [GRPCCore.MethodDescriptor] = [
  68. SayHello.descriptor
  69. ]
  70. }
  71. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  72. internal typealias ClientProtocol = Hello_World_GreeterClientProtocol
  73. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  74. internal typealias Client = Hello_World_GreeterClient
  75. }
  76. extension GRPCCore.ServiceDescriptor {
  77. internal static let hello_world_Greeter = Self(
  78. package: "hello.world",
  79. service: "Greeter"
  80. )
  81. }
  82. /// The greeting service definition.
  83. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  84. internal protocol Hello_World_GreeterClientProtocol: Sendable {
  85. /// Sends a greeting.
  86. func sayHello<R>(
  87. request: GRPCCore.ClientRequest.Single<Hello_World_HelloRequest>,
  88. serializer: some GRPCCore.MessageSerializer<Hello_World_HelloRequest>,
  89. deserializer: some GRPCCore.MessageDeserializer<Hello_World_HelloReply>,
  90. options: GRPCCore.CallOptions,
  91. _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single<Hello_World_HelloReply>) async throws -> R
  92. ) async throws -> R where R: Sendable
  93. }
  94. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  95. extension Hello_World_Greeter.ClientProtocol {
  96. internal func sayHello<R>(
  97. request: GRPCCore.ClientRequest.Single<Hello_World_HelloRequest>,
  98. options: GRPCCore.CallOptions = .defaults,
  99. _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single<Hello_World_HelloReply>) async throws -> R = {
  100. try $0.message
  101. }
  102. ) async throws -> R where R: Sendable {
  103. try await self.sayHello(
  104. request: request,
  105. serializer: GRPCProtobuf.ProtobufSerializer<Hello_World_HelloRequest>(),
  106. deserializer: GRPCProtobuf.ProtobufDeserializer<Hello_World_HelloReply>(),
  107. options: options,
  108. body
  109. )
  110. }
  111. }
  112. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  113. extension Hello_World_Greeter.ClientProtocol {
  114. /// Sends a greeting.
  115. internal func sayHello<Result>(
  116. _ message: Hello_World_HelloRequest,
  117. metadata: GRPCCore.Metadata = [:],
  118. options: GRPCCore.CallOptions = .defaults,
  119. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<Hello_World_HelloReply>) async throws -> Result = {
  120. try $0.message
  121. }
  122. ) async throws -> Result where Result: Sendable {
  123. let request = GRPCCore.ClientRequest.Single<Hello_World_HelloRequest>(
  124. message: message,
  125. metadata: metadata
  126. )
  127. return try await self.sayHello(
  128. request: request,
  129. options: options,
  130. handleResponse
  131. )
  132. }
  133. }
  134. /// The greeting service definition.
  135. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  136. internal struct Hello_World_GreeterClient: Hello_World_Greeter.ClientProtocol {
  137. private let client: GRPCCore.GRPCClient
  138. internal init(wrapping client: GRPCCore.GRPCClient) {
  139. self.client = client
  140. }
  141. /// Sends a greeting.
  142. internal func sayHello<R>(
  143. request: GRPCCore.ClientRequest.Single<Hello_World_HelloRequest>,
  144. serializer: some GRPCCore.MessageSerializer<Hello_World_HelloRequest>,
  145. deserializer: some GRPCCore.MessageDeserializer<Hello_World_HelloReply>,
  146. options: GRPCCore.CallOptions = .defaults,
  147. _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single<Hello_World_HelloReply>) async throws -> R = {
  148. try $0.message
  149. }
  150. ) async throws -> R where R: Sendable {
  151. try await self.client.unary(
  152. request: request,
  153. descriptor: Hello_World_Greeter.Method.SayHello.descriptor,
  154. serializer: serializer,
  155. deserializer: deserializer,
  156. options: options,
  157. handler: body
  158. )
  159. }
  160. }
  161. """
  162. )
  163. try testCodeGeneration(
  164. proto: Google_Protobuf_FileDescriptorProto.helloWorld,
  165. indentation: 2,
  166. visibility: .public,
  167. client: false,
  168. server: true,
  169. expectedCode: """
  170. // Copyright 2015 gRPC authors.
  171. //
  172. // Licensed under the Apache License, Version 2.0 (the "License");
  173. // you may not use this file except in compliance with the License.
  174. // You may obtain a copy of the License at
  175. //
  176. // http://www.apache.org/licenses/LICENSE-2.0
  177. //
  178. // Unless required by applicable law or agreed to in writing, software
  179. // distributed under the License is distributed on an "AS IS" BASIS,
  180. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  181. // See the License for the specific language governing permissions and
  182. // limitations under the License.
  183. // DO NOT EDIT.
  184. // swift-format-ignore-file
  185. //
  186. // Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
  187. // Source: helloworld.proto
  188. //
  189. // For information on using the generated types, please see the documentation:
  190. // https://github.com/grpc/grpc-swift
  191. public import GRPCCore
  192. internal import GRPCProtobuf
  193. public import DifferentModule
  194. public import ExtraModule
  195. public enum Helloworld_Greeter {
  196. public static let descriptor = GRPCCore.ServiceDescriptor.helloworld_Greeter
  197. public enum Method {
  198. public enum SayHello {
  199. public typealias Input = Helloworld_HelloRequest
  200. public typealias Output = Helloworld_HelloReply
  201. public static let descriptor = GRPCCore.MethodDescriptor(
  202. service: Helloworld_Greeter.descriptor.fullyQualifiedService,
  203. method: "SayHello"
  204. )
  205. }
  206. public static let descriptors: [GRPCCore.MethodDescriptor] = [
  207. SayHello.descriptor
  208. ]
  209. }
  210. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  211. public typealias StreamingServiceProtocol = Helloworld_GreeterStreamingServiceProtocol
  212. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  213. public typealias ServiceProtocol = Helloworld_GreeterServiceProtocol
  214. }
  215. extension GRPCCore.ServiceDescriptor {
  216. public static let helloworld_Greeter = Self(
  217. package: "helloworld",
  218. service: "Greeter"
  219. )
  220. }
  221. /// The greeting service definition.
  222. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  223. public protocol Helloworld_GreeterStreamingServiceProtocol: GRPCCore.RegistrableRPCService {
  224. /// Sends a greeting.
  225. func sayHello(request: GRPCCore.ServerRequest.Stream<Helloworld_HelloRequest>) async throws -> GRPCCore.ServerResponse.Stream<Helloworld_HelloReply>
  226. }
  227. /// Conformance to `GRPCCore.RegistrableRPCService`.
  228. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  229. extension Helloworld_Greeter.StreamingServiceProtocol {
  230. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  231. public func registerMethods(with router: inout GRPCCore.RPCRouter) {
  232. router.registerHandler(
  233. forMethod: Helloworld_Greeter.Method.SayHello.descriptor,
  234. deserializer: GRPCProtobuf.ProtobufDeserializer<Helloworld_HelloRequest>(),
  235. serializer: GRPCProtobuf.ProtobufSerializer<Helloworld_HelloReply>(),
  236. handler: { request in
  237. try await self.sayHello(request: request)
  238. }
  239. )
  240. }
  241. }
  242. /// The greeting service definition.
  243. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  244. public protocol Helloworld_GreeterServiceProtocol: Helloworld_Greeter.StreamingServiceProtocol {
  245. /// Sends a greeting.
  246. func sayHello(request: GRPCCore.ServerRequest.Single<Helloworld_HelloRequest>) async throws -> GRPCCore.ServerResponse.Single<Helloworld_HelloReply>
  247. }
  248. /// Partial conformance to `Helloworld_GreeterStreamingServiceProtocol`.
  249. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  250. extension Helloworld_Greeter.ServiceProtocol {
  251. public func sayHello(request: GRPCCore.ServerRequest.Stream<Helloworld_HelloRequest>) async throws -> GRPCCore.ServerResponse.Stream<Helloworld_HelloReply> {
  252. let response = try await self.sayHello(request: GRPCCore.ServerRequest.Single(stream: request))
  253. return GRPCCore.ServerResponse.Stream(single: response)
  254. }
  255. }
  256. """
  257. )
  258. try testCodeGeneration(
  259. proto: Google_Protobuf_FileDescriptorProto.helloWorldEmptyPackage,
  260. indentation: 2,
  261. visibility: .package,
  262. client: true,
  263. server: true,
  264. expectedCode: """
  265. // Copyright 2015 gRPC authors.
  266. //
  267. // Licensed under the Apache License, Version 2.0 (the "License");
  268. // you may not use this file except in compliance with the License.
  269. // You may obtain a copy of the License at
  270. //
  271. // http://www.apache.org/licenses/LICENSE-2.0
  272. //
  273. // Unless required by applicable law or agreed to in writing, software
  274. // distributed under the License is distributed on an "AS IS" BASIS,
  275. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  276. // See the License for the specific language governing permissions and
  277. // limitations under the License.
  278. // DO NOT EDIT.
  279. // swift-format-ignore-file
  280. //
  281. // Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
  282. // Source: helloworld.proto
  283. //
  284. // For information on using the generated types, please see the documentation:
  285. // https://github.com/grpc/grpc-swift
  286. package import GRPCCore
  287. internal import GRPCProtobuf
  288. package import DifferentModule
  289. package import ExtraModule
  290. package enum Greeter {
  291. package static let descriptor = GRPCCore.ServiceDescriptor.Greeter
  292. package enum Method {
  293. package enum SayHello {
  294. package typealias Input = HelloRequest
  295. package typealias Output = HelloReply
  296. package static let descriptor = GRPCCore.MethodDescriptor(
  297. service: Greeter.descriptor.fullyQualifiedService,
  298. method: "SayHello"
  299. )
  300. }
  301. package static let descriptors: [GRPCCore.MethodDescriptor] = [
  302. SayHello.descriptor
  303. ]
  304. }
  305. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  306. package typealias StreamingServiceProtocol = GreeterStreamingServiceProtocol
  307. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  308. package typealias ServiceProtocol = GreeterServiceProtocol
  309. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  310. package typealias ClientProtocol = GreeterClientProtocol
  311. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  312. package typealias Client = GreeterClient
  313. }
  314. extension GRPCCore.ServiceDescriptor {
  315. package static let Greeter = Self(
  316. package: "",
  317. service: "Greeter"
  318. )
  319. }
  320. /// The greeting service definition.
  321. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  322. package protocol GreeterStreamingServiceProtocol: GRPCCore.RegistrableRPCService {
  323. /// Sends a greeting.
  324. func sayHello(request: GRPCCore.ServerRequest.Stream<HelloRequest>) async throws -> GRPCCore.ServerResponse.Stream<HelloReply>
  325. }
  326. /// Conformance to `GRPCCore.RegistrableRPCService`.
  327. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  328. extension Greeter.StreamingServiceProtocol {
  329. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  330. package func registerMethods(with router: inout GRPCCore.RPCRouter) {
  331. router.registerHandler(
  332. forMethod: Greeter.Method.SayHello.descriptor,
  333. deserializer: GRPCProtobuf.ProtobufDeserializer<HelloRequest>(),
  334. serializer: GRPCProtobuf.ProtobufSerializer<HelloReply>(),
  335. handler: { request in
  336. try await self.sayHello(request: request)
  337. }
  338. )
  339. }
  340. }
  341. /// The greeting service definition.
  342. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  343. package protocol GreeterServiceProtocol: Greeter.StreamingServiceProtocol {
  344. /// Sends a greeting.
  345. func sayHello(request: GRPCCore.ServerRequest.Single<HelloRequest>) async throws -> GRPCCore.ServerResponse.Single<HelloReply>
  346. }
  347. /// Partial conformance to `GreeterStreamingServiceProtocol`.
  348. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  349. extension Greeter.ServiceProtocol {
  350. package func sayHello(request: GRPCCore.ServerRequest.Stream<HelloRequest>) async throws -> GRPCCore.ServerResponse.Stream<HelloReply> {
  351. let response = try await self.sayHello(request: GRPCCore.ServerRequest.Single(stream: request))
  352. return GRPCCore.ServerResponse.Stream(single: response)
  353. }
  354. }
  355. /// The greeting service definition.
  356. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  357. package protocol GreeterClientProtocol: Sendable {
  358. /// Sends a greeting.
  359. func sayHello<R>(
  360. request: GRPCCore.ClientRequest.Single<HelloRequest>,
  361. serializer: some GRPCCore.MessageSerializer<HelloRequest>,
  362. deserializer: some GRPCCore.MessageDeserializer<HelloReply>,
  363. options: GRPCCore.CallOptions,
  364. _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single<HelloReply>) async throws -> R
  365. ) async throws -> R where R: Sendable
  366. }
  367. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  368. extension Greeter.ClientProtocol {
  369. package func sayHello<R>(
  370. request: GRPCCore.ClientRequest.Single<HelloRequest>,
  371. options: GRPCCore.CallOptions = .defaults,
  372. _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single<HelloReply>) async throws -> R = {
  373. try $0.message
  374. }
  375. ) async throws -> R where R: Sendable {
  376. try await self.sayHello(
  377. request: request,
  378. serializer: GRPCProtobuf.ProtobufSerializer<HelloRequest>(),
  379. deserializer: GRPCProtobuf.ProtobufDeserializer<HelloReply>(),
  380. options: options,
  381. body
  382. )
  383. }
  384. }
  385. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  386. extension Greeter.ClientProtocol {
  387. /// Sends a greeting.
  388. package func sayHello<Result>(
  389. _ message: HelloRequest,
  390. metadata: GRPCCore.Metadata = [:],
  391. options: GRPCCore.CallOptions = .defaults,
  392. onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<HelloReply>) async throws -> Result = {
  393. try $0.message
  394. }
  395. ) async throws -> Result where Result: Sendable {
  396. let request = GRPCCore.ClientRequest.Single<HelloRequest>(
  397. message: message,
  398. metadata: metadata
  399. )
  400. return try await self.sayHello(
  401. request: request,
  402. options: options,
  403. handleResponse
  404. )
  405. }
  406. }
  407. /// The greeting service definition.
  408. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  409. package struct GreeterClient: Greeter.ClientProtocol {
  410. private let client: GRPCCore.GRPCClient
  411. package init(wrapping client: GRPCCore.GRPCClient) {
  412. self.client = client
  413. }
  414. /// Sends a greeting.
  415. package func sayHello<R>(
  416. request: GRPCCore.ClientRequest.Single<HelloRequest>,
  417. serializer: some GRPCCore.MessageSerializer<HelloRequest>,
  418. deserializer: some GRPCCore.MessageDeserializer<HelloReply>,
  419. options: GRPCCore.CallOptions = .defaults,
  420. _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single<HelloReply>) async throws -> R = {
  421. try $0.message
  422. }
  423. ) async throws -> R where R: Sendable {
  424. try await self.client.unary(
  425. request: request,
  426. descriptor: Greeter.Method.SayHello.descriptor,
  427. serializer: serializer,
  428. deserializer: deserializer,
  429. options: options,
  430. handler: body
  431. )
  432. }
  433. }
  434. """
  435. )
  436. }
  437. func testCodeGeneration(
  438. proto: Google_Protobuf_FileDescriptorProto,
  439. indentation: Int,
  440. visibility: SourceGenerator.Configuration.AccessLevel,
  441. client: Bool,
  442. server: Bool,
  443. expectedCode: String,
  444. file: StaticString = #filePath,
  445. line: UInt = #line
  446. ) throws {
  447. let configs = SourceGenerator.Configuration(
  448. accessLevel: visibility,
  449. client: client,
  450. server: server,
  451. indentation: indentation
  452. )
  453. let descriptorSet = DescriptorSet(
  454. protos: [
  455. Google_Protobuf_FileDescriptorProto(name: "same-module.proto", package: "same-package"),
  456. Google_Protobuf_FileDescriptorProto(
  457. name: "different-module.proto",
  458. package: "different-package"
  459. ),
  460. proto,
  461. ])
  462. guard let fileDescriptor = descriptorSet.fileDescriptor(named: "helloworld.proto") else {
  463. return XCTFail(
  464. """
  465. Could not find the file descriptor of "helloworld.proto".
  466. """
  467. )
  468. }
  469. let moduleMappings = SwiftProtobuf_GenSwift_ModuleMappings.with {
  470. $0.mapping = [
  471. SwiftProtobuf_GenSwift_ModuleMappings.Entry.with {
  472. $0.protoFilePath = ["different-module.proto"]
  473. $0.moduleName = "DifferentModule"
  474. }
  475. ]
  476. }
  477. let generator = ProtobufCodeGenerator(configuration: configs)
  478. try XCTAssertEqualWithDiff(
  479. try generator.generateCode(
  480. from: fileDescriptor,
  481. protoFileModuleMappings: ProtoFileToModuleMappings(moduleMappingsProto: moduleMappings),
  482. extraModuleImports: ["ExtraModule"]
  483. ),
  484. expectedCode,
  485. file: file,
  486. line: line
  487. )
  488. }
  489. }
  490. private func diff(expected: String, actual: String) throws -> String {
  491. let process = Process()
  492. process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
  493. process.arguments = [
  494. "bash", "-c",
  495. "diff -U5 --label=expected <(echo '\(expected)') --label=actual <(echo '\(actual)')",
  496. ]
  497. let pipe = Pipe()
  498. process.standardOutput = pipe
  499. try process.run()
  500. process.waitUntilExit()
  501. let pipeData = try XCTUnwrap(
  502. pipe.fileHandleForReading.readToEnd(),
  503. """
  504. No output from command:
  505. \(process.executableURL!.path) \(process.arguments!.joined(separator: " "))
  506. """
  507. )
  508. return String(decoding: pipeData, as: UTF8.self)
  509. }
  510. internal func XCTAssertEqualWithDiff(
  511. _ actual: String,
  512. _ expected: String,
  513. file: StaticString = #filePath,
  514. line: UInt = #line
  515. ) throws {
  516. if actual == expected { return }
  517. XCTFail(
  518. """
  519. XCTAssertEqualWithDiff failed (click for diff)
  520. \(try diff(expected: expected, actual: actual))
  521. """,
  522. file: file,
  523. line: line
  524. )
  525. }
  526. #endif // os(macOS) || os(Linux)