Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Bundle.testTarget property. #848

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/Overlays/_Testing_Foundation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ add_library(_Testing_Foundation
Attachments/Attachable+Encodable+NSSecureCoding.swift
Attachments/Attachable+Encodable.swift
Events/Clock+Date.swift
Support/Additions/BundleAdditions.swift
ReexportTesting.swift)

target_link_libraries(_Testing_Foundation PUBLIC
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

#if canImport(Foundation)
@_spi(ForSwiftTestingOnly) private import Testing
public import Foundation

extension Bundle {
#if SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING && !SWT_NO_FILE_IO
/// Storage for ``testTarget``.
///
/// On Apple platforms, the bundle containing test content is a loadable
/// XCTest bundle. By the time this property is read, the bundle should have
/// already been loaded.
private static let _testTarget: Bundle? = {
Test.testBundlePath.flatMap { imagePath in
// Construct a lazy sequence of URLs corresponding to the directories that
// contain the loaded image.
let imageURL = URL(fileURLWithFileSystemRepresentation: imagePath, isDirectory: false, relativeTo: nil)
let containingDirectoryURLs = sequence(first: imageURL) { url in
try? url.resourceValues(forKeys: [.parentDirectoryURLKey]).parentDirectory
}

// Find the directory most likely to contain our test content and return it.
return containingDirectoryURLs.lazy
.filter { $0.pathExtension.caseInsensitiveCompare("xctest") == .orderedSame }
.compactMap(Bundle.init(url:))
.first { _ in true }
}
}()
#endif

/// A bundle representing the currently-running test target.
///
/// On Apple platforms, this bundle represents the test bundle built by Xcode
/// or Swift Package Manager. On other platforms, it is equal to the main
/// bundle and represents the test executable built by Swift Package Manager.
///
/// If more than one test bundle has been loaded into the current process, the
/// value of this property represents the first test bundle found by the
/// testing library at runtime.
///
/// - Note: This property accesses the file system the first time it is used.
@_spi(Experimental)
public static var testTarget: Bundle {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think we should extend Foundation this way from Testing. The correct way would be implementation in Foundation itself, following the process there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a prototype only. 🙂 We can certainly follow the Foundation review process for this feature (in whatever form it takes) although due to layering it may need to live here in the overlay.

I have no plans to merge this without your team's signoff!

#if SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING && !SWT_NO_FILE_IO
_testTarget ?? main
#else
// On other platforms, the main executable contains test content.
main
#endif
}
}
#endif
31 changes: 31 additions & 0 deletions Sources/Testing/Test+Discovery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,37 @@ extension Test {
}
}
}

#if SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING && !SWT_NO_FILE_IO
@_spi(ForSwiftTestingOnly)
public static var testBundlePath: String? {
// If the calling environment sets "XCTestBundlePath" (as Xcode does), then
// we can rely on that variable rather than walking loaded images looking
// for test content.
if let envBundlePath = Environment.variable(named: "XCTestBundlePath") {
var s = stat()
if 0 == stat(envBundlePath, &s) && swt_S_ISDIR(s.st_mode) {
return envBundlePath
}
}

// Find the first image loaded into the current process that contains any
// test content.
var imageAddress: UnsafeRawPointer?
enumerateTypes(withNamesContaining: _testContainerTypeNameMagic) { thisImageAddress, _, stop in
imageAddress = thisImageAddress
stop = true
}

// Get the path to the image we found.
var info = Dl_info()
guard let imageAddress, 0 != dladdr(imageAddress, &info), let imageName = info.dli_fname else {
return nil
}

return String(validatingCString: imageName)
}
#endif
}

// MARK: -
Expand Down
11 changes: 11 additions & 0 deletions Sources/_TestingInternals/include/Stubs.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ static int swt_errno(void) {
}

#if !SWT_NO_FILE_IO
#if __has_include(<sys/stat.h>) && defined(S_ISDIR)
/// Check if a given `mode_t` value indicates that a file is a directory.
///
/// This function is exactly equivalent to the `S_ISDIR()` macro. It is
/// necessary because the mode flag macros are not imported into Swift
/// consistently across platforms.
static bool swt_S_ISDIR(mode_t mode) {
return S_ISDIR(mode);
}
#endif

#if __has_include(<sys/stat.h>) && defined(S_ISFIFO)
/// Check if a given `mode_t` value indicates that a file is a pipe (FIFO.)
///
Expand Down
14 changes: 13 additions & 1 deletion Tests/TestingTests/_Testing_Foundation/FoundationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,29 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

#if canImport(Foundation) && !SWT_NO_UTC_CLOCK
#if canImport(Foundation)
@testable @_spi(Experimental) @_spi(ForToolsIntegrationOnly) import _Testing_Foundation
@_spi(Experimental) @_spi(ForToolsIntegrationOnly) import Testing
import Foundation

struct FoundationTests {
#if !SWT_NO_UTC_CLOCK
@Test("Casting Test.Clock.Instant to Date")
func castTestClockInstantToDate() {
let instant = Test.Clock.Instant.now
let date = Date(instant)
#expect(TimeInterval(instant.timeComponentsSince1970.seconds) == date.timeIntervalSince1970.rounded(.down))
}
#endif

#if !SWT_NO_DYNAMIC_LINKING && !SWT_NO_FILE_IO
@Test("Test content bundle")
func testTargetBundle() {
let reportedTestTargetBundle = Bundle.testTarget
final class C {}
let actualTestTargetBundle = Bundle(for: C.self)
#expect(actualTestTargetBundle == reportedTestTargetBundle)
}
#endif
}
#endif