SnippetBasedTranslatorTests.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. /*
  2. * Copyright 2023, 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. @testable import GRPCCodeGen
  18. final class SnippetBasedTranslatorTests: XCTestCase {
  19. typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor
  20. typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor
  21. func testTypealiasTranslator() throws {
  22. let method = MethodDescriptor(
  23. documentation: "Documentation for MethodA",
  24. name: "MethodA",
  25. isInputStreaming: false,
  26. isOutputStreaming: false,
  27. inputType: "NamespaceA_ServiceARequest",
  28. outputType: "NamespaceA_ServiceAResponse"
  29. )
  30. let service = ServiceDescriptor(
  31. documentation: "Documentation for ServiceA",
  32. name: "ServiceA",
  33. namespace: "namespaceA",
  34. methods: [method]
  35. )
  36. let expectedSwift =
  37. """
  38. enum namespaceA {
  39. enum ServiceA {
  40. enum Methods {
  41. enum MethodA {
  42. typealias Input = NamespaceA_ServiceARequest
  43. typealias Output = NamespaceA_ServiceAResponse
  44. static let descriptor = MethodDescriptor(
  45. service: "namespaceA.ServiceA",
  46. method: "MethodA"
  47. )
  48. }
  49. }
  50. static let methods: [MethodDescriptor] = [
  51. Methods.MethodA.descriptor
  52. ]
  53. typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol
  54. typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol
  55. }
  56. }
  57. """
  58. try self.assertTypealiasTranslation(
  59. codeGenerationRequest: self.makeCodeGenerationRequest(services: [service]),
  60. expectedSwift: expectedSwift
  61. )
  62. }
  63. func testTypealiasTranslatorEmptyNamespace() throws {
  64. let method = MethodDescriptor(
  65. documentation: "Documentation for MethodA",
  66. name: "MethodA",
  67. isInputStreaming: false,
  68. isOutputStreaming: false,
  69. inputType: "ServiceARequest",
  70. outputType: "ServiceAResponse"
  71. )
  72. let service = ServiceDescriptor(
  73. documentation: "Documentation for ServiceA",
  74. name: "ServiceA",
  75. namespace: "",
  76. methods: [method]
  77. )
  78. let expectedSwift =
  79. """
  80. enum ServiceA {
  81. enum Methods {
  82. enum MethodA {
  83. typealias Input = ServiceARequest
  84. typealias Output = ServiceAResponse
  85. static let descriptor = MethodDescriptor(
  86. service: "ServiceA",
  87. method: "MethodA"
  88. )
  89. }
  90. }
  91. static let methods: [MethodDescriptor] = [
  92. Methods.MethodA.descriptor
  93. ]
  94. typealias StreamingServiceProtocol = ServiceAServiceStreamingProtocol
  95. typealias ServiceProtocol = ServiceAServiceProtocol
  96. }
  97. """
  98. try self.assertTypealiasTranslation(
  99. codeGenerationRequest: self.makeCodeGenerationRequest(services: [service]),
  100. expectedSwift: expectedSwift
  101. )
  102. }
  103. func testTypealiasTranslatorCheckMethodsOrder() throws {
  104. let methodA = MethodDescriptor(
  105. documentation: "Documentation for MethodA",
  106. name: "MethodA",
  107. isInputStreaming: false,
  108. isOutputStreaming: false,
  109. inputType: "NamespaceA_ServiceARequest",
  110. outputType: "NamespaceA_ServiceAResponse"
  111. )
  112. let methodB = MethodDescriptor(
  113. documentation: "Documentation for MethodB",
  114. name: "MethodB",
  115. isInputStreaming: false,
  116. isOutputStreaming: false,
  117. inputType: "NamespaceA_ServiceARequest",
  118. outputType: "NamespaceA_ServiceAResponse"
  119. )
  120. let service = ServiceDescriptor(
  121. documentation: "Documentation for ServiceA",
  122. name: "ServiceA",
  123. namespace: "namespaceA",
  124. methods: [methodA, methodB]
  125. )
  126. let expectedSwift =
  127. """
  128. enum namespaceA {
  129. enum ServiceA {
  130. enum Methods {
  131. enum MethodA {
  132. typealias Input = NamespaceA_ServiceARequest
  133. typealias Output = NamespaceA_ServiceAResponse
  134. static let descriptor = MethodDescriptor(
  135. service: "namespaceA.ServiceA",
  136. method: "MethodA"
  137. )
  138. }
  139. enum MethodB {
  140. typealias Input = NamespaceA_ServiceARequest
  141. typealias Output = NamespaceA_ServiceAResponse
  142. static let descriptor = MethodDescriptor(
  143. service: "namespaceA.ServiceA",
  144. method: "MethodB"
  145. )
  146. }
  147. }
  148. static let methods: [MethodDescriptor] = [
  149. Methods.MethodA.descriptor,
  150. Methods.MethodB.descriptor
  151. ]
  152. typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol
  153. typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol
  154. }
  155. }
  156. """
  157. try self.assertTypealiasTranslation(
  158. codeGenerationRequest: self.makeCodeGenerationRequest(services: [service]),
  159. expectedSwift: expectedSwift
  160. )
  161. }
  162. func testTypealiasTranslatorNoMethodsService() throws {
  163. let service = ServiceDescriptor(
  164. documentation: "Documentation for ServiceA",
  165. name: "ServiceA",
  166. namespace: "namespaceA",
  167. methods: []
  168. )
  169. let expectedSwift =
  170. """
  171. enum namespaceA {
  172. enum ServiceA {
  173. enum Methods {}
  174. static let methods: [MethodDescriptor] = []
  175. typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol
  176. typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol
  177. }
  178. }
  179. """
  180. try self.assertTypealiasTranslation(
  181. codeGenerationRequest: self.makeCodeGenerationRequest(services: [service]),
  182. expectedSwift: expectedSwift
  183. )
  184. }
  185. func testTypealiasTranslatorServiceAlphabeticalOrder() throws {
  186. let serviceB = ServiceDescriptor(
  187. documentation: "Documentation for BService",
  188. name: "BService",
  189. namespace: "namespacea",
  190. methods: []
  191. )
  192. let serviceA = ServiceDescriptor(
  193. documentation: "Documentation for AService",
  194. name: "AService",
  195. namespace: "namespacea",
  196. methods: []
  197. )
  198. let expectedSwift =
  199. """
  200. enum namespacea {
  201. enum AService {
  202. enum Methods {}
  203. static let methods: [MethodDescriptor] = []
  204. typealias StreamingServiceProtocol = namespacea_AServiceServiceStreamingProtocol
  205. typealias ServiceProtocol = namespacea_AServiceServiceProtocol
  206. }
  207. enum BService {
  208. enum Methods {}
  209. static let methods: [MethodDescriptor] = []
  210. typealias StreamingServiceProtocol = namespacea_BServiceServiceStreamingProtocol
  211. typealias ServiceProtocol = namespacea_BServiceServiceProtocol
  212. }
  213. }
  214. """
  215. try self.assertTypealiasTranslation(
  216. codeGenerationRequest: self.makeCodeGenerationRequest(services: [serviceB, serviceA]),
  217. expectedSwift: expectedSwift
  218. )
  219. }
  220. func testTypealiasTranslatorServiceAlphabeticalOrderNoNamespace() throws {
  221. let serviceB = ServiceDescriptor(
  222. documentation: "Documentation for BService",
  223. name: "BService",
  224. namespace: "",
  225. methods: []
  226. )
  227. let serviceA = ServiceDescriptor(
  228. documentation: "Documentation for AService",
  229. name: "AService",
  230. namespace: "",
  231. methods: []
  232. )
  233. let expectedSwift =
  234. """
  235. enum AService {
  236. enum Methods {}
  237. static let methods: [MethodDescriptor] = []
  238. typealias StreamingServiceProtocol = AServiceServiceStreamingProtocol
  239. typealias ServiceProtocol = AServiceServiceProtocol
  240. }
  241. enum BService {
  242. enum Methods {}
  243. static let methods: [MethodDescriptor] = []
  244. typealias StreamingServiceProtocol = BServiceServiceStreamingProtocol
  245. typealias ServiceProtocol = BServiceServiceProtocol
  246. }
  247. """
  248. try self.assertTypealiasTranslation(
  249. codeGenerationRequest: self.makeCodeGenerationRequest(services: [serviceB, serviceA]),
  250. expectedSwift: expectedSwift
  251. )
  252. }
  253. func testTypealiasTranslatorNamespaceAlphabeticalOrder() throws {
  254. let serviceB = ServiceDescriptor(
  255. documentation: "Documentation for BService",
  256. name: "BService",
  257. namespace: "bnamespace",
  258. methods: []
  259. )
  260. let serviceA = ServiceDescriptor(
  261. documentation: "Documentation for AService",
  262. name: "AService",
  263. namespace: "anamespace",
  264. methods: []
  265. )
  266. let expectedSwift =
  267. """
  268. enum anamespace {
  269. enum AService {
  270. enum Methods {}
  271. static let methods: [MethodDescriptor] = []
  272. typealias StreamingServiceProtocol = anamespace_AServiceServiceStreamingProtocol
  273. typealias ServiceProtocol = anamespace_AServiceServiceProtocol
  274. }
  275. }
  276. enum bnamespace {
  277. enum BService {
  278. enum Methods {}
  279. static let methods: [MethodDescriptor] = []
  280. typealias StreamingServiceProtocol = bnamespace_BServiceServiceStreamingProtocol
  281. typealias ServiceProtocol = bnamespace_BServiceServiceProtocol
  282. }
  283. }
  284. """
  285. try self.assertTypealiasTranslation(
  286. codeGenerationRequest: self.makeCodeGenerationRequest(services: [serviceB, serviceA]),
  287. expectedSwift: expectedSwift
  288. )
  289. }
  290. func testTypealiasTranslatorNamespaceNoNamespaceOrder() throws {
  291. let serviceA = ServiceDescriptor(
  292. documentation: "Documentation for AService",
  293. name: "AService",
  294. namespace: "anamespace",
  295. methods: []
  296. )
  297. let serviceB = ServiceDescriptor(
  298. documentation: "Documentation for BService",
  299. name: "BService",
  300. namespace: "",
  301. methods: []
  302. )
  303. let expectedSwift =
  304. """
  305. enum BService {
  306. enum Methods {}
  307. static let methods: [MethodDescriptor] = []
  308. typealias StreamingServiceProtocol = BServiceServiceStreamingProtocol
  309. typealias ServiceProtocol = BServiceServiceProtocol
  310. }
  311. enum anamespace {
  312. enum AService {
  313. enum Methods {}
  314. static let methods: [MethodDescriptor] = []
  315. typealias StreamingServiceProtocol = anamespace_AServiceServiceStreamingProtocol
  316. typealias ServiceProtocol = anamespace_AServiceServiceProtocol
  317. }
  318. }
  319. """
  320. try self.assertTypealiasTranslation(
  321. codeGenerationRequest: self.makeCodeGenerationRequest(services: [serviceA, serviceB]),
  322. expectedSwift: expectedSwift
  323. )
  324. }
  325. func testTypealiasTranslatorSameNameServicesNoNamespaceError() throws {
  326. let serviceA = ServiceDescriptor(
  327. documentation: "Documentation for AService",
  328. name: "AService",
  329. namespace: "",
  330. methods: []
  331. )
  332. let codeGenerationRequest = self.makeCodeGenerationRequest(services: [serviceA, serviceA])
  333. let translator = TypealiasTranslator()
  334. self.assertThrowsError(
  335. ofType: CodeGenError.self,
  336. try translator.translate(from: codeGenerationRequest)
  337. ) {
  338. error in
  339. XCTAssertEqual(
  340. error as CodeGenError,
  341. CodeGenError(
  342. code: .nonUniqueServiceName,
  343. message: """
  344. Services in an empty namespace must have unique names. \
  345. AService is used as a name for multiple services without namespaces.
  346. """
  347. )
  348. )
  349. }
  350. }
  351. func testTypealiasTranslatorSameNameServicesSameNamespaceError() throws {
  352. let serviceA = ServiceDescriptor(
  353. documentation: "Documentation for AService",
  354. name: "AService",
  355. namespace: "namespacea",
  356. methods: []
  357. )
  358. let codeGenerationRequest = self.makeCodeGenerationRequest(services: [serviceA, serviceA])
  359. let translator = TypealiasTranslator()
  360. self.assertThrowsError(
  361. ofType: CodeGenError.self,
  362. try translator.translate(from: codeGenerationRequest)
  363. ) {
  364. error in
  365. XCTAssertEqual(
  366. error as CodeGenError,
  367. CodeGenError(
  368. code: .nonUniqueServiceName,
  369. message: """
  370. Services within the same namespace must have unique names. \
  371. AService is used as a name for multiple services in the namespacea namespace.
  372. """
  373. )
  374. )
  375. }
  376. }
  377. func testTypealiasTranslatorSameNameMethodsSameServiceError() throws {
  378. let methodA = MethodDescriptor(
  379. documentation: "Documentation for MethodA",
  380. name: "MethodA",
  381. isInputStreaming: false,
  382. isOutputStreaming: false,
  383. inputType: "NamespaceA_ServiceARequest",
  384. outputType: "NamespaceA_ServiceAResponse"
  385. )
  386. let service = ServiceDescriptor(
  387. documentation: "Documentation for AService",
  388. name: "AService",
  389. namespace: "namespacea",
  390. methods: [methodA, methodA]
  391. )
  392. let codeGenerationRequest = self.makeCodeGenerationRequest(services: [service])
  393. let translator = TypealiasTranslator()
  394. self.assertThrowsError(
  395. ofType: CodeGenError.self,
  396. try translator.translate(from: codeGenerationRequest)
  397. ) {
  398. error in
  399. XCTAssertEqual(
  400. error as CodeGenError,
  401. CodeGenError(
  402. code: .nonUniqueMethodName,
  403. message: """
  404. Methods of a service must have unique names. \
  405. MethodA is used as a name for multiple methods of the AService service.
  406. """
  407. )
  408. )
  409. }
  410. }
  411. func testTypealiasTranslatorSameNameNoNamespaceServiceAndNamespaceError() throws {
  412. let serviceA = ServiceDescriptor(
  413. documentation: "Documentation for SameName service with no namespace",
  414. name: "SameName",
  415. namespace: "",
  416. methods: []
  417. )
  418. let serviceB = ServiceDescriptor(
  419. documentation: "Documentation for BService",
  420. name: "BService",
  421. namespace: "SameName",
  422. methods: []
  423. )
  424. let codeGenerationRequest = self.makeCodeGenerationRequest(services: [serviceA, serviceB])
  425. let translator = TypealiasTranslator()
  426. self.assertThrowsError(
  427. ofType: CodeGenError.self,
  428. try translator.translate(from: codeGenerationRequest)
  429. ) {
  430. error in
  431. XCTAssertEqual(
  432. error as CodeGenError,
  433. CodeGenError(
  434. code: .nonUniqueServiceName,
  435. message: """
  436. Services with no namespace must not have the same names as the namespaces. \
  437. SameName is used as a name for a service with no namespace and a namespace.
  438. """
  439. )
  440. )
  441. }
  442. }
  443. }
  444. extension SnippetBasedTranslatorTests {
  445. private func assertTypealiasTranslation(
  446. codeGenerationRequest: CodeGenerationRequest,
  447. expectedSwift: String
  448. ) throws {
  449. let translator = TypealiasTranslator()
  450. let codeBlocks = try translator.translate(from: codeGenerationRequest)
  451. let renderer = TextBasedRenderer.default
  452. renderer.renderCodeBlocks(codeBlocks)
  453. let contents = renderer.renderedContents()
  454. try XCTAssertEqualWithDiff(contents, expectedSwift)
  455. }
  456. private func assertThrowsError<T, E: Error>(
  457. ofType: E.Type,
  458. _ expression: @autoclosure () throws -> T,
  459. _ errorHandler: (E) -> Void
  460. ) {
  461. XCTAssertThrowsError(try expression()) { error in
  462. guard let error = error as? E else {
  463. return XCTFail("Error had unexpected type '\(type(of: error))'")
  464. }
  465. errorHandler(error)
  466. }
  467. }
  468. }
  469. extension SnippetBasedTranslatorTests {
  470. private func makeCodeGenerationRequest(services: [ServiceDescriptor]) -> CodeGenerationRequest {
  471. return CodeGenerationRequest(
  472. fileName: "test.grpc",
  473. leadingTrivia: "Some really exciting license header 2023.",
  474. dependencies: [],
  475. services: services,
  476. lookupSerializer: {
  477. "ProtobufSerializer<\($0)>()"
  478. },
  479. lookupDeserializer: {
  480. "ProtobufDeserializer<\($0)>()"
  481. }
  482. )
  483. }
  484. }