IDLToStructuredSwiftTranslatorSnippetBasedTests.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  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 XCTest
  18. @testable import GRPCCodeGen
  19. final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
  20. typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor
  21. typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor
  22. typealias Name = GRPCCodeGen.CodeGenerationRequest.Name
  23. func testImports() throws {
  24. var dependencies = [CodeGenerationRequest.Dependency]()
  25. dependencies.append(CodeGenerationRequest.Dependency(module: "Foo"))
  26. dependencies.append(
  27. CodeGenerationRequest.Dependency(item: .init(kind: .typealias, name: "Bar"), module: "Foo")
  28. )
  29. dependencies.append(
  30. CodeGenerationRequest.Dependency(item: .init(kind: .struct, name: "Baz"), module: "Foo")
  31. )
  32. dependencies.append(
  33. CodeGenerationRequest.Dependency(item: .init(kind: .class, name: "Bac"), module: "Foo")
  34. )
  35. dependencies.append(
  36. CodeGenerationRequest.Dependency(item: .init(kind: .enum, name: "Bap"), module: "Foo")
  37. )
  38. dependencies.append(
  39. CodeGenerationRequest.Dependency(item: .init(kind: .protocol, name: "Bat"), module: "Foo")
  40. )
  41. dependencies.append(
  42. CodeGenerationRequest.Dependency(item: .init(kind: .let, name: "Baq"), module: "Foo")
  43. )
  44. dependencies.append(
  45. CodeGenerationRequest.Dependency(item: .init(kind: .var, name: "Bag"), module: "Foo")
  46. )
  47. dependencies.append(
  48. CodeGenerationRequest.Dependency(item: .init(kind: .func, name: "Bak"), module: "Foo")
  49. )
  50. let expectedSwift =
  51. """
  52. /// Some really exciting license header 2023.
  53. import GRPCCore
  54. import Foo
  55. import typealias Foo.Bar
  56. import struct Foo.Baz
  57. import class Foo.Bac
  58. import enum Foo.Bap
  59. import protocol Foo.Bat
  60. import let Foo.Baq
  61. import var Foo.Bag
  62. import func Foo.Bak
  63. """
  64. try self.assertIDLToStructuredSwiftTranslation(
  65. codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies),
  66. expectedSwift: expectedSwift,
  67. accessLevel: .public
  68. )
  69. }
  70. func testPreconcurrencyImports() throws {
  71. var dependencies = [CodeGenerationRequest.Dependency]()
  72. dependencies.append(CodeGenerationRequest.Dependency(module: "Foo", preconcurrency: .required))
  73. dependencies.append(
  74. CodeGenerationRequest.Dependency(
  75. item: .init(kind: .enum, name: "Bar"),
  76. module: "Foo",
  77. preconcurrency: .required
  78. )
  79. )
  80. dependencies.append(
  81. CodeGenerationRequest.Dependency(
  82. module: "Baz",
  83. preconcurrency: .requiredOnOS(["Deq", "Der"])
  84. )
  85. )
  86. let expectedSwift =
  87. """
  88. /// Some really exciting license header 2023.
  89. import GRPCCore
  90. @preconcurrency import Foo
  91. @preconcurrency import enum Foo.Bar
  92. #if os(Deq) || os(Der)
  93. @preconcurrency import Baz
  94. #else
  95. import Baz
  96. #endif
  97. """
  98. try self.assertIDLToStructuredSwiftTranslation(
  99. codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies),
  100. expectedSwift: expectedSwift,
  101. accessLevel: .public
  102. )
  103. }
  104. func testSPIImports() throws {
  105. var dependencies = [CodeGenerationRequest.Dependency]()
  106. dependencies.append(CodeGenerationRequest.Dependency(module: "Foo", spi: "Secret"))
  107. dependencies.append(
  108. CodeGenerationRequest.Dependency(
  109. item: .init(kind: .enum, name: "Bar"),
  110. module: "Foo",
  111. spi: "Secret"
  112. )
  113. )
  114. let expectedSwift =
  115. """
  116. /// Some really exciting license header 2023.
  117. import GRPCCore
  118. @_spi(Secret) import Foo
  119. @_spi(Secret) import enum Foo.Bar
  120. """
  121. try self.assertIDLToStructuredSwiftTranslation(
  122. codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies),
  123. expectedSwift: expectedSwift,
  124. accessLevel: .public
  125. )
  126. }
  127. func testGeneration() throws {
  128. var dependencies = [CodeGenerationRequest.Dependency]()
  129. dependencies.append(CodeGenerationRequest.Dependency(module: "Foo", spi: "Secret"))
  130. dependencies.append(
  131. CodeGenerationRequest.Dependency(
  132. item: .init(kind: .enum, name: "Bar"),
  133. module: "Foo",
  134. spi: "Secret"
  135. )
  136. )
  137. let serviceA = ServiceDescriptor(
  138. documentation: "/// Documentation for AService\n",
  139. name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"),
  140. namespace: Name(
  141. base: "namespaceA",
  142. generatedUpperCase: "NamespaceA",
  143. generatedLowerCase: "namespaceA"
  144. ),
  145. methods: []
  146. )
  147. let expectedSwift =
  148. """
  149. /// Some really exciting license header 2023.
  150. import GRPCCore
  151. @_spi(Secret) import Foo
  152. @_spi(Secret) import enum Foo.Bar
  153. public enum NamespaceA_ServiceA {
  154. public enum Method {
  155. public static let descriptors: [MethodDescriptor] = []
  156. }
  157. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  158. public typealias StreamingServiceProtocol = NamespaceA_ServiceAStreamingServiceProtocol
  159. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  160. public typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol
  161. }
  162. /// Documentation for AService
  163. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  164. public protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService {}
  165. /// Conformance to `GRPCCore.RegistrableRPCService`.
  166. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  167. extension NamespaceA_ServiceA.StreamingServiceProtocol {
  168. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  169. public func registerMethods(with router: inout GRPCCore.RPCRouter) {}
  170. }
  171. /// Documentation for AService
  172. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  173. public protocol NamespaceA_ServiceAServiceProtocol: NamespaceA_ServiceA.StreamingServiceProtocol {}
  174. /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`.
  175. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  176. extension NamespaceA_ServiceA.ServiceProtocol {
  177. }
  178. """
  179. try self.assertIDLToStructuredSwiftTranslation(
  180. codeGenerationRequest: makeCodeGenerationRequest(
  181. services: [serviceA],
  182. dependencies: dependencies
  183. ),
  184. expectedSwift: expectedSwift,
  185. accessLevel: .public,
  186. server: true
  187. )
  188. }
  189. private func assertIDLToStructuredSwiftTranslation(
  190. codeGenerationRequest: CodeGenerationRequest,
  191. expectedSwift: String,
  192. accessLevel: SourceGenerator.Configuration.AccessLevel,
  193. server: Bool = false
  194. ) throws {
  195. let translator = IDLToStructuredSwiftTranslator()
  196. let structuredSwift = try translator.translate(
  197. codeGenerationRequest: codeGenerationRequest,
  198. accessLevel: accessLevel,
  199. client: false,
  200. server: server
  201. )
  202. let renderer = TextBasedRenderer.default
  203. let sourceFile = try renderer.render(structured: structuredSwift)
  204. let contents = sourceFile.contents
  205. try XCTAssertEqualWithDiff(contents, expectedSwift)
  206. }
  207. func testSameNameServicesNoNamespaceError() throws {
  208. let serviceA = ServiceDescriptor(
  209. documentation: "Documentation for AService",
  210. name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
  211. namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""),
  212. methods: []
  213. )
  214. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceA])
  215. let translator = IDLToStructuredSwiftTranslator()
  216. XCTAssertThrowsError(
  217. ofType: CodeGenError.self,
  218. try translator.translate(
  219. codeGenerationRequest: codeGenerationRequest,
  220. accessLevel: .public,
  221. client: true,
  222. server: true
  223. )
  224. ) {
  225. error in
  226. XCTAssertEqual(
  227. error as CodeGenError,
  228. CodeGenError(
  229. code: .nonUniqueServiceName,
  230. message: """
  231. Services must have unique descriptors. \
  232. AService is the descriptor of at least two different services.
  233. """
  234. )
  235. )
  236. }
  237. }
  238. func testSameDescriptorsServicesNoNamespaceError() throws {
  239. let serviceA = ServiceDescriptor(
  240. documentation: "Documentation for AService",
  241. name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
  242. namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""),
  243. methods: []
  244. )
  245. let serviceB = ServiceDescriptor(
  246. documentation: "Documentation for BService",
  247. name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
  248. namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""),
  249. methods: []
  250. )
  251. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB])
  252. let translator = IDLToStructuredSwiftTranslator()
  253. XCTAssertThrowsError(
  254. ofType: CodeGenError.self,
  255. try translator.translate(
  256. codeGenerationRequest: codeGenerationRequest,
  257. accessLevel: .public,
  258. client: true,
  259. server: true
  260. )
  261. ) {
  262. error in
  263. XCTAssertEqual(
  264. error as CodeGenError,
  265. CodeGenError(
  266. code: .nonUniqueServiceName,
  267. message: """
  268. Services must have unique descriptors. AService is the descriptor of at least two different services.
  269. """
  270. )
  271. )
  272. }
  273. }
  274. func testSameDescriptorsSameNamespaceError() throws {
  275. let serviceA = ServiceDescriptor(
  276. documentation: "Documentation for AService",
  277. name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
  278. namespace: Name(
  279. base: "namespacea",
  280. generatedUpperCase: "NamespaceA",
  281. generatedLowerCase: "namespacea"
  282. ),
  283. methods: []
  284. )
  285. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceA])
  286. let translator = IDLToStructuredSwiftTranslator()
  287. XCTAssertThrowsError(
  288. ofType: CodeGenError.self,
  289. try translator.translate(
  290. codeGenerationRequest: codeGenerationRequest,
  291. accessLevel: .public,
  292. client: true,
  293. server: true
  294. )
  295. ) {
  296. error in
  297. XCTAssertEqual(
  298. error as CodeGenError,
  299. CodeGenError(
  300. code: .nonUniqueServiceName,
  301. message: """
  302. Services must have unique descriptors. \
  303. namespacea.AService is the descriptor of at least two different services.
  304. """
  305. )
  306. )
  307. }
  308. }
  309. func testSameGeneratedNameServicesSameNamespaceError() throws {
  310. let serviceA = ServiceDescriptor(
  311. documentation: "/// Documentation for AService\n",
  312. name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
  313. namespace: Name(
  314. base: "namespacea",
  315. generatedUpperCase: "NamespaceA",
  316. generatedLowerCase: "namespacea"
  317. ),
  318. methods: []
  319. )
  320. let serviceB = ServiceDescriptor(
  321. documentation: "/// Documentation for BService\n",
  322. name: Name(base: "BService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
  323. namespace: Name(
  324. base: "namespacea",
  325. generatedUpperCase: "NamespaceA",
  326. generatedLowerCase: "namespacea"
  327. ),
  328. methods: []
  329. )
  330. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB])
  331. let translator = IDLToStructuredSwiftTranslator()
  332. XCTAssertThrowsError(
  333. ofType: CodeGenError.self,
  334. try translator.translate(
  335. codeGenerationRequest: codeGenerationRequest,
  336. accessLevel: .internal,
  337. client: true,
  338. server: true
  339. )
  340. ) {
  341. error in
  342. XCTAssertEqual(
  343. error as CodeGenError,
  344. CodeGenError(
  345. code: .nonUniqueServiceName,
  346. message: """
  347. There must be a unique (namespace, service_name) pair for each service. \
  348. NamespaceA_AService is used as a <namespace>_<service_name> construction for multiple services.
  349. """
  350. )
  351. )
  352. }
  353. }
  354. func testSameBaseNameMethodsSameServiceError() throws {
  355. let methodA = MethodDescriptor(
  356. documentation: "Documentation for MethodA",
  357. name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"),
  358. isInputStreaming: false,
  359. isOutputStreaming: false,
  360. inputType: "NamespaceA_ServiceARequest",
  361. outputType: "NamespaceA_ServiceAResponse"
  362. )
  363. let service = ServiceDescriptor(
  364. documentation: "Documentation for AService",
  365. name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
  366. namespace: Name(
  367. base: "namespacea",
  368. generatedUpperCase: "NamespaceA",
  369. generatedLowerCase: "namespacea"
  370. ),
  371. methods: [methodA, methodA]
  372. )
  373. let codeGenerationRequest = makeCodeGenerationRequest(services: [service])
  374. let translator = IDLToStructuredSwiftTranslator()
  375. XCTAssertThrowsError(
  376. ofType: CodeGenError.self,
  377. try translator.translate(
  378. codeGenerationRequest: codeGenerationRequest,
  379. accessLevel: .public,
  380. client: true,
  381. server: true
  382. )
  383. ) {
  384. error in
  385. XCTAssertEqual(
  386. error as CodeGenError,
  387. CodeGenError(
  388. code: .nonUniqueMethodName,
  389. message: """
  390. Methods of a service must have unique base names. \
  391. MethodA is used as a base name for multiple methods of the AService service.
  392. """
  393. )
  394. )
  395. }
  396. }
  397. func testSameGeneratedUpperCaseNameMethodsSameServiceError() throws {
  398. let methodA = MethodDescriptor(
  399. documentation: "Documentation for MethodA",
  400. name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"),
  401. isInputStreaming: false,
  402. isOutputStreaming: false,
  403. inputType: "NamespaceA_ServiceARequest",
  404. outputType: "NamespaceA_ServiceAResponse"
  405. )
  406. let methodB = MethodDescriptor(
  407. documentation: "Documentation for MethodA",
  408. name: Name(base: "MethodB", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"),
  409. isInputStreaming: false,
  410. isOutputStreaming: false,
  411. inputType: "NamespaceA_ServiceARequest",
  412. outputType: "NamespaceA_ServiceAResponse"
  413. )
  414. let service = ServiceDescriptor(
  415. documentation: "Documentation for AService",
  416. name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
  417. namespace: Name(
  418. base: "namespacea",
  419. generatedUpperCase: "NamespaceA",
  420. generatedLowerCase: "namespacea"
  421. ),
  422. methods: [methodA, methodB]
  423. )
  424. let codeGenerationRequest = makeCodeGenerationRequest(services: [service])
  425. let translator = IDLToStructuredSwiftTranslator()
  426. XCTAssertThrowsError(
  427. ofType: CodeGenError.self,
  428. try translator.translate(
  429. codeGenerationRequest: codeGenerationRequest,
  430. accessLevel: .public,
  431. client: true,
  432. server: true
  433. )
  434. ) {
  435. error in
  436. XCTAssertEqual(
  437. error as CodeGenError,
  438. CodeGenError(
  439. code: .nonUniqueMethodName,
  440. message: """
  441. Methods of a service must have unique generated upper case names. \
  442. MethodA is used as a generated upper case name for multiple methods of the AService service.
  443. """
  444. )
  445. )
  446. }
  447. }
  448. func testSameLowerCaseNameMethodsSameServiceError() throws {
  449. let methodA = MethodDescriptor(
  450. documentation: "Documentation for MethodA",
  451. name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"),
  452. isInputStreaming: false,
  453. isOutputStreaming: false,
  454. inputType: "NamespaceA_ServiceARequest",
  455. outputType: "NamespaceA_ServiceAResponse"
  456. )
  457. let methodB = MethodDescriptor(
  458. documentation: "Documentation for MethodA",
  459. name: Name(base: "MethodB", generatedUpperCase: "MethodB", generatedLowerCase: "methodA"),
  460. isInputStreaming: false,
  461. isOutputStreaming: false,
  462. inputType: "NamespaceA_ServiceARequest",
  463. outputType: "NamespaceA_ServiceAResponse"
  464. )
  465. let service = ServiceDescriptor(
  466. documentation: "Documentation for AService",
  467. name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"),
  468. namespace: Name(
  469. base: "namespacea",
  470. generatedUpperCase: "NamespaceA",
  471. generatedLowerCase: "namespacea"
  472. ),
  473. methods: [methodA, methodB]
  474. )
  475. let codeGenerationRequest = makeCodeGenerationRequest(services: [service])
  476. let translator = IDLToStructuredSwiftTranslator()
  477. XCTAssertThrowsError(
  478. ofType: CodeGenError.self,
  479. try translator.translate(
  480. codeGenerationRequest: codeGenerationRequest,
  481. accessLevel: .public,
  482. client: true,
  483. server: true
  484. )
  485. ) {
  486. error in
  487. XCTAssertEqual(
  488. error as CodeGenError,
  489. CodeGenError(
  490. code: .nonUniqueMethodName,
  491. message: """
  492. Methods of a service must have unique lower case names. \
  493. methodA is used as a signature name for multiple methods of the AService service.
  494. """
  495. )
  496. )
  497. }
  498. }
  499. func testSameGeneratedNameNoNamespaceServiceAndNamespaceError() throws {
  500. let serviceA = ServiceDescriptor(
  501. documentation: "Documentation for SameName service with no namespace",
  502. name: Name(
  503. base: "SameName",
  504. generatedUpperCase: "SameName_BService",
  505. generatedLowerCase: "sameName"
  506. ),
  507. namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""),
  508. methods: []
  509. )
  510. let serviceB = ServiceDescriptor(
  511. documentation: "Documentation for BService",
  512. name: Name(base: "BService", generatedUpperCase: "BService", generatedLowerCase: "bService"),
  513. namespace: Name(
  514. base: "sameName",
  515. generatedUpperCase: "SameName",
  516. generatedLowerCase: "sameName"
  517. ),
  518. methods: []
  519. )
  520. let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB])
  521. let translator = IDLToStructuredSwiftTranslator()
  522. XCTAssertThrowsError(
  523. ofType: CodeGenError.self,
  524. try translator.translate(
  525. codeGenerationRequest: codeGenerationRequest,
  526. accessLevel: .public,
  527. client: true,
  528. server: true
  529. )
  530. ) {
  531. error in
  532. XCTAssertEqual(
  533. error as CodeGenError,
  534. CodeGenError(
  535. code: .nonUniqueServiceName,
  536. message: """
  537. There must be a unique (namespace, service_name) pair for each service. \
  538. SameName_BService is used as a <namespace>_<service_name> construction for multiple services.
  539. """
  540. )
  541. )
  542. }
  543. }
  544. }
  545. #endif // os(macOS) || os(Linux)