From 30bda6c8722a4787a2a2439dc667621b24538987 Mon Sep 17 00:00:00 2001 From: Rhys Morgan Date: Wed, 30 Oct 2024 14:55:03 +0000 Subject: [PATCH] Fix tests crashing when accessing SPM dependency resource bundle (#6895) * Adds the base sample project * Adds tests to demonstrate the problem * Tweaks the ResourcesProjectMapper Removes the perhaps unnecessary BuildAcceptanceTest I added? * Runs lint-fix * Adds an install command before testing * Reverts the changes from #6865 * Re-runs lint fix * Revert "Reverts the changes from #6865" This reverts commit 08cc4d19202a618470856fa4fe1ff55e74a05068. * Gates the bundle resource fix behind an #if canImport(XCTest) compiler directive. * Empty commit to re-attempt CI, which failed to install Mise * Runs mise lint again * Adds extended comment with link to PR --- .../TuistAcceptanceFixtures.swift | 3 ++ .../Mappers/ResourcesProjectMapper.swift | 11 ++++++- .../TestAcceptanceTests.swift | 6 ++++ .../Feature/Project.swift | 31 +++++++++++++++++++ .../Feature/Sources/FeatureView.swift | 22 +++++++++++++ .../Feature/Tests/FeatureViewTest.swift | 8 +++++ .../SharedUI/Project.swift | 19 ++++++++++++ .../SharedUI/Sources/PaymentTextField.swift | 25 +++++++++++++++ .../Tuist/Package.resolved | 15 +++++++++ .../Tuist/Package.swift | 9 ++++++ .../framework_with_spm_bundle/Workspace.swift | 6 ++++ 11 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 fixtures/framework_with_spm_bundle/Feature/Project.swift create mode 100644 fixtures/framework_with_spm_bundle/Feature/Sources/FeatureView.swift create mode 100644 fixtures/framework_with_spm_bundle/Feature/Tests/FeatureViewTest.swift create mode 100644 fixtures/framework_with_spm_bundle/SharedUI/Project.swift create mode 100644 fixtures/framework_with_spm_bundle/SharedUI/Sources/PaymentTextField.swift create mode 100644 fixtures/framework_with_spm_bundle/Tuist/Package.resolved create mode 100644 fixtures/framework_with_spm_bundle/Tuist/Package.swift create mode 100644 fixtures/framework_with_spm_bundle/Workspace.swift diff --git a/Sources/TuistAcceptanceTesting/TuistAcceptanceFixtures.swift b/Sources/TuistAcceptanceTesting/TuistAcceptanceFixtures.swift index 3701e254d64..d837c994662 100644 --- a/Sources/TuistAcceptanceTesting/TuistAcceptanceFixtures.swift +++ b/Sources/TuistAcceptanceTesting/TuistAcceptanceFixtures.swift @@ -24,6 +24,7 @@ public enum TuistAcceptanceFixtures { case frameworkWithMacroAndPluginPackages case frameworkWithNativeSwiftMacro case frameworkWithSwiftMacro + case frameworkWithSPMBundle case invalidManifest case invalidWorkspaceManifestName case iosAppLarge @@ -133,6 +134,8 @@ public enum TuistAcceptanceFixtures { return "framework_with_native_swift_macro" case .frameworkWithSwiftMacro: return "framework_with_swift_macro" + case .frameworkWithSPMBundle: + return "framework_with_spm_bundle" case .invalidManifest: return "invalid_manifest" case .invalidWorkspaceManifestName: diff --git a/Sources/TuistGenerator/Mappers/ResourcesProjectMapper.swift b/Sources/TuistGenerator/Mappers/ResourcesProjectMapper.swift index 0fd956141cf..3f9bcdeae65 100644 --- a/Sources/TuistGenerator/Mappers/ResourcesProjectMapper.swift +++ b/Sources/TuistGenerator/Mappers/ResourcesProjectMapper.swift @@ -258,9 +258,10 @@ public class ResourcesProjectMapper: ProjectMapping { // swiftlint:disable:this ), the bundle containing the resources is copied into the final product. static let module: Bundle = { let bundleName = "\(bundleName)" + let bundleFinderResourceURL = Bundle(for: BundleFinder.self).resourceURL var candidates = [ Bundle.main.resourceURL, - Bundle(for: BundleFinder.self).resourceURL, + bundleFinderResourceURL, Bundle.main.bundleURL, ] // This is a fix to make Previews work with bundled resources. @@ -279,6 +280,14 @@ public class ResourcesProjectMapper: ProjectMapping { // swiftlint:disable:this } } } + + // This is a fix to make unit tests work with bundled resources. + // Making this change allows unit tests to search one directory up for a bundle. + // More context can be found in this PR: https://github.com/tuist/tuist/pull/6895 + #if canImport(XCTest) + candidates.append(bundleFinderResourceURL?.appendingPathComponent("..")) + #endif + for candidate in candidates { let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle") if let bundle = bundlePath.flatMap(Bundle.init(url:)) { diff --git a/Tests/TuistAutomationAcceptanceTests/TestAcceptanceTests.swift b/Tests/TuistAutomationAcceptanceTests/TestAcceptanceTests.swift index 5bc90cd10fb..a95447dc496 100644 --- a/Tests/TuistAutomationAcceptanceTests/TestAcceptanceTests.swift +++ b/Tests/TuistAutomationAcceptanceTests/TestAcceptanceTests.swift @@ -21,6 +21,12 @@ final class TestAcceptanceTests: TuistAcceptanceTestCase { try await run(TestCommand.self) } + func test_with_framework_with_spm_bundle() async throws { + try await setUpFixture(.frameworkWithSPMBundle) + try await run(InstallCommand.self) + try await run(TestCommand.self) + } + func test_with_app_with_test_plan() async throws { try await setUpFixture(.appWithTestPlan) try await run(TestCommand.self) diff --git a/fixtures/framework_with_spm_bundle/Feature/Project.swift b/fixtures/framework_with_spm_bundle/Feature/Project.swift new file mode 100644 index 00000000000..973b62c8367 --- /dev/null +++ b/fixtures/framework_with_spm_bundle/Feature/Project.swift @@ -0,0 +1,31 @@ +import ProjectDescription + +let project = Project( + name: "Feature", + targets: [ + .target( + name: "Feature", + destinations: .iOS, + product: .framework, + bundleId: "io.tuist.app", + deploymentTargets: .iOS("16.0"), + infoPlist: .default, + sources: "Sources/**", + dependencies: [ + .project(target: "SharedUI", path: "../SharedUI"), + ] + ), + .target( + name: "FeatureTests", + destinations: .iOS, + product: .unitTests, + bundleId: "io.tuist.app.tests", + deploymentTargets: .iOS("16.0"), + infoPlist: .default, + sources: "Tests/**", + dependencies: [ + .target(name: "Feature"), + ] + ), + ] +) diff --git a/fixtures/framework_with_spm_bundle/Feature/Sources/FeatureView.swift b/fixtures/framework_with_spm_bundle/Feature/Sources/FeatureView.swift new file mode 100644 index 00000000000..60b1661032f --- /dev/null +++ b/fixtures/framework_with_spm_bundle/Feature/Sources/FeatureView.swift @@ -0,0 +1,22 @@ +import SharedUI +import UIKit + +public final class FeatureView: UIView { + let textField = PaymentTextField() + + override public init(frame: CGRect) { + super.init(frame: frame) + addSubview(textField) + NSLayoutConstraint.activate([ + textField.topAnchor.constraint(equalTo: topAnchor), + textField.leadingAnchor.constraint(equalTo: leadingAnchor), + textField.trailingAnchor.constraint(equalTo: trailingAnchor), + textField.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/fixtures/framework_with_spm_bundle/Feature/Tests/FeatureViewTest.swift b/fixtures/framework_with_spm_bundle/Feature/Tests/FeatureViewTest.swift new file mode 100644 index 00000000000..a352e7df1b9 --- /dev/null +++ b/fixtures/framework_with_spm_bundle/Feature/Tests/FeatureViewTest.swift @@ -0,0 +1,8 @@ +import Feature +import XCTest + +final class FeatureViewTest: XCTestCase { + func testFeatureView() { + let view = FeatureView() + } +} diff --git a/fixtures/framework_with_spm_bundle/SharedUI/Project.swift b/fixtures/framework_with_spm_bundle/SharedUI/Project.swift new file mode 100644 index 00000000000..53c76a664a8 --- /dev/null +++ b/fixtures/framework_with_spm_bundle/SharedUI/Project.swift @@ -0,0 +1,19 @@ +import ProjectDescription + +let project = Project( + name: "SharedUI", + targets: [ + .target( + name: "SharedUI", + destinations: .iOS, + product: .framework, + bundleId: "io.tuist.app", + deploymentTargets: .iOS("16.0"), + infoPlist: .default, + sources: "Sources/**", + dependencies: [ + .external(name: "Stripe"), + ] + ), + ] +) diff --git a/fixtures/framework_with_spm_bundle/SharedUI/Sources/PaymentTextField.swift b/fixtures/framework_with_spm_bundle/SharedUI/Sources/PaymentTextField.swift new file mode 100644 index 00000000000..81637580134 --- /dev/null +++ b/fixtures/framework_with_spm_bundle/SharedUI/Sources/PaymentTextField.swift @@ -0,0 +1,25 @@ +import StripePaymentsUI +import SwiftUI +import UIKit + +public final class PaymentTextField: STPPaymentCardTextField, STPPaymentCardTextFieldDelegate { + public init() { + super.init(frame: .zero) + delegate = self + } + + @available(*, unavailable) + public required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +public struct PaymentTextFieldRepresentable: UIViewRepresentable { + public init() {} + + public func makeUIView(context _: Context) -> PaymentTextField { + .init() + } + + public func updateUIView(_: PaymentTextField, context _: Context) {} +} diff --git a/fixtures/framework_with_spm_bundle/Tuist/Package.resolved b/fixtures/framework_with_spm_bundle/Tuist/Package.resolved new file mode 100644 index 00000000000..afa4e1c2a01 --- /dev/null +++ b/fixtures/framework_with_spm_bundle/Tuist/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "b9f3c72bc0a4f32d237e8fe5b2a237ffccde38f6008fc2a64058af8cbf83bf5f", + "pins" : [ + { + "identity" : "stripe-ios-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/stripe/stripe-ios-spm", + "state" : { + "revision" : "87b6850b5e6a9a6767c5ed4c43c8a84e2191d65f", + "version" : "23.27.3" + } + } + ], + "version" : 3 +} diff --git a/fixtures/framework_with_spm_bundle/Tuist/Package.swift b/fixtures/framework_with_spm_bundle/Tuist/Package.swift new file mode 100644 index 00000000000..0853071c1f5 --- /dev/null +++ b/fixtures/framework_with_spm_bundle/Tuist/Package.swift @@ -0,0 +1,9 @@ +// swift-tools-version: 5.10 +@preconcurrency import PackageDescription + +let package = Package( + name: "PackageName", + dependencies: [ + .package(url: "https://github.com/stripe/stripe-ios-spm", exact: "23.27.3"), + ] +) diff --git a/fixtures/framework_with_spm_bundle/Workspace.swift b/fixtures/framework_with_spm_bundle/Workspace.swift new file mode 100644 index 00000000000..ad4586aadcb --- /dev/null +++ b/fixtures/framework_with_spm_bundle/Workspace.swift @@ -0,0 +1,6 @@ +import ProjectDescription + +let workspace = Workspace( + name: "Workspace", + projects: ["SharedUI", "Feature"] +)