Skip to content

Commit

Permalink
Fix testing search path build settings when forcing explicit dependen…
Browse files Browse the repository at this point in the history
…cies (#5773)

* chore: add fixture

* feat: add mocker to dependencies which need XCTest

* chore: lint

* feat: add dependency traversing for search path

* chore: remove unnecessary changes

* chore: try out recursive find of setting

* chore: Formatting and naming

* fix: Fix fixture target

* feat: add unit and acceptance tests

* chore: fix formatting

* fix: Fix acceptance test

* chore: implement PR suggestions

* feat: Update code according to PR suggestions

* fix: Fix method invocation
  • Loading branch information
alexanderwe committed Mar 19, 2024
1 parent 08ecec2 commit 4b3b696
Show file tree
Hide file tree
Showing 22 changed files with 513 additions and 1 deletion.
3 changes: 3 additions & 0 deletions Sources/TuistAcceptanceTesting/TuistAcceptanceFixtures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public enum TuistAcceptanceFixtures {
case manifestWithLogs
case multiplatformAppWithExtension
case multiplatformAppWithSdk
case multiplatformµFeatureUnitTestsWithExplicitDependencies
case plugin
case projectWithFileHeaderTemplate
case projectWithInlineFileHeaderTemplate
Expand Down Expand Up @@ -172,6 +173,8 @@ public enum TuistAcceptanceFixtures {
return "multiplatform_app_with_extension"
case .multiplatformAppWithSdk:
return "multiplatform_app_with_sdk"
case .multiplatformµFeatureUnitTestsWithExplicitDependencies:
return "multiplatform_µFeature_unit_tests_with_explicit_dependencies"
case .plugin:
return "plugin"
case .projectWithFileHeaderTemplate:
Expand Down
56 changes: 56 additions & 0 deletions Sources/TuistCore/Graph/GraphTraverser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,62 @@ public class GraphTraverser: GraphTraversing {
return references
}

public func needsEnableTestingSearchPaths(path: AbsolutePath, name: String) -> Bool {
var cache: [GraphTarget: Bool] = [:]

func _needsEnableTestingSearchPaths(
path: AbsolutePath,
name: String
) -> Bool {
// Target could not be created, something must be wrong
guard let target = target(path: path, name: name) else {
return false
}

// If a cache value is already present use it
if let cacheValue = cache[target] {
return cacheValue
}

// Find all target dependencies
let allTargetDependencies = allTargetDependencies(
path: path,
name: name
)

// Check whether the current target depends on XCTest
let currentTargetDependsOnXCTest = dependsOnXCTest(path: path, name: name)

// If there are no further dependencies cache the value for the current target and return the value of it
guard !allTargetDependencies.isEmpty else {
cache[target] = currentTargetDependsOnXCTest
return currentTargetDependsOnXCTest
}

// If there are dependencies found, we need to traverse deeper down the graph
var enable: Bool? // placeholder when we find a dependency that needs to enable testing paths
for dependency in allTargetDependencies {
let needs = _needsEnableTestingSearchPaths(path: dependency.path, name: dependency.target.name)

if needs {
cache[dependency] = true
enable = true
break
} else {
cache[dependency] = false
}
}

// Either found a value or we use the one from the current target
let result = enable ?? currentTargetDependsOnXCTest

cache[target] = result
return result
}

return _needsEnableTestingSearchPaths(path: path, name: name)
}

public func dependsOnXCTest(path: AbsolutePath, name: String) -> Bool {
guard let target = target(path: path, name: name) else {
return false
Expand Down
8 changes: 8 additions & 0 deletions Sources/TuistCore/GraphTraverser/GraphTraversing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ public protocol GraphTraversing {
/// - Parameter path: Path to the directory where the project is defined.
func allProjectDependencies(path: AbsolutePath) throws -> Set<GraphDependencyReference>

/// Determines whether ENABLE_TESTING_SEARCH_PATHS needs to be enabled
///
/// - Parameters:
/// - path: Path to the project tha defines the target.
/// - name: Target name.
/// - Returns: True if the given target needs to have ENABLE_TESTING_SEARCH_PATHS enabled, false otherwise
func needsEnableTestingSearchPaths(path: AbsolutePath, name: String) -> Bool

/// Returns true if the given target depends on XCTest.
/// - Parameters:
/// - path: Path to the project tha defines the target.
Expand Down
14 changes: 14 additions & 0 deletions Sources/TuistCoreTesting/GraphTraverser/MockGraphTraverser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,20 @@ final class MockGraphTraverser: GraphTraversing {
return stubbedDependsOnXCTestResult
}

var invokedNeedsEnableTestingSearchPaths = false
var invokedNeedsEnableTestingSearchPathsCount = 0
var invokedNeedsEnableTestingSearchPathsParameters: (path: AbsolutePath, name: String)?
var invokedNeedsEnableTestingSearchPathsParametersList = [(path: AbsolutePath, name: String)]()
var stubbedNeedsEnableTestingSearchPathsResult: Bool! = false

func needsEnableTestingSearchPaths(path: AbsolutePath, name: String) -> Bool {
invokedNeedsEnableTestingSearchPaths = true
invokedNeedsEnableTestingSearchPathsCount += 1
invokedNeedsEnableTestingSearchPathsParameters = (path, name)
invokedNeedsEnableTestingSearchPathsParametersList.append((path, name))
return stubbedNeedsEnableTestingSearchPathsResult
}

var schemesStub: (() -> [Scheme])?
func schemes() -> [Scheme] {
schemesStub?() ?? []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ public struct ExplicitDependencyGraphMapper: GraphMapping {
additionalSettings["FRAMEWORK_SEARCH_PATHS"] = .array(frameworkSearchPaths)
}

// If any dependency of this target has "ENABLE_TESTING_SEARCH_PATHS" set to "YES", it needs to be propagated
// on the upstream target as well
if graphTraverser.needsEnableTestingSearchPaths(path: graphTarget.path, name: graphTarget.target.name) {
additionalSettings["ENABLE_TESTING_SEARCH_PATHS"] = .string("YES")
}

var target = graphTarget.target
target.settings = Settings(
base: target.settings?.base ?? [:],
Expand Down Expand Up @@ -148,7 +154,6 @@ public struct ExplicitDependencyGraphMapper: GraphMapping {
),
]
)

return target
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ public final class PackageInfoMapper: PackageInfoMapping {
// Force enable testing search paths
Dictionary(
uniqueKeysWithValues: [
"Mocker", // https://github.com/WeTransfer/Mocker
"Nimble", // https://github.com/Quick/Nimble
"NimbleObjectiveC", // https://github.com/Quick/Nimble
"Quick", // https://github.com/Quick/Quick
Expand Down
10 changes: 10 additions & 0 deletions Tests/TuistAutomationAcceptanceTests/BuildAcceptanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ final class BuildAcceptanceTestMultiplatformAppWithSDK: TuistAcceptanceTestCase
}
}

final class BuildAcceptanceTestMultiplatformµFeatureUnitTestsWithExplicitDependencies: TuistAcceptanceTestCase {
func test() async throws {
try setUpFixture(.multiplatformµFeatureUnitTestsWithExplicitDependencies)
try await run(InstallCommand.self)
try await run(GenerateCommand.self)
try await run(BuildCommand.self, "ExampleApp", "--platform", "ios")
try await run(TestCommand.self, "ModuleA", "--platform", "ios")
}
}

final class BuildAcceptanceTestAppWithSPMDependencies: TuistAcceptanceTestCase {
func test() async throws {
try setUpFixture(.appWithSpmDependencies)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,74 @@ final class ExplicitDependencyGraphMapperTests: TuistUnitTestCase {
]
)
}

func test_enabling_testing_search_paths() async throws {
// Given
let projectAPath = fileHandler.currentPath.appending(component: "ProjectA")
let externalProjectBPath = fileHandler.currentPath.appending(component: "ProjectB")

let frameworkA: Target = .test(
name: "FrameworkA",
product: .framework,
dependencies: [
.project(target: "ExternalFrameworkB", path: externalProjectBPath),
]
)

let externalFrameworkB: Target = .test(
name: "ExternalFrameworkB",
product: .staticFramework,
productName: "ExternalFrameworkB",
settings: .test(base: ["ENABLE_TESTING_SEARCH_PATHS": .string("YES")])
)

let graph = Graph.test(
projects: [
projectAPath: .test(
targets: [
frameworkA,
]
),
externalProjectBPath: .test(
targets: [
externalFrameworkB,
],
isExternal: true
),
],
targets: [
projectAPath: [
"FrameworkA": frameworkA,
],
externalProjectBPath: [
"ExternalFrameworkB": externalFrameworkB,
],
],
dependencies: [
.target(name: "FrameworkA", path: projectAPath): [
.target(name: "ExternalFrameworkB", path: externalProjectBPath),
],
]
)

// When
let got = try await subject.map(graph: graph)

// Then
let gotFrameworkA = try XCTUnwrap(got.0.projects[projectAPath]?.targets[0])
XCTAssertEqual(
gotFrameworkA.name,
"FrameworkA"
)
XCTAssertEqual(
gotFrameworkA.product,
.framework
)

// ENABLE_TESTING_SEARCH_PATHS is propagated from ExternalFrameworkB
XCTAssertEqual(
gotFrameworkA.settings?.baseDebug["ENABLE_TESTING_SEARCH_PATHS"],
.string("YES")
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### Xcode ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## User settings
xcuserdata/

## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout

## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3

### Xcode Patch ###
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcworkspace/contents.xcworkspacedata
/*.gcno

### Projects ###
*.xcodeproj
*.xcworkspace

### Tuist managed dependencies ###
Tuist/Dependencies
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import ModuleAImplementation
import SwiftUI

@main
struct TuistApp: App {
var body: some Scene {
WindowGroup {
Text("Tuist is great")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright ©. All rights reserved.</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Foundation
import ModuleAInterface

public class FrameworkA: FrameworkAInterface {
public init() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public protocol FrameworkAInterface {}
Loading

0 comments on commit 4b3b696

Please sign in to comment.