Test_ProtoFileToModuleMappings.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. // Tests/SwiftProtobufPluginLibraryTests/Test_ProtoFileToModuleMappings.swift - Test ProtoFile to Modules helper
  2. //
  3. // Copyright (c) 2014 - 2017 Apple Inc. and the project authors
  4. // Licensed under Apache License v2.0 with Runtime Library Exception
  5. //
  6. // See LICENSE.txt for license information:
  7. // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
  8. //
  9. // -----------------------------------------------------------------------------
  10. import SwiftProtobuf
  11. import SwiftProtobufPluginLibrary
  12. import SwiftProtobufTestHelpers
  13. import XCTest
  14. // Helpers to make test cases.
  15. private typealias FileDescriptorProto = Google_Protobuf_FileDescriptorProto
  16. final class Test_ProtoFileToModuleMappings: XCTestCase {
  17. func test_Initialization() {
  18. // ProtoFileToModuleMappings always includes mappings for the protos that
  19. // ship with the library, so they will show in the counts below.
  20. let baselineEntries = SwiftProtobufInfo.bundledProtoFiles.count
  21. let baselineModules = 1 // Since those files are in SwiftProtobuf.
  22. // (config, num_expected_mappings, num_expected_modules)
  23. let tests: [(String, Int, Int)] = [
  24. ("", 0, 0),
  25. ("mapping { module_name: \"good\", proto_file_path: \"file.proto\" }", 1, 1),
  26. ("mapping { module_name: \"good\", proto_file_path: [\"a\",\"b\"] }", 2, 1),
  27. // Two mapping {}, same module.
  28. (
  29. "mapping { module_name: \"good\", proto_file_path: \"a\" }\n"
  30. + "mapping { module_name: \"good\", proto_file_path: \"b\" }", 2, 1
  31. ),
  32. // Two mapping {}, different modules.
  33. (
  34. "mapping { module_name: \"one\", proto_file_path: \"a\" }\n"
  35. + "mapping { module_name: \"two\", proto_file_path: \"b\" }", 2, 2
  36. ),
  37. // Same file listed twice; odd, but ok since no conflict.
  38. ("mapping { module_name: \"foo\", proto_file_path: [\"abc\", \"abc\"] }", 1, 1),
  39. // Same module/file listing; odd, but ok since no conflict.
  40. (
  41. "mapping { module_name: \"foo\", proto_file_path: [\"mno\", \"abc\"] }\n"
  42. + "mapping { module_name: \"foo\", proto_file_path: [\"abc\", \"xyz\"] }", 3, 1
  43. ),
  44. ]
  45. for (idx, (configText, expectMappings, expectedModules)) in tests.enumerated() {
  46. let config: SwiftProtobuf_GenSwift_ModuleMappings
  47. do {
  48. config = try SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: configText)
  49. } catch {
  50. XCTFail("Index: \(idx) - Test case wasn't valid TextFormat")
  51. continue
  52. }
  53. do {
  54. let mapper = try ProtoFileToModuleMappings(moduleMappingsProto: config)
  55. XCTAssertEqual(mapper.mappings.count, expectMappings + baselineEntries, "Index: \(idx)")
  56. XCTAssertEqual(Set(mapper.mappings.values).count, expectedModules + baselineModules, "Index: \(idx)")
  57. XCTAssert(mapper.hasMappings == (expectMappings != 0), "Index: \(idx)")
  58. } catch let error {
  59. XCTFail("Index \(idx) - Unexpected error: \(error)")
  60. }
  61. }
  62. }
  63. func test_Initialization_InvalidConfigs() {
  64. // This are valid text format, but not valid config protos.
  65. // (input, expected_error_type)
  66. let partialConfigs: [(String, ProtoFileToModuleMappings.LoadError)] = [
  67. // No module or proto files
  68. ("mapping { }", .entryMissingModuleName(mappingIndex: 0)),
  69. // No proto files
  70. ("mapping { module_name: \"foo\" }", .entryHasNoProtoPaths(mappingIndex: 0)),
  71. // No module
  72. ("mapping { proto_file_path: [\"foo\"] }", .entryMissingModuleName(mappingIndex: 0)),
  73. ("mapping { proto_file_path: [\"foo\", \"bar\"] }", .entryMissingModuleName(mappingIndex: 0)),
  74. // Empty module name.
  75. ("mapping { module_name: \"\" }", .entryMissingModuleName(mappingIndex: 0)),
  76. ("mapping { module_name: \"\", proto_file_path: [\"foo\"] }", .entryMissingModuleName(mappingIndex: 0)),
  77. (
  78. "mapping { module_name: \"\", proto_file_path: [\"foo\", \"bar\"] }",
  79. .entryMissingModuleName(mappingIndex: 0)
  80. ),
  81. // Throw some on a second entry just to check that also.
  82. (
  83. "mapping { module_name: \"good\", proto_file_path: \"file.proto\" }\n" + "mapping { }",
  84. .entryMissingModuleName(mappingIndex: 1)
  85. ),
  86. (
  87. "mapping { module_name: \"good\", proto_file_path: \"file.proto\" }\n"
  88. + "mapping { module_name: \"foo\" }",
  89. .entryHasNoProtoPaths(mappingIndex: 1)
  90. ),
  91. // Duplicates
  92. (
  93. "mapping { module_name: \"foo\", proto_file_path: \"abc\" }\n"
  94. + "mapping { module_name: \"bar\", proto_file_path: \"abc\" }",
  95. .duplicateProtoPathMapping(path: "abc", firstModule: "foo", secondModule: "bar")
  96. ),
  97. (
  98. "mapping { module_name: \"foo\", proto_file_path: \"abc\" }\n"
  99. + "mapping { module_name: \"bar\", proto_file_path: \"xyz\" }\n"
  100. + "mapping { module_name: \"baz\", proto_file_path: \"abc\" }",
  101. .duplicateProtoPathMapping(path: "abc", firstModule: "foo", secondModule: "baz")
  102. ),
  103. ]
  104. for (idx, (configText, expected)) in partialConfigs.enumerated() {
  105. let config: SwiftProtobuf_GenSwift_ModuleMappings
  106. do {
  107. config = try SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: configText)
  108. } catch {
  109. XCTFail("Index: \(idx) - Test case wasn't valid TextFormat")
  110. continue
  111. }
  112. do {
  113. let _ = try ProtoFileToModuleMappings(moduleMappingsProto: config)
  114. XCTFail("Shouldn't have gotten here, index \(idx)")
  115. } catch let error as ProtoFileToModuleMappings.LoadError {
  116. XCTAssertEqual(error, expected, "Index \(idx)")
  117. } catch let error {
  118. XCTFail("Index \(idx) - Unexpected error: \(error)")
  119. }
  120. }
  121. }
  122. func test_moduleName_forFile() {
  123. let configText = [
  124. "mapping { module_name: \"foo\", proto_file_path: \"file\" }",
  125. "mapping { module_name: \"bar\", proto_file_path: \"dir1/file\" }",
  126. "mapping { module_name: \"baz\", proto_file_path: [\"dir2/file\",\"file4\"] }",
  127. "mapping { module_name: \"foo\", proto_file_path: \"file5\" }",
  128. ].joined(separator: "\n")
  129. let config = try! SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: configText)
  130. let mapper = try! ProtoFileToModuleMappings(moduleMappingsProto: config)
  131. let tests: [(String, String?)] = [
  132. ("file", "foo"),
  133. ("dir1/file", "bar"),
  134. ("dir2/file", "baz"),
  135. ("file4", "baz"),
  136. ("file5", "foo"),
  137. ("", nil),
  138. ("not found", nil),
  139. ]
  140. for (name, expected) in tests {
  141. let descSet = DescriptorSet(protos: [FileDescriptorProto(name: name)])
  142. XCTAssertEqual(mapper.moduleName(forFile: descSet.files.first!), expected, "Looking for \(name)")
  143. }
  144. }
  145. func test_neededModules_forFile() {
  146. let configText = [
  147. "mapping { module_name: \"foo\", proto_file_path: \"file\" }",
  148. "mapping { module_name: \"bar\", proto_file_path: \"dir1/file\" }",
  149. "mapping { module_name: \"baz\", proto_file_path: [\"dir2/file\",\"file4\"] }",
  150. "mapping { module_name: \"foo\", proto_file_path: \"file5\" }",
  151. ].joined(separator: "\n")
  152. let config = try! SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: configText)
  153. let mapper = try! ProtoFileToModuleMappings(moduleMappingsProto: config)
  154. let fileProtos = [
  155. FileDescriptorProto(name: "file"),
  156. FileDescriptorProto(name: "google/protobuf/any.proto"),
  157. FileDescriptorProto(name: "dir1/file", dependencies: ["file"]),
  158. FileDescriptorProto(name: "dir2/file", dependencies: ["google/protobuf/any.proto"]),
  159. FileDescriptorProto(name: "file4", dependencies: ["dir2/file", "dir1/file", "file"]),
  160. FileDescriptorProto(name: "file5", dependencies: ["file"]),
  161. ]
  162. let descSet = DescriptorSet(protos: fileProtos)
  163. // ( filename, [deps] )
  164. let tests: [(String, [String]?)] = [
  165. ("file", nil),
  166. ("dir1/file", ["foo"]),
  167. ("dir2/file", nil),
  168. ("file4", ["bar", "foo"]),
  169. ("file5", nil),
  170. ]
  171. for (name, expected) in tests {
  172. let fileDesc = descSet.files.filter { $0.name == name }.first!
  173. let result = mapper.neededModules(forFile: fileDesc)
  174. if let expected = expected {
  175. XCTAssertEqual(result!, expected, "Looking for \(name)")
  176. } else {
  177. XCTAssertNil(result, "Looking for \(name)")
  178. }
  179. }
  180. }
  181. func test_neededModules_forFile_PublicImports() {
  182. // See the note in neededModules(forFile:) about how public import complicate things.
  183. // Given:
  184. //
  185. // + File: a.proto
  186. // message A {}
  187. //
  188. // + File: imports_a_publicly.proto
  189. // import public "a.proto";
  190. //
  191. // message ImportsAPublicly {
  192. // A a = 1;
  193. // }
  194. //
  195. // + File: imports_imports_a_publicly.proto
  196. // import public "imports_a_publicly.proto";
  197. //
  198. // message ImportsImportsAPublicly {
  199. // A a = 1;
  200. // }
  201. //
  202. // + File: uses_a_transitively.proto
  203. // import "imports_a_publicly.proto";
  204. //
  205. // message UsesATransitively {
  206. // A a = 1;
  207. // }
  208. //
  209. // + File: uses_a_transitively2.proto
  210. // import "imports_imports_a_publicly.proto";
  211. //
  212. // message UsesATransitively2 {
  213. // A a = 1;
  214. // }
  215. //
  216. // With a mapping file of:
  217. //
  218. // mapping {
  219. // module_name: "A"
  220. // proto_file_path: "a.proto"
  221. // }
  222. // mapping {
  223. // module_name: "ImportsAPublicly"
  224. // proto_file_path: "imports_a_publicly.proto"
  225. // }
  226. // mapping {
  227. // module_name: "ImportsImportsAPublicly"
  228. // proto_file_path: "imports_imports_a_publicly.proto"
  229. // }
  230. let configText = [
  231. "mapping { module_name: \"A\", proto_file_path: \"a.proto\" }",
  232. "mapping { module_name: \"ImportsAPublicly\", proto_file_path: \"imports_a_publicly.proto\" }",
  233. "mapping { module_name: \"ImportsImportsAPublicly\", proto_file_path: \"imports_imports_a_publicly.proto\" }",
  234. ].joined(separator: "\n")
  235. let config = try! SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: configText)
  236. let mapper = try! ProtoFileToModuleMappings(moduleMappingsProto: config)
  237. let fileProtos = [
  238. FileDescriptorProto(name: "a.proto"),
  239. FileDescriptorProto(
  240. name: "imports_a_publicly.proto",
  241. dependencies: ["a.proto"],
  242. publicDependencies: [0]
  243. ),
  244. FileDescriptorProto(
  245. name: "imports_imports_a_publicly.proto",
  246. dependencies: ["imports_a_publicly.proto"],
  247. publicDependencies: [0]
  248. ),
  249. FileDescriptorProto(
  250. name: "uses_a_transitively.proto",
  251. dependencies: ["imports_a_publicly.proto"]
  252. ),
  253. FileDescriptorProto(
  254. name: "uses_a_transitively2.proto",
  255. dependencies: ["imports_imports_a_publicly.proto"]
  256. ),
  257. ]
  258. let descSet = DescriptorSet(protos: fileProtos)
  259. // ( filename, [deps] )
  260. let tests: [(String, [String]?)] = [
  261. ("a.proto", nil),
  262. ("imports_a_publicly.proto", ["A"]),
  263. ("imports_imports_a_publicly.proto", ["A", "ImportsAPublicly"]),
  264. ("uses_a_transitively.proto", ["A", "ImportsAPublicly"]),
  265. ("uses_a_transitively2.proto", ["A", "ImportsAPublicly", "ImportsImportsAPublicly"]),
  266. ]
  267. for (name, expected) in tests {
  268. let fileDesc = descSet.files.filter { $0.name == name }.first!
  269. let result = mapper.neededModules(forFile: fileDesc)
  270. if let expected = expected {
  271. XCTAssertEqual(result!, expected, "Looking for \(name)")
  272. } else {
  273. XCTAssertNil(result, "Looking for \(name)")
  274. }
  275. }
  276. }
  277. }