// Tests/SwiftProtobufPluginLibraryTests/Test_ProtoFileToModuleMappings.swift - Test ProtoFile to Modules helper // // Copyright (c) 2014 - 2017 Apple Inc. and the project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See LICENSE.txt for license information: // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt // // ----------------------------------------------------------------------------- import SwiftProtobuf import SwiftProtobufPluginLibrary import SwiftProtobufTestHelpers import XCTest // Helpers to make test cases. private typealias FileDescriptorProto = Google_Protobuf_FileDescriptorProto final class Test_ProtoFileToModuleMappings: XCTestCase { func test_Initialization() { // ProtoFileToModuleMappings always includes mappings for the protos that // ship with the library, so they will show in the counts below. let baselineEntries = SwiftProtobufInfo.bundledProtoFiles.count let baselineModules = 1 // Since those files are in SwiftProtobuf. // (config, num_expected_mappings, num_expected_modules) let tests: [(String, Int, Int)] = [ ("", 0, 0), ("mapping { module_name: \"good\", proto_file_path: \"file.proto\" }", 1, 1), ("mapping { module_name: \"good\", proto_file_path: [\"a\",\"b\"] }", 2, 1), // Two mapping {}, same module. ( "mapping { module_name: \"good\", proto_file_path: \"a\" }\n" + "mapping { module_name: \"good\", proto_file_path: \"b\" }", 2, 1 ), // Two mapping {}, different modules. ( "mapping { module_name: \"one\", proto_file_path: \"a\" }\n" + "mapping { module_name: \"two\", proto_file_path: \"b\" }", 2, 2 ), // Same file listed twice; odd, but ok since no conflict. ("mapping { module_name: \"foo\", proto_file_path: [\"abc\", \"abc\"] }", 1, 1), // Same module/file listing; odd, but ok since no conflict. ( "mapping { module_name: \"foo\", proto_file_path: [\"mno\", \"abc\"] }\n" + "mapping { module_name: \"foo\", proto_file_path: [\"abc\", \"xyz\"] }", 3, 1 ), ] for (idx, (configText, expectMappings, expectedModules)) in tests.enumerated() { let config: SwiftProtobuf_GenSwift_ModuleMappings do { config = try SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: configText) } catch { XCTFail("Index: \(idx) - Test case wasn't valid TextFormat") continue } do { let mapper = try ProtoFileToModuleMappings(moduleMappingsProto: config) XCTAssertEqual(mapper.mappings.count, expectMappings + baselineEntries, "Index: \(idx)") XCTAssertEqual(Set(mapper.mappings.values).count, expectedModules + baselineModules, "Index: \(idx)") XCTAssert(mapper.hasMappings == (expectMappings != 0), "Index: \(idx)") } catch let error { XCTFail("Index \(idx) - Unexpected error: \(error)") } } } func test_Initialization_InvalidConfigs() { // This are valid text format, but not valid config protos. // (input, expected_error_type) let partialConfigs: [(String, ProtoFileToModuleMappings.LoadError)] = [ // No module or proto files ("mapping { }", .entryMissingModuleName(mappingIndex: 0)), // No proto files ("mapping { module_name: \"foo\" }", .entryHasNoProtoPaths(mappingIndex: 0)), // No module ("mapping { proto_file_path: [\"foo\"] }", .entryMissingModuleName(mappingIndex: 0)), ("mapping { proto_file_path: [\"foo\", \"bar\"] }", .entryMissingModuleName(mappingIndex: 0)), // Empty module name. ("mapping { module_name: \"\" }", .entryMissingModuleName(mappingIndex: 0)), ("mapping { module_name: \"\", proto_file_path: [\"foo\"] }", .entryMissingModuleName(mappingIndex: 0)), ( "mapping { module_name: \"\", proto_file_path: [\"foo\", \"bar\"] }", .entryMissingModuleName(mappingIndex: 0) ), // Throw some on a second entry just to check that also. ( "mapping { module_name: \"good\", proto_file_path: \"file.proto\" }\n" + "mapping { }", .entryMissingModuleName(mappingIndex: 1) ), ( "mapping { module_name: \"good\", proto_file_path: \"file.proto\" }\n" + "mapping { module_name: \"foo\" }", .entryHasNoProtoPaths(mappingIndex: 1) ), // Duplicates ( "mapping { module_name: \"foo\", proto_file_path: \"abc\" }\n" + "mapping { module_name: \"bar\", proto_file_path: \"abc\" }", .duplicateProtoPathMapping(path: "abc", firstModule: "foo", secondModule: "bar") ), ( "mapping { module_name: \"foo\", proto_file_path: \"abc\" }\n" + "mapping { module_name: \"bar\", proto_file_path: \"xyz\" }\n" + "mapping { module_name: \"baz\", proto_file_path: \"abc\" }", .duplicateProtoPathMapping(path: "abc", firstModule: "foo", secondModule: "baz") ), ] for (idx, (configText, expected)) in partialConfigs.enumerated() { let config: SwiftProtobuf_GenSwift_ModuleMappings do { config = try SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: configText) } catch { XCTFail("Index: \(idx) - Test case wasn't valid TextFormat") continue } do { let _ = try ProtoFileToModuleMappings(moduleMappingsProto: config) XCTFail("Shouldn't have gotten here, index \(idx)") } catch let error as ProtoFileToModuleMappings.LoadError { XCTAssertEqual(error, expected, "Index \(idx)") } catch let error { XCTFail("Index \(idx) - Unexpected error: \(error)") } } } func test_moduleName_forFile() { let configText = [ "mapping { module_name: \"foo\", proto_file_path: \"file\" }", "mapping { module_name: \"bar\", proto_file_path: \"dir1/file\" }", "mapping { module_name: \"baz\", proto_file_path: [\"dir2/file\",\"file4\"] }", "mapping { module_name: \"foo\", proto_file_path: \"file5\" }", ].joined(separator: "\n") let config = try! SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: configText) let mapper = try! ProtoFileToModuleMappings(moduleMappingsProto: config) let tests: [(String, String?)] = [ ("file", "foo"), ("dir1/file", "bar"), ("dir2/file", "baz"), ("file4", "baz"), ("file5", "foo"), ("", nil), ("not found", nil), ] for (name, expected) in tests { let descSet = DescriptorSet(protos: [FileDescriptorProto(name: name)]) XCTAssertEqual(mapper.moduleName(forFile: descSet.files.first!), expected, "Looking for \(name)") } } func test_neededModules_forFile() { let configText = [ "mapping { module_name: \"foo\", proto_file_path: \"file\" }", "mapping { module_name: \"bar\", proto_file_path: \"dir1/file\" }", "mapping { module_name: \"baz\", proto_file_path: [\"dir2/file\",\"file4\"] }", "mapping { module_name: \"foo\", proto_file_path: \"file5\" }", ].joined(separator: "\n") let config = try! SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: configText) let mapper = try! ProtoFileToModuleMappings(moduleMappingsProto: config) let fileProtos = [ FileDescriptorProto(name: "file"), FileDescriptorProto(name: "google/protobuf/any.proto"), FileDescriptorProto(name: "dir1/file", dependencies: ["file"]), FileDescriptorProto(name: "dir2/file", dependencies: ["google/protobuf/any.proto"]), FileDescriptorProto(name: "file4", dependencies: ["dir2/file", "dir1/file", "file"]), FileDescriptorProto(name: "file5", dependencies: ["file"]), ] let descSet = DescriptorSet(protos: fileProtos) // ( filename, [deps] ) let tests: [(String, [String]?)] = [ ("file", nil), ("dir1/file", ["foo"]), ("dir2/file", nil), ("file4", ["bar", "foo"]), ("file5", nil), ] for (name, expected) in tests { let fileDesc = descSet.files.filter { $0.name == name }.first! let result = mapper.neededModules(forFile: fileDesc) if let expected = expected { XCTAssertEqual(result!, expected, "Looking for \(name)") } else { XCTAssertNil(result, "Looking for \(name)") } } } func test_neededModules_forFile_PublicImports() { // See the note in neededModules(forFile:) about how public import complicate things. // Given: // // + File: a.proto // message A {} // // + File: imports_a_publicly.proto // import public "a.proto"; // // message ImportsAPublicly { // A a = 1; // } // // + File: imports_imports_a_publicly.proto // import public "imports_a_publicly.proto"; // // message ImportsImportsAPublicly { // A a = 1; // } // // + File: uses_a_transitively.proto // import "imports_a_publicly.proto"; // // message UsesATransitively { // A a = 1; // } // // + File: uses_a_transitively2.proto // import "imports_imports_a_publicly.proto"; // // message UsesATransitively2 { // A a = 1; // } // // With a mapping file of: // // mapping { // module_name: "A" // proto_file_path: "a.proto" // } // mapping { // module_name: "ImportsAPublicly" // proto_file_path: "imports_a_publicly.proto" // } // mapping { // module_name: "ImportsImportsAPublicly" // proto_file_path: "imports_imports_a_publicly.proto" // } let configText = [ "mapping { module_name: \"A\", proto_file_path: \"a.proto\" }", "mapping { module_name: \"ImportsAPublicly\", proto_file_path: \"imports_a_publicly.proto\" }", "mapping { module_name: \"ImportsImportsAPublicly\", proto_file_path: \"imports_imports_a_publicly.proto\" }", ].joined(separator: "\n") let config = try! SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: configText) let mapper = try! ProtoFileToModuleMappings(moduleMappingsProto: config) let fileProtos = [ FileDescriptorProto(name: "a.proto"), FileDescriptorProto( name: "imports_a_publicly.proto", dependencies: ["a.proto"], publicDependencies: [0] ), FileDescriptorProto( name: "imports_imports_a_publicly.proto", dependencies: ["imports_a_publicly.proto"], publicDependencies: [0] ), FileDescriptorProto( name: "uses_a_transitively.proto", dependencies: ["imports_a_publicly.proto"] ), FileDescriptorProto( name: "uses_a_transitively2.proto", dependencies: ["imports_imports_a_publicly.proto"] ), ] let descSet = DescriptorSet(protos: fileProtos) // ( filename, [deps] ) let tests: [(String, [String]?)] = [ ("a.proto", nil), ("imports_a_publicly.proto", ["A"]), ("imports_imports_a_publicly.proto", ["A", "ImportsAPublicly"]), ("uses_a_transitively.proto", ["A", "ImportsAPublicly"]), ("uses_a_transitively2.proto", ["A", "ImportsAPublicly", "ImportsImportsAPublicly"]), ] for (name, expected) in tests { let fileDesc = descSet.files.filter { $0.name == name }.first! let result = mapper.neededModules(forFile: fileDesc) if let expected = expected { XCTAssertEqual(result!, expected, "Looking for \(name)") } else { XCTAssertNil(result, "Looking for \(name)") } } } }