Skip to content

Commit c5582fb

Browse files
authored
Tests: Conditionally skip the Archiver tests (#8601)
The Archiver tests has a dependency on a system binary being available on the host. Update the archiver test to skip the test is the required executable is not available on the system. Fixes: #8586 Issue: rdar://150414402 Required for: swiftlang/swift#80405
1 parent 8dbd83e commit c5582fb

File tree

8 files changed

+130
-6
lines changed

8 files changed

+130
-6
lines changed

Sources/Basics/Archiver/TarArchiver.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public struct TarArchiver: Archiver {
2525
private let cancellator: Cancellator
2626

2727
/// The underlying command
28-
private let tarCommand: String
28+
internal let tarCommand: String
2929

3030
/// Creates a `TarArchiver`.
3131
///

Sources/Basics/Archiver/ZipArchiver.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ public struct ZipArchiver: Archiver, Cancellable {
2929

3030
/// Absolute path to the Windows tar in the system folder
3131
#if os(Windows)
32-
private let windowsTar: String
32+
internal let windowsTar: String
33+
#else
34+
internal let unzip = "unzip"
35+
internal let zip = "zip"
36+
#endif
37+
38+
#if os(FreeBSD)
39+
internal let tar = "tar"
3340
#endif
3441

3542
/// Creates a `ZipArchiver`.
@@ -74,7 +81,9 @@ public struct ZipArchiver: Archiver, Cancellable {
7481
// It's part of system32 anyway so use the absolute path.
7582
let process = AsyncProcess(arguments: [windowsTar, "xf", archivePath.pathString, "-C", destinationPath.pathString])
7683
#else
77-
let process = AsyncProcess(arguments: ["unzip", archivePath.pathString, "-d", destinationPath.pathString])
84+
let process = AsyncProcess(arguments: [
85+
self.unzip, archivePath.pathString, "-d", destinationPath.pathString,
86+
])
7887
#endif
7988
guard let registrationKey = self.cancellator.register(process) else {
8089
throw CancellationError.failedToRegisterProcess(process)
@@ -113,7 +122,10 @@ public struct ZipArchiver: Archiver, Cancellable {
113122
// On FreeBSD, the unzip command is available in base but not the zip command.
114123
// Therefore; we use libarchive(bsdtar) to produce the ZIP archive instead.
115124
let process = AsyncProcess(
116-
arguments: ["tar", "-c", "--format", "zip", "-f", destinationPath.pathString, directory.basename],
125+
arguments: [
126+
self.tar, "-c", "--format", "zip", "-f", destinationPath.pathString,
127+
directory.basename,
128+
],
117129
workingDirectory: directory.parentDirectory
118130
)
119131
#else
@@ -127,7 +139,7 @@ public struct ZipArchiver: Archiver, Cancellable {
127139
arguments: [
128140
"/bin/sh",
129141
"-c",
130-
"cd \(directory.parentDirectory.underlying.pathString) && zip -ry \(destinationPath.pathString) \(directory.basename)",
142+
"cd \(directory.parentDirectory.underlying.pathString) && \(self.zip) -ry \(destinationPath.pathString) \(directory.basename)"
131143
]
132144
)
133145
#endif
@@ -154,7 +166,7 @@ public struct ZipArchiver: Archiver, Cancellable {
154166
#if os(Windows)
155167
let process = AsyncProcess(arguments: [windowsTar, "tf", path.pathString])
156168
#else
157-
let process = AsyncProcess(arguments: ["unzip", "-t", path.pathString])
169+
let process = AsyncProcess(arguments: [self.unzip, "-t", path.pathString])
158170
#endif
159171
guard let registrationKey = self.cancellator.register(process) else {
160172
throw CancellationError.failedToRegisterProcess(process)

Sources/_InternalTestSupport/XCTAssertHelpers.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,31 @@ public func XCTSkipOnWindows(because reason: String? = nil, skipPlatformCi: Bool
7676
#endif
7777
}
7878

79+
public func _requiresTools(_ executable: String) throws {
80+
func getAsyncProcessArgs(_ executable: String) -> [String] {
81+
#if os(Windows)
82+
let args = ["cmd.exe", "/c", "where.exe", executable]
83+
#else
84+
let args = ["which", executable]
85+
#endif
86+
return args
87+
}
88+
try AsyncProcess.checkNonZeroExit(arguments: getAsyncProcessArgs(executable))
89+
}
90+
public func XCTRequires(
91+
executable: String,
92+
file: StaticString = #filePath,
93+
line: UInt = #line
94+
) throws {
95+
96+
do {
97+
try _requiresTools(executable)
98+
} catch (let AsyncProcessResult.Error.nonZeroExit(result)) {
99+
throw XCTSkip(
100+
"Skipping as tool \(executable) is not found in the path. (\(result.description))")
101+
}
102+
}
103+
79104
/// An `async`-friendly replacement for `XCTAssertThrowsError`.
80105
public func XCTAssertAsyncThrowsError<T>(
81106
_ expression: @autoclosure () async throws -> T,

Tests/BasicsTests/Archiver/TarArchiverTests.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Basics
14+
@testable import struct Basics.TarArchiver
1415
import TSCclibc // for SPM_posix_spawn_file_actions_addchdir_np_supported
1516
import _InternalTestSupport
1617
import XCTest
1718

1819
import struct TSCBasic.FileSystemError
1920

2021
final class TarArchiverTests: XCTestCase {
22+
override func setUp() async throws {
23+
let archiver = TarArchiver(fileSystem: localFileSystem)
24+
try XCTRequires(executable: archiver.tarCommand)
25+
}
26+
2127
func testSuccess() async throws {
2228
try await testWithTemporaryDirectory { tmpdir in
2329
let archiver = TarArchiver(fileSystem: localFileSystem)

Tests/BasicsTests/Archiver/UniversalArchiverTests.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,31 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Basics
14+
@testable import struct Basics.TarArchiver
15+
@testable import struct Basics.ZipArchiver
1416
import TSCclibc // for SPM_posix_spawn_file_actions_addchdir_np_supported
1517
import _InternalTestSupport
1618
import XCTest
1719

1820
import struct TSCBasic.FileSystemError
1921

2022
final class UniversalArchiverTests: XCTestCase {
23+
override func setUp() async throws {
24+
let zipAchiver = ZipArchiver(fileSystem: localFileSystem)
25+
#if os(Windows)
26+
try XCTRequires(executable: zipAchiver.windowsTar)
27+
#else
28+
try XCTRequires(executable: zipAchiver.unzip)
29+
try XCTRequires(executable: zipAchiver.zip)
30+
#endif
31+
#if os(FreeBSD)
32+
try XCTRequires(executable: zipAchiver.tar)
33+
#endif
34+
35+
let tarAchiver = TarArchiver(fileSystem: localFileSystem)
36+
try XCTRequires(executable: tarAchiver.tarCommand)
37+
}
38+
2139
func testSuccess() async throws {
2240
try await testWithTemporaryDirectory { tmpdir in
2341
let archiver = UniversalArchiver(localFileSystem)

Tests/BasicsTests/Archiver/ZipArchiverTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,27 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Basics
14+
@testable import struct Basics.ZipArchiver
1415
import _InternalTestSupport
1516
import XCTest
1617
import TSCclibc // for SPM_posix_spawn_file_actions_addchdir_np_supported
1718

1819
import struct TSCBasic.FileSystemError
1920

2021
final class ZipArchiverTests: XCTestCase {
22+
override func setUp() async throws {
23+
let archiver = ZipArchiver(fileSystem: localFileSystem)
24+
#if os(Windows)
25+
try XCTRequires(executable: archiver.windowsTar)
26+
#else
27+
try XCTRequires(executable: archiver.unzip)
28+
try XCTRequires(executable: archiver.zip)
29+
#endif
30+
#if os(FreeBSD)
31+
try XCTRequires(executable: archiver.tar)
32+
#endif
33+
}
34+
2135
func testZipArchiverSuccess() async throws {
2236
try await testWithTemporaryDirectory { tmpdir in
2337
let archiver = ZipArchiver(fileSystem: localFileSystem)

Tests/_InternalTestSupportTests/Misc.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2024-2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
112
import SPMBuildCore
213
import _InternalTestSupport
314
import XCTest
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Basics
14+
import XCTest
15+
import func _InternalTestSupport.XCTAssertThrows
16+
import func _InternalTestSupport._requiresTools
17+
18+
final class TestRequiresTool: XCTestCase {
19+
func testErrorIsThrownIfExecutableIsNotFoundOnThePath() throws {
20+
XCTAssertThrows(
21+
try _requiresTools("doesNotExists")
22+
) { (error: AsyncProcessResult.Error) in
23+
return true
24+
}
25+
}
26+
27+
func testErrorIsNotThrownIfExecutableIsOnThePath() throws {
28+
// Essentially call either "which which" or "where.exe where.exe"
29+
#if os(Window)
30+
let executable = "where.exe"
31+
#else
32+
let executable = "which"
33+
#endif
34+
XCTAssertNoThrow(
35+
try _requiresTools(executable)
36+
)
37+
}
38+
}

0 commit comments

Comments
 (0)