├── .swiftlint.yml
├── .gitignore
├── Sources
├── Hypershard
│ └── main.swift
└── HypershardCore
│ ├── Path+ArgumentConvertible.swift
│ ├── CLI.swift
│ └── Tivan.swift
├── Resources
└── Xcode
│ ├── TivanUITests
│ ├── TivanUITests.swift
│ └── Info.plist
│ └── TivanTests.xcodeproj
│ └── project.pbxproj
├── Package.swift
├── README.md
├── Tests
└── TivanTests.swift
└── LICENSE
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | excluded:
2 | - .build
3 | disabled_rules:
4 | - closure_parameter_position
5 | - line_length
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Obj-C/Swift specific
2 | *.hmap
3 |
4 | # Swift Package Manager
5 | Packages/
6 | Package.pins
7 | Package.resolved
8 | *.xcodeproj
9 | .build/
10 |
--------------------------------------------------------------------------------
/Sources/Hypershard/main.swift:
--------------------------------------------------------------------------------
1 | // Hypershard - a ridiculously fast XCUITest collector
2 |
3 | import HypershardCore
4 |
5 | let hypershard = HypershardCore.CLI.entryPoint()
6 | hypershard.run()
7 |
--------------------------------------------------------------------------------
/Resources/Xcode/TivanUITests/TivanUITests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | class TivanUITests: XCTestCase {
4 | func testExample() {
5 | let app = XCUIApplication()
6 | app.launch()
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Sources/HypershardCore/Path+ArgumentConvertible.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Commander
3 | import PathKit
4 |
5 | extension Path: ArgumentConvertible {
6 | public init(parser: ArgumentParser) throws {
7 | if let path = parser.shift() {
8 | self.init(path)
9 | } else {
10 | throw ArgumentError.missingValue(argument: nil)
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Resources/Xcode/TivanUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.0
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "Hypershard",
6 | products: [
7 | .executable(
8 | name: "hypershard",
9 | targets: [ "Hypershard" ])
10 | ],
11 | dependencies: [
12 | .package(url: "https://github.com/kylef/PathKit.git", .upToNextMajor(from: "1.0.0")),
13 | .package(url: "https://github.com/jpsim/SourceKitten.git", .upToNextMajor(from: "0.26.0")),
14 | .package(url: "https://github.com/kylef/Commander.git", .upToNextMajor(from: "0.9.1")),
15 | .package(url: "https://github.com/tuist/Xcodeproj.git", .upToNextMajor(from: "7.1.0"))
16 | ],
17 | targets: [
18 | .target(
19 | name: "Hypershard",
20 | dependencies: [
21 | "HypershardCore"
22 | ]
23 | ),
24 | .target(
25 | name: "HypershardCore",
26 | dependencies: [
27 | "SourceKittenFramework",
28 | "PathKit",
29 | "Commander",
30 | "XcodeProj"
31 | ]
32 | ),
33 | .testTarget(
34 | name: "HypershardCoreTests",
35 | dependencies: [
36 | "HypershardCore"
37 | ],
38 | path: "Tests"
39 | )
40 | ]
41 | )
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ⚡ Hypershard
2 | the ridiculously fast `XCUITest` collector
3 |
4 | ## About
5 |
6 | Hypershard is a CLI tool that leverages SourceKit and Swift's Abstract Syntax Tree (AST) to parse our `XCUITest` files for the purposes of test collection. It makes a couple of assumptions about how the `XCUITest`s are organized:
7 | - it assumes that all files containing tests have suffix `Tests.swift`,
8 | - it assumes all test methods' names begin with the `test` prefix,
9 |
10 | In benchmarks, Hypershard takes, on average, under 0.06s per collection of tests from Dropbox and Paper iOS apps, down from roughly ~15 minutes it tooks us to collect tests by building of the individual apps.
11 |
12 | Check out the sibling Hypershard tool for Android, [dropbox/hypershard-android](https://github.com/dropbox/hypershard-android)!
13 |
14 | ## Building
15 | Hypershard is built using Swift Package Manager.
16 |
17 | To build Hypershard for development purposes, enter Hypershard's root directory and run:
18 |
19 | ```> swift build```
20 |
21 | This will check out the relevant dependencies and build the debug binary into the `.build` folder. The path for the resulting binary will be provided in the `swift build`'s output.
22 |
23 | To build Hypershard binary for direct distribution, enter Hypershard's root directory and run:
24 |
25 | ```swift build -c release```
26 |
27 | The resulting binary will be placed in the `.build/release/hypershard`.
28 |
29 | ## Running
30 | To run Hypershard, you have to follow this CLI invocation:
31 |
32 | `> hypershard TEST_TARGET_NAME ROOT_PATH --phase PHASE_NAME --path PATH --cmd CMD --output-file OUTPUT_PATH`
33 |
34 | - `TEST_TARGET_NAME` – the name of the Xcode test target containing the UI tests,
35 | - `ROOT_PATH` – either a path where all the `XCUITest`s clases are stored, or the path of the Xcode project containing `TEST_TARGET_NAME`,
36 | - `PHASE_NAME` – *optional* – name of the Changes phase,
37 | - `PATH` – *optional* – the custom `PATH` variable,
38 | - `CMD` – *optional* – the command to run each test with,
39 | - `OUTPUT_PATH` – *optional* – the path where the output JSON should be saved, if it's not passed, the output will be printed
40 |
41 | The first two parameters are required. You need to provide all optional parameters if you want an output consumable by [Changes](https://github.com/dropbox/changes). The final parameter is a path where the output JSON should be saved.
42 |
43 | For CI systems, we recommend to *not* rebuild Hypershard every time, and to store and use a static binary instead.
44 |
45 | ## Output
46 | There are two possible outputs from Hypershard:
47 | - an error due to a malformed Swift file or an incomprehensible test - the tool will output relevant information before aborting,
48 | - a JSON file containing a list of all available `XCUITest`s.
49 |
50 | ## Testing
51 | You can test Hypershard using Swift Package Manager, simply by running the following command in the Hypershard's root:
52 |
53 | ```> swift test```
54 |
--------------------------------------------------------------------------------
/Sources/HypershardCore/CLI.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import PathKit
3 | import Commander
4 |
5 | public class CLI {
6 | // MARK: - Public
7 |
8 | // returns a Commander-powered entry point to the Hypershard's CLI
9 | public class func entryPoint() -> CommandType {
10 | return command(
11 | Argument("testTargetName", description: "name of the Xcode target for XCUITests"),
12 | Argument("rootPath", description: "path to either the directory containing all XCUITests or the Xcode project"),
13 | Option("path", default: "", description: "The relevant PATH for shards"),
14 |
15 | Option("output-tests-only", default: "false", description: "When set to true, only output the test list"),
16 |
17 | Option("cmd", default: "", description: "The command to launch Xcode with"),
18 | Option("services-required", default: "false", description: "Whether need to wait for SandCastle init"),
19 | Option("phase", default: "XCUI test shard", description: "The build phase for which the shard is prepared"),
20 | Option("output-file", default: Path(), description: "File to output the final JSON to")
21 | ) { testTargetName, rootPath, path,
22 | testsOnly,
23 | cmd, servicesRequired, phase, outputFilePath in
24 | let configuration = Configuration(testTargetName: testTargetName,
25 | rootPath: rootPath)
26 |
27 | let tests = collectTests(withConfiguration: configuration)
28 | if testsOnly.boolValue {
29 | print(tests)
30 | } else {
31 | let outputFile = OutputFile(path: path,
32 | phase: phase,
33 | cmd: cmd,
34 | env: servicesRequired.boolValue ? ["SERVICES_REQUIRED": "1"] : [:],
35 | tests: tests)
36 | outputTests(outputFile: outputFile, outputFilePath: outputFilePath)
37 | }
38 | }
39 | }
40 |
41 | // MARK: - Internal
42 |
43 | struct Configuration {
44 | let testTargetName: String
45 | let rootPath: Path
46 | }
47 |
48 | struct OutputFile: Codable {
49 | let path: String
50 | let phase: String
51 | let cmd: String
52 | let env: [String: String]
53 | let tests: [ String ]
54 | }
55 |
56 | class func collectTests(withConfiguration configuration: Configuration) -> [ String ] {
57 | let testPaths = { () -> [Path] in
58 | let path = configuration.rootPath
59 | if path.string.hasSuffix(".xcodeproj") {
60 | return Tivan.getListOfTestFiles(inProject: path.normalize(),
61 | target: configuration.testTargetName)
62 | }
63 |
64 | // search the root test directory for test files
65 | return Tivan.getListOfTestFiles(inDirectory: path.normalize())
66 | }()
67 |
68 | // evaluate each test file for XCUITests
69 | let testNames = testPaths.reduce(into: [String: [String]](), { (testClassesToTestsMap, path) in
70 | let testClassesFromFile = Tivan.getListOfTests(inTestFile: path.normalize())
71 | testClassesFromFile.forEach({ (testName, tests) in
72 | testClassesToTestsMap[testName] = tests
73 | })
74 | })
75 |
76 | // flatten the sub-arrays into one array of tests
77 | let flatListOfTests = testNames.flatMap { (arg) -> [String] in
78 | let (testClassName, testList) = arg
79 | return testList.map({ (testName) -> String in
80 | "\(configuration.testTargetName).\(testClassName).\(testName)"
81 | })
82 | }
83 |
84 | return flatListOfTests
85 | }
86 |
87 | class func outputTests(outputFile: OutputFile, outputFilePath: Path) {
88 | guard let jsonData = try? JSONEncoder().encode(outputFile) else {
89 | fatalError("Could not encode the output as valid JSON")
90 | }
91 |
92 | // if the output file path is empty, just print to screen
93 | if outputFilePath == Path() {
94 | guard let jsonString = String(data: jsonData, encoding: .utf8) else {
95 | fatalError("Could not obtain the final JSON string")
96 | }
97 | print(jsonString)
98 | } else {
99 | do {
100 | try jsonData.write(to: outputFilePath.normalize().url)
101 | } catch {
102 | fatalError("Unable to write the output JSON file")
103 | }
104 | }
105 | }
106 | }
107 |
108 | extension String {
109 | var boolValue: Bool {
110 | return (self as NSString).boolValue
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Sources/HypershardCore/Tivan.swift:
--------------------------------------------------------------------------------
1 | import SourceKittenFramework
2 | import PathKit
3 | import XcodeProj
4 |
5 | // Tivan, the Hypershard's Test Collector
6 | public enum Tivan {
7 | // MARK: - Public
8 |
9 | // returns a list of Path objects for test files enabled in Xcode
10 | static func getListOfTestFiles(inProject path: Path, target: String) -> [Path] {
11 | guard let project = try? XcodeProj(path: path.normalize()) else {
12 | fatalError("Invalid path or Xcode project.")
13 | }
14 |
15 | guard let testTarget = project.pbxproj.targets(named: target).first else {
16 | fatalError("The UI test target named \(target) is missing.")
17 | }
18 |
19 | return getListOfTestFiles(inTarget: testTarget,
20 | rootPath: path.normalize().parent())
21 | }
22 |
23 | static func getListOfTestFiles(inTarget target: PBXTarget, rootPath: Path) -> [Path] {
24 | guard let sourceFiles = try? target.sourceFiles() else {
25 | fatalError("Missing source files in the \(target) target.")
26 | }
27 |
28 | return sourceFiles
29 | .compactMap { (try? $0.fullPath(sourceRoot: rootPath)) ?? nil }
30 | .filter { $0.string.hasSuffix("Tests.swift") }
31 | }
32 |
33 | // returns a list of Path objects for valid test files
34 | static func getListOfTestFiles(inDirectory path: Path) -> [Path] {
35 | guard let allFiles = try? path.children() else {
36 | fatalError("Cannot obtain contents of the directory")
37 | }
38 |
39 | let allTests = allFiles.filter { $0.string.hasSuffix("Tests.swift") }
40 | return allTests
41 | }
42 |
43 | // returns the list of individual XCUITests in the test class
44 | static func getListOfTests(inTestFile path: Path) -> [String: [String]] {
45 | let testFile = File(path: path.string)
46 | guard let structure = try? Structure(file: testFile!) else {
47 | fatalError("Could not parse the XCUITest's Swift structure")
48 | }
49 | return getListOfTests(fromStructure: structure)
50 | }
51 |
52 | // MARK: - Internal
53 |
54 | // Enums and Structs for Tivan
55 | enum NodeType: String {
56 | case `class` = "source.lang.swift.decl.class"
57 | case instanceMethod = "source.lang.swift.decl.function.method.instance"
58 | case `extension` = "source.lang.swift.decl.extension" // NOTE:(bogo) this is meant for testing only
59 |
60 | var allowedAsRoot: Bool {
61 | switch self {
62 | case .class: return true
63 | case .extension: return false
64 | case .instanceMethod: return false
65 | }
66 | }
67 | }
68 |
69 | enum KeyNames {
70 | static let Kind = "key.kind"
71 | static let Name = "key.name"
72 | static let Substructure = "key.substructure"
73 | }
74 |
75 | struct Node {
76 | var type: NodeType
77 | var name: String
78 | var subnodes: [Node]
79 | }
80 |
81 | // gets lists of tests from a SourceKit Structure object
82 | static func getListOfTests(fromStructure structure: Structure) -> [String: [String]] {
83 | // there's a diagnostic wrapper we need to blow off first
84 | guard let diagnosticSubstructuresArray = structure.dictionary[KeyNames.Substructure] as? [[String: SourceKitRepresentable]] else {
85 | fatalError("The XCUITest doesn't make sense after removing the diagnostic wrapper.")
86 | }
87 |
88 | // parse all structures into Tivan nodes
89 | let structuresAsNodes = diagnosticSubstructuresArray.compactMap { (diagnosticSubstructure) -> Node? in
90 | parseStructure(diagnosticSubstructure)
91 | }
92 |
93 | // there will usually be a number of valid resulting nodes, including extensions, empty classes, and helper
94 | // classes. only classes with test methods, with name ending in `Test` are valid for collection purposes
95 | let filteredStructures = structuresAsNodes.filter { (node) -> Bool in
96 | if !node.type.allowedAsRoot {
97 | return false
98 | }
99 |
100 | if node.subnodes.count == 0 {
101 | return false
102 | }
103 |
104 | if !node.name.hasSuffix("Tests") {
105 | return false
106 | }
107 |
108 | return true
109 | }
110 |
111 | // finally, reduce the filtered structures into a dictionary
112 | let listOfTests = filteredStructures.reduce(into: [String: [String]]()) { (listOfTests, node) in
113 | let testsPerClass = node.subnodes.filter { (node) -> Bool in
114 | node.name.hasPrefix("test")
115 | }.map { (node) -> String in
116 | // drop the braces at the end
117 | String(node.name.dropLast(2))
118 | }
119 |
120 | listOfTests[node.name] = testsPerClass
121 | }
122 |
123 | return listOfTests
124 | }
125 |
126 | // parses the SourceKit's structure to obtain class/method representation
127 | static func parseStructure(_ structure: [String: SourceKitRepresentable]) -> Node? {
128 | guard let nodeTypeString = structure[KeyNames.Kind] as? String else {
129 | return nil
130 | }
131 |
132 | guard let nodeType = NodeType(rawValue: nodeTypeString) else {
133 | return nil
134 | }
135 |
136 | guard let nodeNameString = structure[KeyNames.Name] as? String else {
137 | return nil
138 | }
139 |
140 | guard let subnodesCollection = structure[KeyNames.Substructure] as? [[String: SourceKitRepresentable]] else {
141 | return Node(type: nodeType, name: nodeNameString, subnodes: [])
142 | }
143 |
144 | let subnodes = subnodesCollection.compactMap { (structure) -> Node? in
145 | parseStructure(structure)
146 | }
147 |
148 | return Node(type: nodeType, name: nodeNameString, subnodes: subnodes)
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/Tests/TivanTests.swift:
--------------------------------------------------------------------------------
1 | // Hypershard - test for Tivan
2 |
3 | import XCTest
4 | import PathKit
5 | import SourceKittenFramework
6 | @testable import HypershardCore
7 |
8 | typealias MockSubstructure = [String: SourceKitRepresentable]
9 |
10 | extension Dictionary where Key == String, Value == SourceKitRepresentable {
11 | func asStructure() -> Structure {
12 | return Structure(sourceKitResponse: self)
13 | }
14 | }
15 |
16 | struct TivanMocks {
17 |
18 | // Specific object type mocks
19 |
20 | static func mockDiagnosticWrapper(_ substructures: [MockSubstructure]) -> MockSubstructure {
21 | return [
22 | // the diagnostic file wrapper
23 | Tivan.KeyNames.Substructure: substructures
24 | ]
25 | }
26 |
27 | static func mockClass(named className: String, withMethods methods: [MockSubstructure]) -> MockSubstructure {
28 | return self.mockObject(ofKind: Tivan.NodeType.class, named: className, withSubstructure: methods)
29 | }
30 |
31 | static func mockMethod(named methodName: String) -> MockSubstructure {
32 | return self.mockObject(ofKind: Tivan.NodeType.instanceMethod, named: methodName + "()")
33 | }
34 |
35 | static func mockExtension(named extensionName: String) -> MockSubstructure {
36 | return self.mockObject(ofKind: Tivan.NodeType.extension, named: extensionName)
37 | }
38 |
39 | // Generic object mocks
40 |
41 | static func mockObject(ofKind kind: Tivan.NodeType) -> MockSubstructure {
42 | return [
43 | Tivan.KeyNames.Kind: kind.rawValue
44 | ]
45 | }
46 |
47 | static func mockObject(ofKind kind: Tivan.NodeType, named name: String) -> MockSubstructure {
48 | return [
49 | Tivan.KeyNames.Kind: kind.rawValue,
50 | Tivan.KeyNames.Name: name
51 | ]
52 | }
53 |
54 | static func mockObject(ofKind kind: Tivan.NodeType,
55 | named name: String,
56 | withSubstructure substructure: [MockSubstructure]) -> MockSubstructure {
57 | return [
58 | Tivan.KeyNames.Kind: kind.rawValue,
59 | Tivan.KeyNames.Name: name,
60 | Tivan.KeyNames.Substructure: substructure
61 | ]
62 | }
63 | }
64 |
65 | class TivanTests: XCTestCase {
66 | enum ClassNames {
67 | static let firstClassName = "FirstClassTests"
68 | static let secondClassName = "SecondClassTests"
69 | static let invalidClassName = "InvalidClassTest"
70 | }
71 |
72 | enum MethodNames {
73 | static let setupMethodName = "setUp"
74 | static let firstTestMethodName = "testMethod"
75 | static let secondTestMethodName = "testMethod2"
76 | static let invalidTestMethodName = "yoloMethod"
77 | }
78 |
79 | func testXcodeReading() {
80 | let paths = Tivan.getListOfTestFiles(inProject: Path("Resources/Xcode/TivanTests.xcodeproj"),
81 | target: "TivanUITests")
82 | XCTAssert(paths.count > 0, "There must be a non-zero list of files with UI tests returned.")
83 |
84 | guard let firstPath = paths.first else {
85 | XCTFail("There must be at least one test file parsed out of the project.")
86 | return
87 | }
88 |
89 | let tests = Tivan.getListOfTests(inTestFile: firstPath)
90 | guard let testClass = tests.values.first else {
91 | XCTFail("There must be at least one test class parsed in the test file.")
92 | return
93 | }
94 |
95 | XCTAssert(testClass.count > 0, "There must be at least one test case parsed out of the test class.")
96 | }
97 |
98 | func testDiagnosticStripping() {
99 | let mockStructure = TivanMocks.mockDiagnosticWrapper([])
100 |
101 | let listOfTests = Tivan.getListOfTests(fromStructure: mockStructure.asStructure())
102 |
103 | XCTAssertEqual(listOfTests.keys.count, 0, "The test list should be completely empty")
104 | }
105 |
106 | func testBasicTestCollecting() {
107 | let mockStructure = TivanMocks.mockDiagnosticWrapper([
108 | TivanMocks.mockClass(named: ClassNames.firstClassName,
109 | withMethods: [
110 | TivanMocks.mockMethod(named: MethodNames.setupMethodName),
111 | TivanMocks.mockMethod(named: MethodNames.firstTestMethodName),
112 | TivanMocks.mockMethod(named: MethodNames.secondTestMethodName)
113 | ])
114 | ])
115 |
116 | let listOfTests = Tivan.getListOfTests(fromStructure: mockStructure.asStructure())
117 | XCTAssertEqual(listOfTests.count, 1)
118 | XCTAssertTrue(listOfTests.keys.contains(ClassNames.firstClassName))
119 |
120 | guard let testsForClass = listOfTests[ClassNames.firstClassName] else {
121 | XCTAssert(false, "Cannot open the test results")
122 | return
123 | }
124 |
125 | XCTAssertEqual(testsForClass.count, 2, "The test list should contain only two tests")
126 | XCTAssertTrue(testsForClass.contains(MethodNames.firstTestMethodName))
127 | XCTAssertTrue(testsForClass.contains(MethodNames.secondTestMethodName))
128 | XCTAssertFalse(testsForClass.contains(MethodNames.setupMethodName))
129 | XCTAssertFalse(testsForClass.contains(MethodNames.invalidTestMethodName))
130 | }
131 |
132 | func testSkippingInvalidClasses() {
133 | let mockStructure = TivanMocks.mockDiagnosticWrapper([
134 | TivanMocks.mockClass(named: ClassNames.firstClassName, withMethods: []),
135 | TivanMocks.mockClass(named: ClassNames.invalidClassName, withMethods: [])
136 | ])
137 |
138 | let listOfTests = Tivan.getListOfTests(fromStructure: mockStructure.asStructure())
139 | XCTAssertEqual(listOfTests.keys.count, 0, "There should be no valid classes")
140 | }
141 |
142 | func testMultipleClasses() {
143 | let mockStructure = TivanMocks.mockDiagnosticWrapper([
144 | TivanMocks.mockClass(named: ClassNames.firstClassName,
145 | withMethods: [
146 | TivanMocks.mockMethod(named: MethodNames.setupMethodName),
147 | TivanMocks.mockMethod(named: MethodNames.firstTestMethodName)
148 | ]),
149 | TivanMocks.mockClass(named: ClassNames.secondClassName,
150 | withMethods: [
151 | TivanMocks.mockMethod(named: MethodNames.setupMethodName),
152 | TivanMocks.mockMethod(named: MethodNames.firstTestMethodName),
153 | TivanMocks.mockMethod(named: MethodNames.secondTestMethodName)
154 | ])
155 | ])
156 |
157 | let listOfTests = Tivan.getListOfTests(fromStructure: mockStructure.asStructure())
158 | XCTAssertEqual(listOfTests.keys.count, 2)
159 |
160 | XCTAssertTrue(listOfTests.keys.contains(ClassNames.firstClassName))
161 | XCTAssertEqual(listOfTests[ClassNames.firstClassName]?.count, 1)
162 |
163 | XCTAssertTrue(listOfTests.keys.contains(ClassNames.secondClassName))
164 | XCTAssertEqual(listOfTests[ClassNames.secondClassName]?.count, 2)
165 | }
166 |
167 | func testForStrippingNonTestClasses() {
168 | let mockStructure = TivanMocks.mockDiagnosticWrapper([
169 | TivanMocks.mockExtension(named: "TestsExtension"),
170 | TivanMocks.mockClass(named: "TestingMock",
171 | withMethods: [
172 | TivanMocks.mockMethod(named: MethodNames.firstTestMethodName)
173 | ]),
174 | TivanMocks.mockClass(named: ClassNames.firstClassName,
175 | withMethods: [
176 | TivanMocks.mockMethod(named: MethodNames.setupMethodName),
177 | TivanMocks.mockMethod(named: MethodNames.firstTestMethodName)
178 | ]),
179 | TivanMocks.mockClass(named: ClassNames.secondClassName,
180 | withMethods: [
181 | TivanMocks.mockMethod(named: MethodNames.setupMethodName),
182 | TivanMocks.mockMethod(named: MethodNames.firstTestMethodName),
183 | TivanMocks.mockMethod(named: MethodNames.secondTestMethodName)
184 | ])
185 | ])
186 |
187 | let listOfTests = Tivan.getListOfTests(fromStructure: mockStructure.asStructure())
188 | XCTAssertEqual(listOfTests.count, 2, "Incorrect number of test classes")
189 |
190 | XCTAssertTrue(listOfTests.keys.contains(ClassNames.firstClassName))
191 | XCTAssertTrue(listOfTests[ClassNames.firstClassName]?.count == 1)
192 |
193 | XCTAssertTrue(listOfTests.keys.contains(ClassNames.secondClassName))
194 | XCTAssertTrue(listOfTests[ClassNames.secondClassName]?.count == 2)
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright (c) 2019 Dropbox, Inc.
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/Resources/Xcode/TivanTests.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | DFA12D0B234E66CE005E4D12 /* TivanUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFA12D0A234E66CE005E4D12 /* TivanUITests.swift */; };
11 | /* End PBXBuildFile section */
12 |
13 | /* Begin PBXContainerItemProxy section */
14 | DFA12D0D234E66CE005E4D12 /* PBXContainerItemProxy */ = {
15 | isa = PBXContainerItemProxy;
16 | containerPortal = DFA12CE5234E66A0005E4D12 /* Project object */;
17 | proxyType = 1;
18 | remoteGlobalIDString = DFA12CEC234E66A0005E4D12;
19 | remoteInfo = TivanTests;
20 | };
21 | /* End PBXContainerItemProxy section */
22 |
23 | /* Begin PBXFileReference section */
24 | DFA12CED234E66A0005E4D12 /* TivanTests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TivanTests.app; sourceTree = BUILT_PRODUCTS_DIR; };
25 | DFA12D08234E66CE005E4D12 /* TivanUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TivanUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
26 | DFA12D0A234E66CE005E4D12 /* TivanUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TivanUITests.swift; sourceTree = ""; };
27 | DFA12D0C234E66CE005E4D12 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
28 | /* End PBXFileReference section */
29 |
30 | /* Begin PBXFrameworksBuildPhase section */
31 | DFA12CEA234E66A0005E4D12 /* Frameworks */ = {
32 | isa = PBXFrameworksBuildPhase;
33 | buildActionMask = 2147483647;
34 | files = (
35 | );
36 | runOnlyForDeploymentPostprocessing = 0;
37 | };
38 | DFA12D05234E66CE005E4D12 /* Frameworks */ = {
39 | isa = PBXFrameworksBuildPhase;
40 | buildActionMask = 2147483647;
41 | files = (
42 | );
43 | runOnlyForDeploymentPostprocessing = 0;
44 | };
45 | /* End PBXFrameworksBuildPhase section */
46 |
47 | /* Begin PBXGroup section */
48 | DFA12CE4234E66A0005E4D12 = {
49 | isa = PBXGroup;
50 | children = (
51 | DFA12D09234E66CE005E4D12 /* TivanUITests */,
52 | DFA12CEE234E66A0005E4D12 /* Products */,
53 | );
54 | sourceTree = "";
55 | };
56 | DFA12CEE234E66A0005E4D12 /* Products */ = {
57 | isa = PBXGroup;
58 | children = (
59 | DFA12CED234E66A0005E4D12 /* TivanTests.app */,
60 | DFA12D08234E66CE005E4D12 /* TivanUITests.xctest */,
61 | );
62 | name = Products;
63 | sourceTree = "";
64 | };
65 | DFA12D09234E66CE005E4D12 /* TivanUITests */ = {
66 | isa = PBXGroup;
67 | children = (
68 | DFA12D0A234E66CE005E4D12 /* TivanUITests.swift */,
69 | DFA12D0C234E66CE005E4D12 /* Info.plist */,
70 | );
71 | path = TivanUITests;
72 | sourceTree = "";
73 | };
74 | /* End PBXGroup section */
75 |
76 | /* Begin PBXNativeTarget section */
77 | DFA12CEC234E66A0005E4D12 /* TivanTests */ = {
78 | isa = PBXNativeTarget;
79 | buildConfigurationList = DFA12D01234E66A3005E4D12 /* Build configuration list for PBXNativeTarget "TivanTests" */;
80 | buildPhases = (
81 | DFA12CE9234E66A0005E4D12 /* Sources */,
82 | DFA12CEA234E66A0005E4D12 /* Frameworks */,
83 | DFA12CEB234E66A0005E4D12 /* Resources */,
84 | );
85 | buildRules = (
86 | );
87 | dependencies = (
88 | );
89 | name = TivanTests;
90 | productName = TivanTests;
91 | productReference = DFA12CED234E66A0005E4D12 /* TivanTests.app */;
92 | productType = "com.apple.product-type.application";
93 | };
94 | DFA12D07234E66CE005E4D12 /* TivanUITests */ = {
95 | isa = PBXNativeTarget;
96 | buildConfigurationList = DFA12D0F234E66CE005E4D12 /* Build configuration list for PBXNativeTarget "TivanUITests" */;
97 | buildPhases = (
98 | DFA12D04234E66CE005E4D12 /* Sources */,
99 | DFA12D05234E66CE005E4D12 /* Frameworks */,
100 | DFA12D06234E66CE005E4D12 /* Resources */,
101 | );
102 | buildRules = (
103 | );
104 | dependencies = (
105 | DFA12D0E234E66CE005E4D12 /* PBXTargetDependency */,
106 | );
107 | name = TivanUITests;
108 | productName = TivanUITests;
109 | productReference = DFA12D08234E66CE005E4D12 /* TivanUITests.xctest */;
110 | productType = "com.apple.product-type.bundle.ui-testing";
111 | };
112 | /* End PBXNativeTarget section */
113 |
114 | /* Begin PBXProject section */
115 | DFA12CE5234E66A0005E4D12 /* Project object */ = {
116 | isa = PBXProject;
117 | attributes = {
118 | LastSwiftUpdateCheck = 1110;
119 | LastUpgradeCheck = 1110;
120 | ORGANIZATIONNAME = "Dropbox, Inc.";
121 | TargetAttributes = {
122 | DFA12CEC234E66A0005E4D12 = {
123 | CreatedOnToolsVersion = 11.1;
124 | };
125 | DFA12D07234E66CE005E4D12 = {
126 | CreatedOnToolsVersion = 11.1;
127 | TestTargetID = DFA12CEC234E66A0005E4D12;
128 | };
129 | };
130 | };
131 | buildConfigurationList = DFA12CE8234E66A0005E4D12 /* Build configuration list for PBXProject "TivanTests" */;
132 | compatibilityVersion = "Xcode 9.3";
133 | developmentRegion = en;
134 | hasScannedForEncodings = 0;
135 | knownRegions = (
136 | en,
137 | Base,
138 | );
139 | mainGroup = DFA12CE4234E66A0005E4D12;
140 | productRefGroup = DFA12CEE234E66A0005E4D12 /* Products */;
141 | projectDirPath = "";
142 | projectRoot = "";
143 | targets = (
144 | DFA12CEC234E66A0005E4D12 /* TivanTests */,
145 | DFA12D07234E66CE005E4D12 /* TivanUITests */,
146 | );
147 | };
148 | /* End PBXProject section */
149 |
150 | /* Begin PBXResourcesBuildPhase section */
151 | DFA12CEB234E66A0005E4D12 /* Resources */ = {
152 | isa = PBXResourcesBuildPhase;
153 | buildActionMask = 2147483647;
154 | files = (
155 | );
156 | runOnlyForDeploymentPostprocessing = 0;
157 | };
158 | DFA12D06234E66CE005E4D12 /* Resources */ = {
159 | isa = PBXResourcesBuildPhase;
160 | buildActionMask = 2147483647;
161 | files = (
162 | );
163 | runOnlyForDeploymentPostprocessing = 0;
164 | };
165 | /* End PBXResourcesBuildPhase section */
166 |
167 | /* Begin PBXSourcesBuildPhase section */
168 | DFA12CE9234E66A0005E4D12 /* Sources */ = {
169 | isa = PBXSourcesBuildPhase;
170 | buildActionMask = 2147483647;
171 | files = (
172 | );
173 | runOnlyForDeploymentPostprocessing = 0;
174 | };
175 | DFA12D04234E66CE005E4D12 /* Sources */ = {
176 | isa = PBXSourcesBuildPhase;
177 | buildActionMask = 2147483647;
178 | files = (
179 | DFA12D0B234E66CE005E4D12 /* TivanUITests.swift in Sources */,
180 | );
181 | runOnlyForDeploymentPostprocessing = 0;
182 | };
183 | /* End PBXSourcesBuildPhase section */
184 |
185 | /* Begin PBXTargetDependency section */
186 | DFA12D0E234E66CE005E4D12 /* PBXTargetDependency */ = {
187 | isa = PBXTargetDependency;
188 | target = DFA12CEC234E66A0005E4D12 /* TivanTests */;
189 | targetProxy = DFA12D0D234E66CE005E4D12 /* PBXContainerItemProxy */;
190 | };
191 | /* End PBXTargetDependency section */
192 |
193 | /* Begin XCBuildConfiguration section */
194 | DFA12CFF234E66A3005E4D12 /* Debug */ = {
195 | isa = XCBuildConfiguration;
196 | buildSettings = {
197 | ALWAYS_SEARCH_USER_PATHS = NO;
198 | CLANG_ANALYZER_NONNULL = YES;
199 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
200 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
201 | CLANG_CXX_LIBRARY = "libc++";
202 | CLANG_ENABLE_MODULES = YES;
203 | CLANG_ENABLE_OBJC_ARC = YES;
204 | CLANG_ENABLE_OBJC_WEAK = YES;
205 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
206 | CLANG_WARN_BOOL_CONVERSION = YES;
207 | CLANG_WARN_COMMA = YES;
208 | CLANG_WARN_CONSTANT_CONVERSION = YES;
209 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
210 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
211 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
212 | CLANG_WARN_EMPTY_BODY = YES;
213 | CLANG_WARN_ENUM_CONVERSION = YES;
214 | CLANG_WARN_INFINITE_RECURSION = YES;
215 | CLANG_WARN_INT_CONVERSION = YES;
216 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
217 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
218 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
219 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
220 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
221 | CLANG_WARN_STRICT_PROTOTYPES = YES;
222 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
223 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
224 | CLANG_WARN_UNREACHABLE_CODE = YES;
225 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
226 | COPY_PHASE_STRIP = NO;
227 | DEBUG_INFORMATION_FORMAT = dwarf;
228 | ENABLE_STRICT_OBJC_MSGSEND = YES;
229 | ENABLE_TESTABILITY = YES;
230 | GCC_C_LANGUAGE_STANDARD = gnu11;
231 | GCC_DYNAMIC_NO_PIC = NO;
232 | GCC_NO_COMMON_BLOCKS = YES;
233 | GCC_OPTIMIZATION_LEVEL = 0;
234 | GCC_PREPROCESSOR_DEFINITIONS = (
235 | "DEBUG=1",
236 | "$(inherited)",
237 | );
238 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
239 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
240 | GCC_WARN_UNDECLARED_SELECTOR = YES;
241 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
242 | GCC_WARN_UNUSED_FUNCTION = YES;
243 | GCC_WARN_UNUSED_VARIABLE = YES;
244 | IPHONEOS_DEPLOYMENT_TARGET = 13.1;
245 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
246 | MTL_FAST_MATH = YES;
247 | ONLY_ACTIVE_ARCH = YES;
248 | SDKROOT = iphoneos;
249 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
250 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
251 | };
252 | name = Debug;
253 | };
254 | DFA12D00234E66A3005E4D12 /* Release */ = {
255 | isa = XCBuildConfiguration;
256 | buildSettings = {
257 | ALWAYS_SEARCH_USER_PATHS = NO;
258 | CLANG_ANALYZER_NONNULL = YES;
259 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
261 | CLANG_CXX_LIBRARY = "libc++";
262 | CLANG_ENABLE_MODULES = YES;
263 | CLANG_ENABLE_OBJC_ARC = YES;
264 | CLANG_ENABLE_OBJC_WEAK = YES;
265 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
266 | CLANG_WARN_BOOL_CONVERSION = YES;
267 | CLANG_WARN_COMMA = YES;
268 | CLANG_WARN_CONSTANT_CONVERSION = YES;
269 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
270 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
271 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
272 | CLANG_WARN_EMPTY_BODY = YES;
273 | CLANG_WARN_ENUM_CONVERSION = YES;
274 | CLANG_WARN_INFINITE_RECURSION = YES;
275 | CLANG_WARN_INT_CONVERSION = YES;
276 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
277 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
278 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
279 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
280 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
281 | CLANG_WARN_STRICT_PROTOTYPES = YES;
282 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
283 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
284 | CLANG_WARN_UNREACHABLE_CODE = YES;
285 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
286 | COPY_PHASE_STRIP = NO;
287 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
288 | ENABLE_NS_ASSERTIONS = NO;
289 | ENABLE_STRICT_OBJC_MSGSEND = YES;
290 | GCC_C_LANGUAGE_STANDARD = gnu11;
291 | GCC_NO_COMMON_BLOCKS = YES;
292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
294 | GCC_WARN_UNDECLARED_SELECTOR = YES;
295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
296 | GCC_WARN_UNUSED_FUNCTION = YES;
297 | GCC_WARN_UNUSED_VARIABLE = YES;
298 | IPHONEOS_DEPLOYMENT_TARGET = 13.1;
299 | MTL_ENABLE_DEBUG_INFO = NO;
300 | MTL_FAST_MATH = YES;
301 | SDKROOT = iphoneos;
302 | SWIFT_COMPILATION_MODE = wholemodule;
303 | SWIFT_OPTIMIZATION_LEVEL = "-O";
304 | VALIDATE_PRODUCT = YES;
305 | };
306 | name = Release;
307 | };
308 | DFA12D02234E66A3005E4D12 /* Debug */ = {
309 | isa = XCBuildConfiguration;
310 | buildSettings = {
311 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
312 | CODE_SIGN_STYLE = Automatic;
313 | INFOPLIST_FILE = TivanTests/Info.plist;
314 | LD_RUNPATH_SEARCH_PATHS = (
315 | "$(inherited)",
316 | "@executable_path/Frameworks",
317 | );
318 | PRODUCT_BUNDLE_IDENTIFIER = com.getdropbox.TivanTests;
319 | PRODUCT_NAME = "$(TARGET_NAME)";
320 | SWIFT_VERSION = 5.0;
321 | TARGETED_DEVICE_FAMILY = "1,2";
322 | };
323 | name = Debug;
324 | };
325 | DFA12D03234E66A3005E4D12 /* Release */ = {
326 | isa = XCBuildConfiguration;
327 | buildSettings = {
328 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
329 | CODE_SIGN_STYLE = Automatic;
330 | INFOPLIST_FILE = TivanTests/Info.plist;
331 | LD_RUNPATH_SEARCH_PATHS = (
332 | "$(inherited)",
333 | "@executable_path/Frameworks",
334 | );
335 | PRODUCT_BUNDLE_IDENTIFIER = com.getdropbox.TivanTests;
336 | PRODUCT_NAME = "$(TARGET_NAME)";
337 | SWIFT_VERSION = 5.0;
338 | TARGETED_DEVICE_FAMILY = "1,2";
339 | };
340 | name = Release;
341 | };
342 | DFA12D10234E66CE005E4D12 /* Debug */ = {
343 | isa = XCBuildConfiguration;
344 | buildSettings = {
345 | CODE_SIGN_STYLE = Automatic;
346 | INFOPLIST_FILE = TivanUITests/Info.plist;
347 | LD_RUNPATH_SEARCH_PATHS = (
348 | "$(inherited)",
349 | "@executable_path/Frameworks",
350 | "@loader_path/Frameworks",
351 | );
352 | PRODUCT_BUNDLE_IDENTIFIER = com.getdropbox.TivanUITests;
353 | PRODUCT_NAME = "$(TARGET_NAME)";
354 | SWIFT_VERSION = 5.0;
355 | TARGETED_DEVICE_FAMILY = "1,2";
356 | TEST_TARGET_NAME = TivanTests;
357 | };
358 | name = Debug;
359 | };
360 | DFA12D11234E66CE005E4D12 /* Release */ = {
361 | isa = XCBuildConfiguration;
362 | buildSettings = {
363 | CODE_SIGN_STYLE = Automatic;
364 | INFOPLIST_FILE = TivanUITests/Info.plist;
365 | LD_RUNPATH_SEARCH_PATHS = (
366 | "$(inherited)",
367 | "@executable_path/Frameworks",
368 | "@loader_path/Frameworks",
369 | );
370 | PRODUCT_BUNDLE_IDENTIFIER = com.getdropbox.TivanUITests;
371 | PRODUCT_NAME = "$(TARGET_NAME)";
372 | SWIFT_VERSION = 5.0;
373 | TARGETED_DEVICE_FAMILY = "1,2";
374 | TEST_TARGET_NAME = TivanTests;
375 | };
376 | name = Release;
377 | };
378 | /* End XCBuildConfiguration section */
379 |
380 | /* Begin XCConfigurationList section */
381 | DFA12CE8234E66A0005E4D12 /* Build configuration list for PBXProject "TivanTests" */ = {
382 | isa = XCConfigurationList;
383 | buildConfigurations = (
384 | DFA12CFF234E66A3005E4D12 /* Debug */,
385 | DFA12D00234E66A3005E4D12 /* Release */,
386 | );
387 | defaultConfigurationIsVisible = 0;
388 | defaultConfigurationName = Release;
389 | };
390 | DFA12D01234E66A3005E4D12 /* Build configuration list for PBXNativeTarget "TivanTests" */ = {
391 | isa = XCConfigurationList;
392 | buildConfigurations = (
393 | DFA12D02234E66A3005E4D12 /* Debug */,
394 | DFA12D03234E66A3005E4D12 /* Release */,
395 | );
396 | defaultConfigurationIsVisible = 0;
397 | defaultConfigurationName = Release;
398 | };
399 | DFA12D0F234E66CE005E4D12 /* Build configuration list for PBXNativeTarget "TivanUITests" */ = {
400 | isa = XCConfigurationList;
401 | buildConfigurations = (
402 | DFA12D10234E66CE005E4D12 /* Debug */,
403 | DFA12D11234E66CE005E4D12 /* Release */,
404 | );
405 | defaultConfigurationIsVisible = 0;
406 | defaultConfigurationName = Release;
407 | };
408 | /* End XCConfigurationList section */
409 | };
410 | rootObject = DFA12CE5234E66A0005E4D12 /* Project object */;
411 | }
412 |
--------------------------------------------------------------------------------