Skip to content

Run CFamilyTargetTests with Swift Build backend #8772

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
120 changes: 75 additions & 45 deletions Tests/FunctionalTests/CFamilyTargetTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,94 +16,124 @@ import PackageGraph
import PackageLoading
import PackageModel
import SourceControl
import SPMBuildCore
import _InternalTestSupport
import Workspace
import XCTest
import Testing

import class Basics.AsyncProcess

/// Asserts if a directory (recursively) contains a file.
private func XCTAssertDirectoryContainsFile(dir: AbsolutePath, filename: String, file: StaticString = #file, line: UInt = #line) {
private func assertDirectoryContainsFile(dir: AbsolutePath, filename: String, sourceLocation: SourceLocation = #_sourceLocation) {
do {
for entry in try walk(dir) {
if entry.basename == filename { return }
}
} catch {
XCTFail("Failed with error \(error)", file: file, line: line)
Issue.record(StringError("Failed with error \(error)"), sourceLocation: sourceLocation)
}
XCTFail("Directory \(dir) does not contain \(file)", file: file, line: line)
Issue.record(StringError("Directory \(dir) does not contain \(filename)"), sourceLocation: sourceLocation)
}

final class CFamilyTargetTestCase: XCTestCase {
func testCLibraryWithSpaces() async throws {
try await fixture(name: "CFamilyTargets/CLibraryWithSpaces") { fixturePath in
await XCTAssertBuilds(fixturePath)
let debugPath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug")
XCTAssertDirectoryContainsFile(dir: debugPath, filename: "Bar.c.o")
XCTAssertDirectoryContainsFile(dir: debugPath, filename: "Foo.c.o")
@Suite(.serialized)
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: can we tag this as a large test?

e.g.: .tag(Tag.TestSize.large)

struct CFamilyTargetTestCase {
@Test(arguments: [BuildSystemProvider.Kind.native, .swiftbuild])
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (blocking): The XCTAssertBuilds (used on line 40 of the left-hand file) builds, by default, against both release and debug Build configuration. Can we augment this test to add the build arguments as a parameter.

ie:

    @Test(arguments: [BuildSystemProvider.Kind.native, .swiftbuild], BuildConfiguration.allCases)
    func testCLibraryWithSpaces(
        buildSystem: BuildSystemProvider.Kind,
        configuration: BuildConfiguration
    ) async throws {
        // ...
        try await executSwiftBuild(..., configuration: configuration,...),
        // ...
    }

NOTE: This comment applies to all other occurrences where XCTAssertBuild was called in a test.

func testCLibraryWithSpaces(buildSystem: BuildSystemProvider.Kind) async throws {
try await withKnownIssue("https://github.com/swiftlang/swift-build/issues/333") {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (non-blocking): can we add the .bug trait to the test and reference the GitHub issue?

try await fixture(name: "CFamilyTargets/CLibraryWithSpaces") { fixturePath in
try await executeSwiftBuild(fixturePath, buildSystem: buildSystem)
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (non-blocking): since this is calling swift-build executable, can we add the Tag.Feature.Command.Build tag? We would need to add the following test trait: .tags(Tag.Feature.Command.Build)

This comment applies to all tests that invoke the swift-build executable.

Copy link
Contributor

Choose a reason for hiding this comment

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

This might require #8714.

if buildSystem == .native {
let debugPath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug")
assertDirectoryContainsFile(dir: debugPath, filename: "Bar.c.o")
assertDirectoryContainsFile(dir: debugPath, filename: "Foo.c.o")
}
}
} when: {
buildSystem == .swiftbuild
}
}

func testCUsingCAndSwiftDep() async throws {
@Test(arguments: [BuildSystemProvider.Kind.native, .swiftbuild])
func testCUsingCAndSwiftDep(buildSystem: BuildSystemProvider.Kind) async throws {
try await fixture(name: "DependencyResolution/External/CUsingCDep") { fixturePath in
let packageRoot = fixturePath.appending("Bar")
await XCTAssertBuilds(packageRoot)
let debugPath = fixturePath.appending(components: "Bar", ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug")
XCTAssertDirectoryContainsFile(dir: debugPath, filename: "Sea.c.o")
XCTAssertDirectoryContainsFile(dir: debugPath, filename: "Foo.c.o")
try await executeSwiftBuild(packageRoot, buildSystem: buildSystem)
if buildSystem == .native {
Copy link
Contributor

@bkhouri bkhouri Jun 11, 2025

Choose a reason for hiding this comment

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

question: do we expect the swiftbuild build system to "support' the subsequent "expectations"? If so, can we raise a GitHub issue and put wrap this with a withKnownIssue { ... } when: { ... }

This comment applies to all tests where there is a similar conditional.

let debugPath = fixturePath.appending(components: "Bar", ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug")
assertDirectoryContainsFile(dir: debugPath, filename: "Sea.c.o")
assertDirectoryContainsFile(dir: debugPath, filename: "Foo.c.o")
}
let path = try SwiftPM.packagePath(for: "Foo", packageRoot: packageRoot)
XCTAssertEqual(try GitRepository(path: path).getTags(), ["1.2.3"])
#expect(try GitRepository(path: path).getTags() == ["1.2.3"])
}
}

func testModuleMapGenerationCases() async throws {
@Test(arguments: [BuildSystemProvider.Kind.native, .swiftbuild])
func testModuleMapGenerationCases(buildSystem: BuildSystemProvider.Kind) async throws {
try await fixture(name: "CFamilyTargets/ModuleMapGenerationCases") { fixturePath in
await XCTAssertBuilds(fixturePath)
let debugPath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug")
XCTAssertDirectoryContainsFile(dir: debugPath, filename: "Jaz.c.o")
XCTAssertDirectoryContainsFile(dir: debugPath, filename: "main.swift.o")
XCTAssertDirectoryContainsFile(dir: debugPath, filename: "FlatInclude.c.o")
XCTAssertDirectoryContainsFile(dir: debugPath, filename: "UmbrellaHeader.c.o")
try await executeSwiftBuild(fixturePath, buildSystem: buildSystem)
if buildSystem == .native {
let debugPath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug")
assertDirectoryContainsFile(dir: debugPath, filename: "Jaz.c.o")
assertDirectoryContainsFile(dir: debugPath, filename: "main.swift.o")
assertDirectoryContainsFile(dir: debugPath, filename: "FlatInclude.c.o")
assertDirectoryContainsFile(dir: debugPath, filename: "UmbrellaHeader.c.o")
}
}
}

func testNoIncludeDirCheck() async throws {

@Test(arguments: [BuildSystemProvider.Kind.native, .swiftbuild])
func testNoIncludeDirCheck(buildSystem: BuildSystemProvider.Kind) async throws {
try await fixture(name: "CFamilyTargets/CLibraryNoIncludeDir") { fixturePath in
await XCTAssertAsyncThrowsError(try await executeSwiftBuild(fixturePath), "This build should throw an error") { err in
// The err.localizedDescription doesn't capture the detailed error string so interpolate
let errStr = "\(err)"
let missingIncludeDirStr = "\(ModuleError.invalidPublicHeadersDirectory("Cfactorial"))"
XCTAssert(errStr.contains(missingIncludeDirStr))
var buildError: (any Error)? = nil
Copy link
Contributor

Choose a reason for hiding this comment

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

If we know the exact error message. this can be replaced with the following, or similar.

try await #expect(throws: ModuleError.invalidPublicHeadersDirectory("Cfactorial")) {
    try await executeSwiftBuild(fixturePath, buildSystem: buildSystem)
}

or

let error = await #expect(throws: SwiftPMError.self ) {
   try await executeSwiftBuild(fixturePath, buildSystem: buildSystem
}
// THEN I expect a failure
guard case SwiftPMError.executionFailure(_, let stdout, let stderr) = try #require(error) else {
    Issue.record("Incorrect error was raised.")
    return
}

 let missingIncludeDirStr = "\(ModuleError.invalidPublicHeadersDirectory("Cfactorial"))"
 #expect(stderr.contains(missingIncludeDirStr))

do {
try await executeSwiftBuild(fixturePath, buildSystem: buildSystem)
} catch {
buildError = error
}
guard let buildError else {
Issue.record(StringError("This build should throw an error"))
return
}
// The err.localizedDescription doesn't capture the detailed error string so interpolate
let errStr = "\(buildError)"
let missingIncludeDirStr = "\(ModuleError.invalidPublicHeadersDirectory("Cfactorial"))"
#expect(errStr.contains(missingIncludeDirStr))
}
}

func testCanForwardExtraFlagsToClang() async throws {
@Test(arguments: [BuildSystemProvider.Kind.native, .swiftbuild])
func testCanForwardExtraFlagsToClang(buildSystem: BuildSystemProvider.Kind) async throws {
// Try building a fixture which needs extra flags to be able to build.
try await fixture(name: "CFamilyTargets/CDynamicLookup") { fixturePath in
await XCTAssertBuilds(fixturePath, Xld: ["-undefined", "dynamic_lookup"])
try await executeSwiftBuild(fixturePath, Xld: ["-undefined", "dynamic_lookup"], buildSystem: buildSystem)
let debugPath = fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug")
XCTAssertDirectoryContainsFile(dir: debugPath, filename: "Foo.c.o")
if buildSystem == .native {
assertDirectoryContainsFile(dir: debugPath, filename: "Foo.c.o")
}
}
}

func testObjectiveCPackageWithTestTarget() async throws {
#if !os(macOS)
try XCTSkipIf(true, "test is only supported on macOS")
#endif
@Test(.requireHostOS(.macOS), arguments: [BuildSystemProvider.Kind.native, .swiftbuild])
func testObjectiveCPackageWithTestTarget(buildSystem: BuildSystemProvider.Kind) async throws {
try await fixture(name: "CFamilyTargets/ObjCmacOSPackage") { fixturePath in
// Build the package.
await XCTAssertBuilds(fixturePath)
XCTAssertDirectoryContainsFile(dir: fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug"), filename: "HelloWorldExample.m.o")
try await executeSwiftBuild(fixturePath, buildSystem: buildSystem)
if buildSystem == .native {
assertDirectoryContainsFile(dir: fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug"), filename: "HelloWorldExample.m.o")
}
// Run swift-test on package.
await XCTAssertSwiftTest(fixturePath)
try await executeSwiftTest(fixturePath, buildSystem: buildSystem)
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (non-blocking): since this is calling swift-test executable, can we add the Tag.Feature.Command.Test tag? We would need to add the following test trait: .tags(Tag.Feature.Command.Test)

This comment applies to all tests that invoke the swift-test executable.

Copy link
Contributor

Choose a reason for hiding this comment

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

This might require #8714

}
}

func testCanBuildRelativeHeaderSearchPaths() async throws {

@Test(arguments: [BuildSystemProvider.Kind.native, .swiftbuild])
func testCanBuildRelativeHeaderSearchPaths(buildSystem: BuildSystemProvider.Kind) async throws {
try await fixture(name: "CFamilyTargets/CLibraryParentSearchPath") { fixturePath in
await XCTAssertBuilds(fixturePath)
XCTAssertDirectoryContainsFile(dir: fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug"), filename: "HeaderInclude.swiftmodule")
try await executeSwiftBuild(fixturePath, buildSystem: buildSystem)
if buildSystem == .native {
assertDirectoryContainsFile(dir: fixturePath.appending(components: ".build", try UserToolchain.default.targetTriple.platformBuildPathComponent, "debug"), filename: "HeaderInclude.swiftmodule")
}
}
}
}