diff --git a/Generator/Sources/NeedleFramework/Models/Component.swift b/Generator/Sources/NeedleFramework/Models/Component.swift index 88b454c3..faaf39ab 100644 --- a/Generator/Sources/NeedleFramework/Models/Component.swift +++ b/Generator/Sources/NeedleFramework/Models/Component.swift @@ -53,10 +53,7 @@ class ASTComponent { let parentValues = parents.map { (parent: ASTComponent) -> Component in parent.valueType } - guard let dependency = dependencyProtocol else { - fatalError("\(self)'s dependency protocol data model is not yet linked.") - } - return Component(name: name, properties: properties, parents: parentValues, dependency: dependency) + return Component(name: name, properties: properties, parents: parentValues, dependency: dependencyProtocol!) } /// Initializer. diff --git a/Generator/Sources/NeedleFramework/Models/Dependency.swift b/Generator/Sources/NeedleFramework/Models/Dependency.swift index 4c30c02b..5193fb93 100644 --- a/Generator/Sources/NeedleFramework/Models/Dependency.swift +++ b/Generator/Sources/NeedleFramework/Models/Dependency.swift @@ -28,4 +28,12 @@ struct Dependency: Equatable { let name: String /// The list of dependency properties. let properties: [Property] + + /// Check if the dependency name is an empty dependency. + /// + /// - returns: `true` if this dependency prootocol is the `EmptyDependency`. + /// `false` otherwise. + static func isEmptyDependency(name: String) -> Bool { + return name == "EmptyDependency" + } } diff --git a/Generator/Sources/NeedleFramework/Models/DependencyProvider.swift b/Generator/Sources/NeedleFramework/Models/DependencyProvider.swift index b6a190d9..5c641ae2 100644 --- a/Generator/Sources/NeedleFramework/Models/DependencyProvider.swift +++ b/Generator/Sources/NeedleFramework/Models/DependencyProvider.swift @@ -55,7 +55,7 @@ struct ProcessedDependencyProvider { /// `true` if this provider's dependency prootocol is the `EmptyDependency`. /// `false` otherwise. var isEmptyDependency: Bool { - return unprocessed.dependency.name == "EmptyDependency" + return Dependency.isEmptyDependency(name: unprocessed.dependency.name) } } diff --git a/Generator/Sources/NeedleFramework/Models/Pluginized/PluginizableComponent.swift b/Generator/Sources/NeedleFramework/Models/Pluginized/PluginizableComponent.swift index 2e96e3bd..b6bac1ef 100644 --- a/Generator/Sources/NeedleFramework/Models/Pluginized/PluginizableComponent.swift +++ b/Generator/Sources/NeedleFramework/Models/Pluginized/PluginizableComponent.swift @@ -30,6 +30,13 @@ class PluginizableASTComponent { let nonCoreComponentType: String /// The linked non-core component. var nonCoreComponent: ASTComponent? + /// The linked plugin extension. + var pluginExtension: PluginExtension? + + /// Convert the mutable reference type into a thread-safe value type. + var valueType: PluginizableComponent { + return PluginizableComponent(data: data.valueType, nonCoreComponent: nonCoreComponent!.valueType, pluginExtension: pluginExtension!) + } /// Initializer. /// @@ -51,4 +58,6 @@ struct PluginizableComponent { let data: Component /// The non-core component. let nonCoreComponent: Component + /// The plugin extension. + let pluginExtension: PluginExtension } diff --git a/Generator/Sources/NeedleFramework/Parsing/DependencyGraphParser.swift b/Generator/Sources/NeedleFramework/Parsing/DependencyGraphParser.swift index 4c30d026..0fe8b5c0 100644 --- a/Generator/Sources/NeedleFramework/Parsing/DependencyGraphParser.swift +++ b/Generator/Sources/NeedleFramework/Parsing/DependencyGraphParser.swift @@ -14,9 +14,7 @@ // limitations under the License. // -import Basic import Foundation -import SourceKittenFramework /// Errors that can occur during parsing of the dependency graph from /// Swift sources. @@ -39,7 +37,8 @@ class DependencyGraphParser { /// in this list, the said file is excluded from parsing. /// - parameter executor: The executor to use for concurrent processing /// of files. - /// - returns: The list of component data models. + /// - returns: The list of component data models and sorted import + /// statements. /// - throws: `DependencyGraphParserError.timeout` if parsing a Swift /// source timed out. func parse(from rootUrl: URL, excludingFilesWithSuffixes exclusionSuffixes: [String] = [], using executor: SequenceExecutor) throws -> (components: [Component], imports: [String]) { diff --git a/Generator/Sources/NeedleFramework/Parsing/Pluginized/PluginizableDependencyGraphParser.swift b/Generator/Sources/NeedleFramework/Parsing/Pluginized/PluginizableDependencyGraphParser.swift index c4985d23..74c704ed 100644 --- a/Generator/Sources/NeedleFramework/Parsing/Pluginized/PluginizableDependencyGraphParser.swift +++ b/Generator/Sources/NeedleFramework/Parsing/Pluginized/PluginizableDependencyGraphParser.swift @@ -14,9 +14,7 @@ // limitations under the License. // -import Basic import Foundation -import SourceKittenFramework /// The entry utility for the parsing phase. The parser deeply scans a /// directory and parses the relevant Swift source files, and finally @@ -32,10 +30,11 @@ class PluginizableDependencyGraphParser { /// in this list, the said file is excluded from parsing. /// - parameter executor: The executor to use for concurrent processing /// of files. - /// - returns: The list of component data models. + /// - returns: The list of component data models, pluginized component + /// data models and sorted import statements. /// - throws: `DependencyGraphParserError.timeout` if parsing a Swift /// source timed out. - func parse(from rootUrl: URL, excludingFilesWithSuffixes exclusionSuffixes: [String] = [], using executor: SequenceExecutor) throws -> (components: [Component], imports: [String]) { + func parse(from rootUrl: URL, excludingFilesWithSuffixes exclusionSuffixes: [String] = [], using executor: SequenceExecutor) throws -> ([Component], [PluginizableComponent], [String]) { let urlHandles: [UrlSequenceHandle] = enqueueParsingTasks(with: rootUrl, excludingFilesWithSuffixes: exclusionSuffixes, using: executor) let (pluginizableComponents, nonCoreComponents, pluginExtensions, components, dependencies, imports) = try collectDataModels(with: urlHandles) return process(pluginizableComponents, nonCoreComponents, pluginExtensions, components, dependencies, imports) @@ -101,7 +100,7 @@ class PluginizableDependencyGraphParser { return (pluginizableComponents, nonCoreComponents, pluginExtensions, components, dependencies, imports) } - private func process(_ pluginizableComponents: [PluginizableASTComponent], _ nonCoreComponents: [ASTComponent], _ pluginExtensions: [PluginExtension], _ components: [ASTComponent], _ dependencies: [Dependency], _ imports: Set) -> ([Component], [String]) { + private func process(_ pluginizableComponents: [PluginizableASTComponent], _ nonCoreComponents: [ASTComponent], _ pluginExtensions: [PluginExtension], _ components: [ASTComponent], _ dependencies: [Dependency], _ imports: Set) -> ([Component], [PluginizableComponent], [String]) { var allComponents = nonCoreComponents + components let pluginizableComponentData = pluginizableComponents.map { (component: PluginizableASTComponent) -> ASTComponent in component.data @@ -111,7 +110,8 @@ class PluginizableDependencyGraphParser { DuplicateValidator(components: allComponents, dependencies: dependencies), ParentLinker(components: allComponents), DependencyLinker(components: allComponents, dependencies: dependencies), - NonCoreComponentLinker(pluginizableComponents: pluginizableComponents, nonCoreComponents: nonCoreComponents) + NonCoreComponentLinker(pluginizableComponents: pluginizableComponents, nonCoreComponents: nonCoreComponents), + PluginExtensionLinker(pluginizableComponents: pluginizableComponents, pluginExtensions: pluginExtensions) ] for processor in processors { do { @@ -121,12 +121,14 @@ class PluginizableDependencyGraphParser { } } - // TODO: This needs further processing. let valueTypeComponents = components.map { (astComponent: ASTComponent) -> Component in astComponent.valueType } + let valueTypePluginizedComponents = pluginizableComponents.map { (astComponent: PluginizableASTComponent) -> PluginizableComponent in + return astComponent.valueType + } let sortedImports = imports.sorted() - return (valueTypeComponents, sortedImports) + return (valueTypeComponents, valueTypePluginizedComponents, sortedImports) } } diff --git a/Generator/Sources/NeedleFramework/Parsing/Pluginized/Processors/NonCoreComponentLinker.swift b/Generator/Sources/NeedleFramework/Parsing/Pluginized/Processors/NonCoreComponentLinker.swift index 5dbe0f4f..fb7a8d53 100644 --- a/Generator/Sources/NeedleFramework/Parsing/Pluginized/Processors/NonCoreComponentLinker.swift +++ b/Generator/Sources/NeedleFramework/Parsing/Pluginized/Processors/NonCoreComponentLinker.swift @@ -42,6 +42,9 @@ class NonCoreComponentLinker: Processor { for pluginizableComponent in pluginizableComponents { pluginizableComponent.nonCoreComponent = nonCoreMap[pluginizableComponent.nonCoreComponentType] + if pluginizableComponent.nonCoreComponent == nil { + throw ProcessingError.fail("Cannot find \(pluginizableComponent.data.name)'s non-core component with type name \(pluginizableComponent.nonCoreComponentType)") + } } } diff --git a/Generator/Sources/NeedleFramework/Parsing/Pluginized/Processors/PluginExtensionLinker.swift b/Generator/Sources/NeedleFramework/Parsing/Pluginized/Processors/PluginExtensionLinker.swift new file mode 100644 index 00000000..affccd5f --- /dev/null +++ b/Generator/Sources/NeedleFramework/Parsing/Pluginized/Processors/PluginExtensionLinker.swift @@ -0,0 +1,55 @@ +// +// Copyright (c) 2018. Uber Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// A processor that links pluginizable components with their plugin +/// extensions based on type name. +class PluginExtensionLinker: Processor { + + /// Initializer. + /// + /// - parameter pluginizableComponents: The pluginizable components to + /// link with plugin extensions. + /// - parameter pluginExtensions: The non-core components to link. + init(pluginizableComponents: [PluginizableASTComponent], pluginExtensions: [PluginExtension]) { + self.pluginizableComponents = pluginizableComponents + self.pluginExtensions = pluginExtensions + } + + /// Process the data models. + /// + /// - throws: `ProcessingError` if some pluginized components cannot + /// find matching plugin extensions. + func process() throws { + var extensionMap = [String: PluginExtension]() + for pluginExtension in pluginExtensions { + extensionMap[pluginExtension.name] = pluginExtension + } + + for pluginizableComponent in pluginizableComponents { + pluginizableComponent.pluginExtension = extensionMap[pluginizableComponent.pluginExtensionType] + if pluginizableComponent.pluginExtension == nil { + throw ProcessingError.fail("Cannot find \(pluginizableComponent.data.name)'s plugin extension with type name \(pluginizableComponent.pluginExtensionType)") + } + } + } + + // MARK: - Private + + private let pluginizableComponents: [PluginizableASTComponent] + private let pluginExtensions: [PluginExtension] +} diff --git a/Generator/Sources/NeedleFramework/Parsing/Processors/DependencyLinker.swift b/Generator/Sources/NeedleFramework/Parsing/Processors/DependencyLinker.swift index 0db3d3ba..fb27653e 100644 --- a/Generator/Sources/NeedleFramework/Parsing/Processors/DependencyLinker.swift +++ b/Generator/Sources/NeedleFramework/Parsing/Processors/DependencyLinker.swift @@ -38,8 +38,8 @@ class DependencyLinker: Processor { for component in components { if let dependency = nameToDependency[component.dependencyProtocolName] { component.dependencyProtocol = dependency - } else { - throw ProcessingError.fail("Missing dependency protocol data model for \(component.dependencyProtocolName).") + } else if !Dependency.isEmptyDependency(name: component.dependencyProtocolName) { + throw ProcessingError.fail("Missing dependency protocol data model with name \(component.dependencyProtocolName), for \(component.name).") } } } diff --git a/Generator/Tests/NeedleFrameworkTests/Fixtures/ComponentSample.swift b/Generator/Tests/NeedleFrameworkTests/Fixtures/ComponentSample.swift index 6ed2e636..ee594b7d 100644 --- a/Generator/Tests/NeedleFrameworkTests/Fixtures/ComponentSample.swift +++ b/Generator/Tests/NeedleFrameworkTests/Fixtures/ComponentSample.swift @@ -75,7 +75,7 @@ protocol BExtension: PluginExtension { var myPluginPoint: MyPluginPoint { get } } -class SomePluginizedCompo: PluginizedComponent, Stuff { +class SomePluginizedComp: PluginizedComponent, Stuff { var tv: Tv { return LGOLEDTv() } diff --git a/Generator/Tests/NeedleFrameworkTests/Fixtures/Pluginized/OnlyPluginizedComponent.swift b/Generator/Tests/NeedleFrameworkTests/Fixtures/Pluginized/OnlyPluginizedComponent.swift index dca757a2..b87a3e34 100644 --- a/Generator/Tests/NeedleFrameworkTests/Fixtures/Pluginized/OnlyPluginizedComponent.swift +++ b/Generator/Tests/NeedleFrameworkTests/Fixtures/Pluginized/OnlyPluginizedComponent.swift @@ -1,5 +1,9 @@ import UIKit import RIBs; import Foundation +class ANonCoreComponent: NonCoreComponent {} + +protocol BExtension: PluginExtension {} + class SomePluginizedCompo: PluginizedComponent, Stuff { } diff --git a/Generator/Tests/NeedleFrameworkTests/Parsing/DependencyLinkerTests.swift b/Generator/Tests/NeedleFrameworkTests/Parsing/DependencyLinkerTests.swift new file mode 100644 index 00000000..3b8a5ace --- /dev/null +++ b/Generator/Tests/NeedleFrameworkTests/Parsing/DependencyLinkerTests.swift @@ -0,0 +1,52 @@ +// +// Copyright (c) 2018. Uber Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import NeedleFramework + +class DependencyLinkerTests: AbstractParserTests { + + static var allTests = [ + ("test_process_withComponents_verifyLinkages", test_process_withComponents_verifyLinkages), + ("test_process_withComponentsNoDependency_verifyError", test_process_withComponentsNoDependency_verifyError) + ] + + func test_process_withComponents_verifyLinkages() { + let component = ASTComponent(name: "SomeComp", dependencyProtocolName: "ItsDependency", properties: [], expressionCallTypeNames: []) + let dependency = Dependency(name: "ItsDependency", properties: []) + + let linker = DependencyLinker(components: [component], dependencies: [dependency]) + + try! linker.process() + + XCTAssertEqual(component.dependencyProtocol, dependency) + } + + func test_process_withComponentsNoDependency_verifyError() { + let component = ASTComponent(name: "SomeComp", dependencyProtocolName: "ItsDependency", properties: [], expressionCallTypeNames: []) + let dependency = Dependency(name: "WrongDep", properties: []) + + let linker = DependencyLinker(components: [component], dependencies: [dependency]) + + do { + try linker.process() + XCTFail() + } catch ProcessingError.fail(_) { + } catch { + XCTFail() + } + } +} diff --git a/Generator/Tests/NeedleFrameworkTests/Parsing/ParentLinkerTests.swift b/Generator/Tests/NeedleFrameworkTests/Parsing/ParentLinkerTests.swift new file mode 100644 index 00000000..57b04d5c --- /dev/null +++ b/Generator/Tests/NeedleFrameworkTests/Parsing/ParentLinkerTests.swift @@ -0,0 +1,36 @@ +// +// Copyright (c) 2018. Uber Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import NeedleFramework + +class ParentLinkerTests: AbstractParserTests { + + static var allTests = [ + ("test_process_withComponents_verifyLinkages", test_process_withComponents_verifyLinkages), + ] + + func test_process_withComponents_verifyLinkages() { + let parentComponent = ASTComponent(name: "ParentComp", dependencyProtocolName: "Doesn't matter", properties: [], expressionCallTypeNames: ["ChildComp", "someOtherStuff"]) + let childComp = ASTComponent(name: "ChildComp", dependencyProtocolName: "Still doesn't matter", properties: [], expressionCallTypeNames: []) + + let linker = ParentLinker(components: [parentComponent, childComp]) + try! linker.process() + + XCTAssertEqual(childComp.parents.count, 1) + XCTAssertTrue(childComp.parents[0] === parentComponent) + } +} diff --git a/Generator/Tests/NeedleFrameworkTests/Parsing/Pluginized/NonCoreComponentLinkerTests.swift b/Generator/Tests/NeedleFrameworkTests/Parsing/Pluginized/NonCoreComponentLinkerTests.swift new file mode 100644 index 00000000..3f1bddd9 --- /dev/null +++ b/Generator/Tests/NeedleFrameworkTests/Parsing/Pluginized/NonCoreComponentLinkerTests.swift @@ -0,0 +1,54 @@ +// +// Copyright (c) 2018. Uber Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import NeedleFramework + +class NonCoreComponentLinkerTests: AbstractParserTests { + + static var allTests = [ + ("test_process_withComponents_verifyLinkages", test_process_withComponents_verifyLinkages), + ("test_process_withComponentsNoNonCoreComp_verifyError", test_process_withComponentsNoNonCoreComp_verifyError), + ] + + func test_process_withComponents_verifyLinkages() { + let data = ASTComponent(name: "SomePluginizedComp", dependencyProtocolName: "Doesn't matter", properties: [], expressionCallTypeNames: []) + let pluginizedComp = PluginizableASTComponent(data: data, pluginExtensionType: "Doesn't matter", nonCoreComponentType: "SomeComp") + let nonCoreComponent = ASTComponent(name: "SomeComp", dependencyProtocolName: "ItsDependency", properties: [], expressionCallTypeNames: []) + + let linker = NonCoreComponentLinker(pluginizableComponents: [pluginizedComp], nonCoreComponents: [nonCoreComponent]) + + try! linker.process() + + XCTAssertTrue(pluginizedComp.nonCoreComponent === nonCoreComponent) + } + + func test_process_withComponentsNoNonCoreComp_verifyError() { + let data = ASTComponent(name: "SomePluginizedComp", dependencyProtocolName: "Doesn't matter", properties: [], expressionCallTypeNames: []) + let pluginizedComp = PluginizableASTComponent(data: data, pluginExtensionType: "Doesn't matter", nonCoreComponentType: "SomeComp") + let nonCoreComponent = ASTComponent(name: "WrongNonCoreComp", dependencyProtocolName: "ItsDependency", properties: [], expressionCallTypeNames: []) + + let linker = NonCoreComponentLinker(pluginizableComponents: [pluginizedComp], nonCoreComponents: [nonCoreComponent]) + + do { + try linker.process() + XCTFail() + } catch ProcessingError.fail(_) { + } catch { + XCTFail() + } + } +} diff --git a/Generator/Tests/NeedleFrameworkTests/Parsing/Pluginized/PluginExtensionLinkerTests.swift b/Generator/Tests/NeedleFrameworkTests/Parsing/Pluginized/PluginExtensionLinkerTests.swift new file mode 100644 index 00000000..fce0f2c2 --- /dev/null +++ b/Generator/Tests/NeedleFrameworkTests/Parsing/Pluginized/PluginExtensionLinkerTests.swift @@ -0,0 +1,53 @@ +// +// Copyright (c) 2018. Uber Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import NeedleFramework + +class PluginExtensionLinkerTests: AbstractParserTests { + + static var allTests = [ + ("test_process_withComponents_verifyLinkages", test_process_withComponents_verifyLinkages), + ("test_process_withComponentsNoPluginExtension_verifyError", test_process_withComponentsNoPluginExtension_verifyError), + ] + + func test_process_withComponents_verifyLinkages() { + let data = ASTComponent(name: "SomePluginizedComp", dependencyProtocolName: "Doesn't matter", properties: [], expressionCallTypeNames: []) + let pluginizedComp = PluginizableASTComponent(data: data, pluginExtensionType: "MyExtension", nonCoreComponentType: "Doesn't matter") + let pluginExtension = PluginExtension(name: "MyExtension", properties: []) + + let linker = PluginExtensionLinker(pluginizableComponents: [pluginizedComp], pluginExtensions: [pluginExtension]) + + try! linker.process() + + XCTAssertTrue(pluginizedComp.pluginExtension == pluginExtension) + } + + func test_process_withComponentsNoPluginExtension_verifyError() { + let data = ASTComponent(name: "SomePluginizedComp", dependencyProtocolName: "Doesn't matter", properties: [], expressionCallTypeNames: []) + let pluginizedComp = PluginizableASTComponent(data: data, pluginExtensionType: "StuffExtension", nonCoreComponentType: "SomeComp") + + let linker = PluginExtensionLinker(pluginizableComponents: [pluginizedComp], pluginExtensions: []) + + do { + try linker.process() + XCTFail() + } catch ProcessingError.fail(_) { + } catch { + XCTFail() + } + } +} diff --git a/Generator/Tests/NeedleFrameworkTests/Parsing/Pluginized/PluginizableDependencyGraphParserTests.swift b/Generator/Tests/NeedleFrameworkTests/Parsing/Pluginized/PluginizableDependencyGraphParserTests.swift new file mode 100644 index 00000000..1f5a78ee --- /dev/null +++ b/Generator/Tests/NeedleFrameworkTests/Parsing/Pluginized/PluginizableDependencyGraphParserTests.swift @@ -0,0 +1,87 @@ +// +// Copyright (c) 2018. Uber Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import NeedleFramework + +class PluginizableDependencyGraphParserTests: AbstractPluginizedParserTests { + + static var allTests = [ + ("test_parse_withTaskCompleteion_verifyTaskSequence", test_parse_withTaskCompleteion_verifyTaskSequence), + ("test_parse_withTaskCompleteion_verifyResults", test_parse_withTaskCompleteion_verifyResults), + ] + + func test_parse_withTaskCompleteion_verifyTaskSequence() { + let parser = PluginizableDependencyGraphParser() + let fixturesURL = fixtureUrl(for: "") + let enumerator = FileManager.default.enumerator(at: fixturesURL, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles], errorHandler: nil) + let files = enumerator!.allObjects as! [URL] + + let executor = MockSequenceExecutor() + var filterCount = 0 + var producerCount = 0 + var parserCount = 0 + executor.executionHandler = { (task: Task, result: Any) in + if task is PluginizableFileFilterTask { + filterCount += 1 + } else if task is ASTProducerTask { + producerCount += 1 + } else if task is PluginizableASTParserTask { + parserCount += 1 + } else { + XCTFail() + } + } + + XCTAssertEqual(executor.executeCallCount, 0) + + do { + _ = try parser.parse(from: fixturesURL, excludingFilesWithSuffixes: ["ha", "yay", "blah"], using: executor) + } catch { + XCTFail("\(error)") + } + + XCTAssertEqual(executor.executeCallCount, files.count) + XCTAssertEqual(filterCount, files.count) + XCTAssertEqual(producerCount, 7) + XCTAssertEqual(parserCount, 7) + XCTAssertEqual(producerCount, parserCount) + } + + func test_parse_withTaskCompleteion_verifyResults() { + let parser = PluginizableDependencyGraphParser() + let fixturesURL = fixtureUrl(for: "") + let enumerator = FileManager.default.enumerator(at: fixturesURL, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles], errorHandler: nil) + let files = enumerator!.allObjects as! [URL] + let executor = MockSequenceExecutor() + + XCTAssertEqual(executor.executeCallCount, 0) + + do { + let (components, pluginizedComponents, imports) = try parser.parse(from: fixturesURL, excludingFilesWithSuffixes: ["ha", "yay", "blah"], using: executor) + let childComponent = components.filter { $0.name == "MyChildComponent" }.first! + let parentComponent = components.filter { $0.name == "MyComponent" }.first! + XCTAssertTrue(childComponent.parents.first! == parentComponent) + XCTAssertEqual(components.count, 5) + XCTAssertEqual(pluginizedComponents.count, 2) + XCTAssertEqual(imports, ["import Foundation", "import NeedleFoundation", "import NeedleFoundationExtension", "import RIBs", "import RxSwift", "import ScoreSheet", "import UIKit", "import Utility"]) + } catch { + XCTFail("\(error)") + } + + XCTAssertEqual(executor.executeCallCount, files.count) + } +}