diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2903dbbd..7699e3e6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,26 +23,22 @@ jobs: fail-fast: false matrix: swift-version: - - 5.10 - 6.0 - 6.1 + - 6.2 include: - - { swift-version: 5.10, swift-branch: swift-5.10.1-release, swift-tag: 5.10.1-RELEASE } - { swift-version: 6.0, swift-branch: swift-6.0.3-release, swift-tag: 6.0.3-RELEASE } - - { swift-version: 6.1, swift-branch: swift-6.1-release, swift-tag: 6.1-RELEASE } + - { swift-version: 6.1, swift-branch: swift-6.1.3-release, swift-tag: 6.1.3-RELEASE } + - { swift-version: 6.2, swift-branch: swift-6.2-release, swift-tag: 6.2-RELEASE } runs-on: windows-latest timeout-minutes: 60 steps: - - name: Configure dev environment - uses: compnerd/gha-setup-vsdevenv@main - with: - toolset_version: 14.29 - winsdk: 10.0.22621.0 - name: Install Windows Swift toolchain uses: compnerd/gha-setup-swift@main with: - branch: ${{ matrix.swift-branch }} - tag: ${{ matrix.swift-tag }} + swift-version: ${{ matrix.swift-branch }} + swift-build: ${{ matrix.swift-tag }} + update-sdk-modules: true - name: Download zlib run: | curl -L -o zlib.zip https://www.zlib.net/zlib131.zip @@ -57,7 +53,8 @@ jobs: cmake --build . --config Release cmake --install . --prefix ../install - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Run unit tests run: | - swift test -Xcc -I'C:/Program Files (x86)/zlib/include' -Xcc -D_INTSAFE_H_INCLUDED_ -Xlinker -L'C:/Program Files (x86)/zlib/lib' -Xbuild-tools-swiftc -windows-sdk-root -Xbuild-tools-swiftc "${env:WindowsSdkDir}" -Xbuild-tools-swiftc -windows-sdk-version -Xbuild-tools-swiftc ${env:UCRTVersion} -Xswiftc -windows-sdk-root -Xswiftc "${env:WindowsSdkDir}" -Xswiftc -windows-sdk-version -Xswiftc ${env:UCRTVersion} + swift test -Xcc -I'C:/Program Files (x86)/zlib/include' -Xcc -D_INTSAFE_H_INCLUDED_ -Xlinker -L'C:/Program Files (x86)/zlib/lib' + #-Xbuild-tools-swiftc -windows-sdk-root -Xbuild-tools-swiftc "${env:WindowsSdkDir}" -Xbuild-tools-swiftc -windows-sdk-version -Xbuild-tools-swiftc ${env:UCRTVersion} -Xswiftc -windows-sdk-root -Xswiftc "${env:WindowsSdkDir}" -Xswiftc -windows-sdk-version -Xswiftc ${env:UCRTVersion} diff --git a/.swift-format b/.swift-format index dea65687..1268f53b 100644 --- a/.swift-format +++ b/.swift-format @@ -5,7 +5,7 @@ "indentation": { "spaces": 4 }, - "indentConditionalCompilationBlocks": true, + "indentConditionalCompilationBlocks": false, "indentSwitchCaseLabels": false, "lineBreakAroundMultilineExpressionChainComponents": false, "lineBreakBeforeControlFlowKeywords": false, diff --git a/Package.swift b/Package.swift index 6ef4951f..72614189 100644 --- a/Package.swift +++ b/Package.swift @@ -1,12 +1,6 @@ -// swift-tools-version:5.10 +// swift-tools-version:6.0 import PackageDescription -#if canImport(Darwin) || compiler(<6.0) - import Foundation -#else - import FoundationEssentials -#endif - let package = Package( name: "Zip", products: [ @@ -45,25 +39,19 @@ let package = Package( var swiftSettings: [SwiftSetting] { [ - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("ConciseMagicFile"), - .enableUpcomingFeature("ForwardTrailingClosures"), - .enableUpcomingFeature("DisableOutwardActorInference"), - .enableUpcomingFeature("StrictConcurrency"), - .enableExperimentalFeature("StrictConcurrency=complete"), - .enableExperimentalFeature("AccessLevelOnImport"), + .enableUpcomingFeature("ExistentialAny") ] } -if let target = package.targets.filter({ $0.name == "CMinizip" }).first { +if let target = package.targets.first(where: { $0.name == "CMinizip" }) { #if os(Windows) - if ProcessInfo.processInfo.environment["ZIP_USE_DYNAMIC_ZLIB"] == nil { - target.cSettings?.append(contentsOf: [.define("ZLIB_STATIC")]) - target.linkerSettings = [.linkedLibrary("zlibstatic")] - } else { - target.linkerSettings = [.linkedLibrary("zlib")] - } + if Context.environment["ZIP_USE_DYNAMIC_ZLIB"] == nil { + target.cSettings?.append(contentsOf: [.define("ZLIB_STATIC")]) + target.linkerSettings = [.linkedLibrary("zlibstatic")] + } else { + target.linkerSettings = [.linkedLibrary("zlib")] + } #else - target.linkerSettings = [.linkedLibrary("z")] + target.linkerSettings = [.linkedLibrary("z")] #endif } diff --git a/README.md b/README.md index 57df6a85..ec6eff1e 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,10 @@ Continuous Integration - + Code Coverage - Swift 5.10+ + Swift 6.0+
@@ -40,7 +40,7 @@ and add it to your target's dependencies: ### Supported Platforms -Zip supports all platforms supported by Swift 5.10 and later. +Zip supports all platforms supported by Swift 6.0 and later. To use Zip on Windows, you need to pass an available build of `zlib` to the build via extended flags. For example: diff --git a/Sources/Zip/Date+dosDate.swift b/Sources/Zip/Date+dosDate.swift index d4834282..aea5d5aa 100644 --- a/Sources/Zip/Date+dosDate.swift +++ b/Sources/Zip/Date+dosDate.swift @@ -1,7 +1,7 @@ -#if canImport(Darwin) || compiler(<6.0) - import Foundation +#if canImport(FoundationEssentials) +import FoundationEssentials #else - import FoundationEssentials +import Foundation #endif extension Date { diff --git a/Sources/Zip/QuickZip.swift b/Sources/Zip/QuickZip.swift index 5d7fdc00..a89b29bd 100644 --- a/Sources/Zip/QuickZip.swift +++ b/Sources/Zip/QuickZip.swift @@ -6,10 +6,10 @@ // Copyright © 2016 Roy Marmelstein. All rights reserved. // -#if canImport(Darwin) || compiler(<6.0) - import Foundation +#if canImport(FoundationEssentials) +import FoundationEssentials #else - import FoundationEssentials +import Foundation #endif extension Zip { @@ -23,7 +23,7 @@ extension Zip { /// /// - Returns: `URL` of the destination folder. public class func quickUnzipFile(_ path: URL) throws -> URL { - return try quickUnzipFile(path, progress: nil) + try quickUnzipFile(path, progress: nil) } /// Unzips a file with less configuration. @@ -57,7 +57,7 @@ extension Zip { /// /// - Returns: `URL` of the destination folder. public class func quickZipFiles(_ paths: [URL], fileName: String) throws -> URL { - return try quickZipFiles(paths, fileName: fileName, progress: nil) + try quickZipFiles(paths, fileName: fileName, progress: nil) } /// Zips files with less configuration. diff --git a/Sources/Zip/URL+nativePath.swift b/Sources/Zip/URL+nativePath.swift index f15e2a5d..6d6227e5 100644 --- a/Sources/Zip/URL+nativePath.swift +++ b/Sources/Zip/URL+nativePath.swift @@ -1,11 +1,11 @@ -#if canImport(Darwin) || compiler(<6.0) - import Foundation +#if canImport(FoundationEssentials) +import FoundationEssentials #else - import FoundationEssentials +import Foundation #endif extension URL { var nativePath: String { - return withUnsafeFileSystemRepresentation { String(cString: $0!) } + withUnsafeFileSystemRepresentation { String(cString: $0!) } } } diff --git a/Sources/Zip/Zip.docc/Documentation.md b/Sources/Zip/Zip.docc/Documentation.md index 5c875293..0c7959b8 100644 --- a/Sources/Zip/Zip.docc/Documentation.md +++ b/Sources/Zip/Zip.docc/Documentation.md @@ -31,7 +31,7 @@ and add it to your target's dependencies: ### Supported Platforms -Zip supports all platforms supported by Swift 5.10 and later. +Zip supports all platforms supported by Swift 6.0 and later. To use Zip on Windows, you need to pass an available build of `zlib` to the build via extended flags. For example: diff --git a/Sources/Zip/Zip.swift b/Sources/Zip/Zip.swift index 75d44475..c4d5a3ca 100644 --- a/Sources/Zip/Zip.swift +++ b/Sources/Zip/Zip.swift @@ -66,7 +66,7 @@ public class Zip { } #if os(Windows) - var fileNames = Set() + var fileNames = Set() #endif var buffer = [CUnsignedChar](repeating: 0, count: 4096) @@ -101,23 +101,23 @@ public class Zip { var pathString = String(cString: fileName) #if os(Windows) - // Windows Reserved Characters - let reservedCharacters: CharacterSet = ["<", ">", ":", "\"", "|", "?", "*"] - - if pathString.rangeOfCharacter(from: reservedCharacters) != nil { - pathString = pathString.components(separatedBy: reservedCharacters).joined(separator: "_") - - let pathExtension = (pathString as NSString).pathExtension - let pathWithoutExtension = (pathString as NSString).deletingPathExtension - var counter = 1 - while fileNames.contains(pathString) { - let newFileName = "\(pathWithoutExtension) (\(counter))" - pathString = pathExtension.isEmpty ? newFileName : newFileName.appendingPathExtension(pathExtension) ?? newFileName - counter += 1 - } + // Windows Reserved Characters + let reservedCharacters: CharacterSet = ["<", ">", ":", "\"", "|", "?", "*"] + + if pathString.rangeOfCharacter(from: reservedCharacters) != nil { + pathString = pathString.components(separatedBy: reservedCharacters).joined(separator: "_") + + let pathExtension = (pathString as NSString).pathExtension + let pathWithoutExtension = (pathString as NSString).deletingPathExtension + var counter = 1 + while fileNames.contains(pathString) { + let newFileName = "\(pathWithoutExtension) (\(counter))" + pathString = pathExtension.isEmpty ? newFileName : newFileName.appendingPathExtension(pathExtension) ?? newFileName + counter += 1 } + } - fileNames.insert(pathString) + fileNames.insert(pathString) #endif guard !pathString.isEmpty else { @@ -136,16 +136,11 @@ public class Zip { throw ZipError.unzipFail } - let directoryAttributes: [FileAttributeKey: Any]? - #if (os(Linux) || os(Windows)) && compiler(<6.0) - directoryAttributes = nil - #else - let creationDate = Date() - directoryAttributes = [ - .creationDate: creationDate, - .modificationDate: creationDate, - ] - #endif + let creationDate = Date() + let directoryAttributes: [FileAttributeKey: Any]? = [ + .creationDate: creationDate, + .modificationDate: creationDate, + ] let isDirectory = fileName[Int(fileInfo.size_filename - 1)] == "/".cString(using: String.Encoding.utf8)?.first diff --git a/Sources/Zip/ZipCompression.swift b/Sources/Zip/ZipCompression.swift index cd4269ba..cdd196b9 100644 --- a/Sources/Zip/ZipCompression.swift +++ b/Sources/Zip/ZipCompression.swift @@ -9,14 +9,10 @@ public enum ZipCompression: Int { internal var minizipCompression: Int32 { switch self { - case .NoCompression: - return Z_NO_COMPRESSION - case .BestSpeed: - return Z_BEST_SPEED - case .DefaultCompression: - return Z_DEFAULT_COMPRESSION - case .BestCompression: - return Z_BEST_COMPRESSION + case .NoCompression: Z_NO_COMPRESSION + case .BestSpeed: Z_BEST_SPEED + case .DefaultCompression: Z_DEFAULT_COMPRESSION + case .BestCompression: Z_BEST_COMPRESSION } } } diff --git a/Sources/Zip/ZipError.swift b/Sources/Zip/ZipError.swift index 5bbf0eae..5504e347 100644 --- a/Sources/Zip/ZipError.swift +++ b/Sources/Zip/ZipError.swift @@ -1,7 +1,7 @@ -#if canImport(Darwin) || compiler(<6.0) - import Foundation +#if canImport(FoundationEssentials) +import FoundationEssentials #else - import FoundationEssentials +import Foundation #endif /// Errors that can be thrown by Zip. diff --git a/Tests/ZipTests/ZipTests.swift b/Tests/ZipTests/ZipTests.swift index d94ecced..57122b52 100644 --- a/Tests/ZipTests/ZipTests.swift +++ b/Tests/ZipTests/ZipTests.swift @@ -6,196 +6,222 @@ // Copyright © 2015 Roy Marmelstein. All rights reserved. // -import XCTest +import Foundation +import Testing @testable import Zip -final class ZipTests: XCTestCase { +@Suite("Zip Tests", .serialized) +struct ZipTests { private func url(forResource resource: String, withExtension ext: String? = nil) -> URL? { Bundle.module.url(forResource: "TestResources/\(resource)", withExtension: ext) } - private func autoRemovingSandbox() throws -> URL { + private func withSandbox(_ block: (URL) throws -> Void) throws { let sandbox = FileManager.default.temporaryDirectory.appendingPathComponent("ZipTests_" + UUID().uuidString, isDirectory: true) // We can always create it. UUID should be unique. try FileManager.default.createDirectory(at: sandbox, withIntermediateDirectories: true, attributes: nil) - // Schedule the teardown block _after_ the directory has been created (so that if it fails, no teardown block is registered). - addTeardownBlock { - do { - try FileManager.default.removeItem(at: sandbox) - } catch { - print("Could not remove test sandbox at '\(sandbox.path)': \(error)") - } + + try block(sandbox) + + do { + try FileManager.default.removeItem(at: sandbox) + } catch { + print("Could not remove test sandbox at '\(sandbox.path)': \(error)") } - return sandbox } - func testQuickUnzip() throws { + @Test("Quick Unzip") + func quickUnzip() throws { let filePath = url(forResource: "bb8", withExtension: "zip")! let destinationURL = try Zip.quickUnzipFile(filePath) - addTeardownBlock { + defer { try? FileManager.default.removeItem(at: destinationURL) } - XCTAssertTrue(FileManager.default.fileExists(atPath: destinationURL.path)) - try XCTAssertGreaterThan(Data(contentsOf: destinationURL.appendingPathComponent("3crBXeO.gif")).count, 0) - try XCTAssertGreaterThan(Data(contentsOf: destinationURL.appendingPathComponent("kYkLkPf.gif")).count, 0) + #expect(FileManager.default.fileExists(atPath: destinationURL.path)) + #expect(try !Data(contentsOf: destinationURL.appendingPathComponent("3crBXeO.gif")).isEmpty) + #expect(try !Data(contentsOf: destinationURL.appendingPathComponent("kYkLkPf.gif")).isEmpty) } - func testQuickUnzipNonExistingPath() { + @Test("Quick Unzip with Non-Existing Path") + func quickUnzipNonExistingPath() { let filePath = URL(fileURLWithPath: "/some/path/to/nowhere/bb9.zip") - XCTAssertThrowsError(try Zip.quickUnzipFile(filePath)) + #expect(throws: (any Error).self) { try Zip.quickUnzipFile(filePath) } } - func testQuickUnzipNonZipPath() { + @Test("Quick Unzip with Non-Zip Path") + func quickUnzipNonZipPath() { let filePath = url(forResource: "3crBXeO", withExtension: "gif")! - XCTAssertThrowsError(try Zip.quickUnzipFile(filePath)) + #expect(throws: (any Error).self) { try Zip.quickUnzipFile(filePath) } } - func testQuickUnzipProgress() throws { + @Test("Quick Unzip with Progress") + func quickUnzipProgress() throws { let filePath = url(forResource: "bb8", withExtension: "zip")! let destinationURL = try Zip.quickUnzipFile(filePath) { progress in - XCTAssertFalse(progress.isNaN) + #expect(!progress.isNaN) } - addTeardownBlock { + defer { try? FileManager.default.removeItem(at: destinationURL) } - XCTAssertTrue(FileManager.default.fileExists(atPath: destinationURL.path)) - try XCTAssertGreaterThan(Data(contentsOf: destinationURL.appendingPathComponent("3crBXeO.gif")).count, 0) - try XCTAssertGreaterThan(Data(contentsOf: destinationURL.appendingPathComponent("kYkLkPf.gif")).count, 0) + #expect(FileManager.default.fileExists(atPath: destinationURL.path)) + #expect(try !Data(contentsOf: destinationURL.appendingPathComponent("3crBXeO.gif")).isEmpty) + #expect(try !Data(contentsOf: destinationURL.appendingPathComponent("kYkLkPf.gif")).isEmpty) } - func testQuickUnzipOnlineURL() { + @Test("Quick Unzip with Online URL") + func quickUnzipOnlineURL() { let filePath = URL(string: "http://www.google.com/google.zip")! - XCTAssertThrowsError(try Zip.quickUnzipFile(filePath)) + #expect(throws: (any Error).self) { try Zip.quickUnzipFile(filePath) } } - func testUnzip() throws { + @Test("Unzip") + func unzip() throws { let filePath = url(forResource: "bb8", withExtension: "zip")! - let destinationPath = try autoRemovingSandbox() - XCTAssertNoThrow(try Zip.unzipFile(filePath, destination: destinationPath, overwrite: true, password: "password", progress: nil)) + try withSandbox { destinationPath in + #expect(throws: Never.self) { + try Zip.unzipFile(filePath, destination: destinationPath, overwrite: true, password: "password", progress: nil) + } - XCTAssertTrue(FileManager.default.fileExists(atPath: destinationPath.path)) - try XCTAssertGreaterThan(Data(contentsOf: destinationPath.appendingPathComponent("3crBXeO.gif")).count, 0) - try XCTAssertGreaterThan(Data(contentsOf: destinationPath.appendingPathComponent("kYkLkPf.gif")).count, 0) + #expect(FileManager.default.fileExists(atPath: destinationPath.path)) + try #expect(!Data(contentsOf: destinationPath.appendingPathComponent("3crBXeO.gif")).isEmpty) + try #expect(!Data(contentsOf: destinationPath.appendingPathComponent("kYkLkPf.gif")).isEmpty) + } } - func testImplicitProgressUnzip() throws { + @Test("Implicit Progress Unzip") + func implicitProgressUnzip() throws { let progress = Progress(totalUnitCount: 1) let filePath = url(forResource: "bb8", withExtension: "zip")! - let destinationPath = try autoRemovingSandbox() - progress.becomeCurrent(withPendingUnitCount: 1) - try Zip.unzipFile(filePath, destination: destinationPath, overwrite: true, password: "password", progress: nil) - progress.resignCurrent() + try withSandbox { destinationPath in + progress.becomeCurrent(withPendingUnitCount: 1) + try Zip.unzipFile(filePath, destination: destinationPath, overwrite: true, password: "password", progress: nil) + progress.resignCurrent() - XCTAssertTrue(progress.totalUnitCount == progress.completedUnitCount) + #expect(progress.totalUnitCount == progress.completedUnitCount) + } } - func testImplicitProgressZip() throws { + @Test("Implicit Progress Zip") + func implicitProgressZip() throws { let progress = Progress(totalUnitCount: 1) let imageURL1 = url(forResource: "3crBXeO", withExtension: "gif")! let imageURL2 = url(forResource: "kYkLkPf", withExtension: "gif")! - let sandboxFolder = try autoRemovingSandbox() - let zipFilePath = sandboxFolder.appendingPathComponent("archive.zip") + try withSandbox { sandboxFolder in + let zipFilePath = sandboxFolder.appendingPathComponent("archive.zip") - progress.becomeCurrent(withPendingUnitCount: 1) - try Zip.zipFiles(paths: [imageURL1, imageURL2], zipFilePath: zipFilePath, password: nil, progress: nil) - progress.resignCurrent() + progress.becomeCurrent(withPendingUnitCount: 1) + try Zip.zipFiles(paths: [imageURL1, imageURL2], zipFilePath: zipFilePath, password: nil, progress: nil) + progress.resignCurrent() - XCTAssertTrue(progress.totalUnitCount == progress.completedUnitCount) + #expect(progress.totalUnitCount == progress.completedUnitCount) + } } - func testQuickZip() throws { + @Test("Quick Zip") + func quickZip() throws { let imageURL1 = url(forResource: "3crBXeO", withExtension: "gif")! let imageURL2 = url(forResource: "kYkLkPf", withExtension: "gif")! let destinationURL = try Zip.quickZipFiles([imageURL1, imageURL2], fileName: "archive.zip") - XCTAssertTrue(FileManager.default.fileExists(atPath: destinationURL.path)) - try XCTAssertGreaterThan(Data(contentsOf: destinationURL).count, 0) - addTeardownBlock { + defer { try? FileManager.default.removeItem(at: destinationURL) } + #expect(FileManager.default.fileExists(atPath: destinationURL.path)) + #expect(try !Data(contentsOf: destinationURL).isEmpty) } - func testQuickZipProgress() throws { + @Test("Quick Zip with Progress") + func quickZipProgress() throws { let imageURL1 = url(forResource: "3crBXeO", withExtension: "gif")! let imageURL2 = url(forResource: "kYkLkPf", withExtension: "gif")! let destinationURL = try Zip.quickZipFiles([imageURL1, imageURL2], fileName: "archive") { progress in - XCTAssertFalse(progress.isNaN) + #expect(!progress.isNaN) } - XCTAssertTrue(FileManager.default.fileExists(atPath: destinationURL.path)) - try XCTAssertGreaterThan(Data(contentsOf: destinationURL).count, 0) - addTeardownBlock { + defer { try? FileManager.default.removeItem(at: destinationURL) } + #expect(FileManager.default.fileExists(atPath: destinationURL.path)) + #expect(try !Data(contentsOf: destinationURL).isEmpty) } - func testQuickZipFolder() throws { + @Test("Quick Zip Folder") + func quickZipFolder() throws { let fileManager = FileManager.default let imageURL1 = url(forResource: "3crBXeO", withExtension: "gif")! let imageURL2 = url(forResource: "kYkLkPf", withExtension: "gif")! - let folderURL = try autoRemovingSandbox() - let targetImageURL1 = folderURL.appendingPathComponent("3crBXeO.gif") - let targetImageURL2 = folderURL.appendingPathComponent("kYkLkPf.gif") - try fileManager.copyItem(at: imageURL1, to: targetImageURL1) - try fileManager.copyItem(at: imageURL2, to: targetImageURL2) - let destinationURL = try Zip.quickZipFiles([folderURL], fileName: "directory") - XCTAssertTrue(fileManager.fileExists(atPath: destinationURL.path)) - addTeardownBlock { - try? FileManager.default.removeItem(at: destinationURL) + try withSandbox { folderURL in + let targetImageURL1 = folderURL.appendingPathComponent("3crBXeO.gif") + let targetImageURL2 = folderURL.appendingPathComponent("kYkLkPf.gif") + try fileManager.copyItem(at: imageURL1, to: targetImageURL1) + try fileManager.copyItem(at: imageURL2, to: targetImageURL2) + let destinationURL = try Zip.quickZipFiles([folderURL], fileName: "directory") + defer { + try? FileManager.default.removeItem(at: destinationURL) + } + #expect(fileManager.fileExists(atPath: destinationURL.path)) } } - func testZip() throws { + @Test("Zip") + func zip() throws { let imageURL1 = url(forResource: "3crBXeO", withExtension: "gif")! let imageURL2 = url(forResource: "kYkLkPf", withExtension: "gif")! - let sandboxFolder = try autoRemovingSandbox() - let zipFilePath = sandboxFolder.appendingPathComponent("archive.zip") - XCTAssertNoThrow(try Zip.zipFiles(paths: [imageURL1, imageURL2], zipFilePath: zipFilePath, password: nil, progress: nil)) - XCTAssertTrue(FileManager.default.fileExists(atPath: zipFilePath.path)) + try withSandbox { sandboxFolder in + let zipFilePath = sandboxFolder.appendingPathComponent("archive.zip") + #expect(throws: Never.self) { + try Zip.zipFiles(paths: [imageURL1, imageURL2], zipFilePath: zipFilePath, password: nil, progress: nil) + } + #expect(FileManager.default.fileExists(atPath: zipFilePath.path)) + } } - func testZipUnzipPassword() throws { + @Test("Zip and Unzip with Password") + func zipUnzipPassword() throws { let imageURL1 = url(forResource: "3crBXeO", withExtension: "gif")! let imageURL2 = url(forResource: "kYkLkPf", withExtension: "gif")! - let zipFilePath = try autoRemovingSandbox().appendingPathComponent("archive.zip") - try Zip.zipFiles(paths: [imageURL1, imageURL2], zipFilePath: zipFilePath, password: "password", progress: nil) - let fileManager = FileManager.default - XCTAssertTrue(fileManager.fileExists(atPath: zipFilePath.path)) - let directoryName = zipFilePath.lastPathComponent.replacingOccurrences(of: ".\(zipFilePath.pathExtension)", with: "") - let destinationUrl = try autoRemovingSandbox().appendingPathComponent(directoryName, isDirectory: true) - try Zip.unzipFile(zipFilePath, destination: destinationUrl, overwrite: true, password: "password", progress: nil) - XCTAssertTrue(fileManager.fileExists(atPath: destinationUrl.path)) + try withSandbox { firstSandbox in + let zipFilePath = firstSandbox.appendingPathComponent("archive.zip") + try Zip.zipFiles(paths: [imageURL1, imageURL2], zipFilePath: zipFilePath, password: "password", progress: nil) + let fileManager = FileManager.default + #expect(fileManager.fileExists(atPath: zipFilePath.path)) + let directoryName = zipFilePath.lastPathComponent.replacingOccurrences(of: ".\(zipFilePath.pathExtension)", with: "") + try withSandbox { secondSandbox in + let destinationUrl = secondSandbox.appendingPathComponent(directoryName, isDirectory: true) + try Zip.unzipFile(zipFilePath, destination: destinationUrl, overwrite: true, password: "password", progress: nil) + #expect(fileManager.fileExists(atPath: destinationUrl.path)) + } + } } - func testUnzipWithUnsupportedPermissions() throws { + @Test("Unzip with Unsupported Permissions") + func unzipWithUnsupportedPermissions() throws { let permissionsURL = url(forResource: "unsupported_permissions", withExtension: "zip")! let unzipDestination = try Zip.quickUnzipFile(permissionsURL) let permission644 = unzipDestination.appendingPathComponent("unsupported_permission").appendingPathExtension("txt") let foundPermissions = try FileManager.default.attributesOfItem(atPath: permission644.path)[.posixPermissions] as? Int - #if os(Windows) && compiler(<6.0) - let expectedPermissions = 0o700 - #elseif os(Windows) && compiler(>=6.0) - let expectedPermissions = 0o600 + #if os(Windows) + let expectedPermissions = 0o600 #elseif os(Android) - let expectedPermissions = 0o666 + let expectedPermissions = 0o666 #else - let expectedPermissions = 0o644 + let expectedPermissions = 0o644 #endif - XCTAssertNotNil(foundPermissions) - XCTAssertEqual( - foundPermissions, - expectedPermissions, + try #require(foundPermissions != nil) + #expect( + foundPermissions == expectedPermissions, "\(foundPermissions.map { String($0, radix: 8) } ?? "nil") is not equal to \(String(expectedPermissions, radix: 8))" ) } - func testUnzipPermissions() throws { + @Test("Unzip with Permissions") + func unzipPermissions() throws { let permissionsURL = url(forResource: "permissions", withExtension: "zip")! let unzipDestination = try Zip.quickUnzipFile(permissionsURL) - addTeardownBlock { + defer { try? FileManager.default.removeItem(at: unzipDestination) } let fileManager = FileManager.default @@ -206,82 +232,81 @@ final class ZipTests: XCTestCase { let attributes777 = try fileManager.attributesOfItem(atPath: permission777.path) let attributes600 = try fileManager.attributesOfItem(atPath: permission600.path) let attributes604 = try fileManager.attributesOfItem(atPath: permission604.path) - #if os(Windows) && compiler(<6.0) - XCTAssertEqual(attributes777[.posixPermissions] as? Int, 0o700) - XCTAssertEqual(attributes600[.posixPermissions] as? Int, 0o700) - XCTAssertEqual(attributes604[.posixPermissions] as? Int, 0o700) - #elseif os(Windows) && compiler(>=6.0) - XCTAssertEqual(attributes777[.posixPermissions] as? Int, 0o600) - XCTAssertEqual(attributes600[.posixPermissions] as? Int, 0o600) - XCTAssertEqual(attributes604[.posixPermissions] as? Int, 0o600) + #if os(Windows) + #expect(attributes777[.posixPermissions] as? Int == 0o600) + #expect(attributes600[.posixPermissions] as? Int == 0o600) + #expect(attributes604[.posixPermissions] as? Int == 0o600) #else - XCTAssertEqual(attributes777[.posixPermissions] as? Int, 0o777) - XCTAssertEqual(attributes600[.posixPermissions] as? Int, 0o600) - XCTAssertEqual(attributes604[.posixPermissions] as? Int, 0o604) + #expect(attributes777[.posixPermissions] as? Int == 0o777) + #expect(attributes600[.posixPermissions] as? Int == 0o600) + #expect(attributes604[.posixPermissions] as? Int == 0o604) #endif } - // Tests if https://github.com/marmelroy/Zip/issues/245 does not uccor anymore. - func testUnzipProtectsAgainstPathTraversal() throws { + @Test("Unzip protects against Path Traversal", .bug("https://github.com/marmelroy/Zip/issues/245")) + func unzipProtectsAgainstPathTraversal() throws { let filePath = url(forResource: "pathTraversal", withExtension: "zip")! - let destinationPath = try autoRemovingSandbox() - - do { - try Zip.unzipFile(filePath, destination: destinationPath, overwrite: true, password: "password", progress: nil) - XCTFail("ZipError.unzipFail expected.") - } catch {} - - XCTAssertFalse( - FileManager.default.fileExists( - atPath: destinationPath.appendingPathComponent("../naughtyFile.txt").path + try withSandbox { destinationPath in + do { + try Zip.unzipFile(filePath, destination: destinationPath, overwrite: true, password: "password", progress: nil) + Issue.record("ZipError.unzipFail expected.") + } catch {} + + #expect( + !FileManager.default.fileExists( + atPath: destinationPath.appendingPathComponent("../naughtyFile.txt").path + ) ) - ) + } } - func testQuickUnzipSubDir() throws { + @Test("Quick Unzip Subdirectory") + func quickUnzipSubDir() throws { let bookURL = url(forResource: "bb8", withExtension: "zip")! let unzipDestination = try Zip.quickUnzipFile(bookURL) - addTeardownBlock { + defer { try? FileManager.default.removeItem(at: unzipDestination) } let fileManager = FileManager.default let subDir = unzipDestination.appendingPathComponent("subDir") let imageURL = subDir.appendingPathComponent("r2W9yu9").appendingPathExtension("gif") - XCTAssertTrue(fileManager.fileExists(atPath: unzipDestination.path)) - XCTAssertTrue(fileManager.fileExists(atPath: subDir.path)) - XCTAssertTrue(fileManager.fileExists(atPath: imageURL.path)) + #expect(fileManager.fileExists(atPath: unzipDestination.path)) + #expect(fileManager.fileExists(atPath: subDir.path)) + #expect(fileManager.fileExists(atPath: imageURL.path)) } - func testAddedCustomFileExtensionIsValid() { + @Test("Added Custom File Extension Is Valid") + func addedCustomFileExtensionIsValid() { let fileExtension = "cstm" Zip.addCustomFileExtension(fileExtension) let result = Zip.isValidFileExtension(fileExtension) - XCTAssertTrue(result) + #expect(result) Zip.removeCustomFileExtension(fileExtension) } - func testRemovedCustomFileExtensionIsInvalid() { + @Test("Removed Custom File Extension Is Invalid") + func removedCustomFileExtensionIsInvalid() { let fileExtension = "cstm" Zip.addCustomFileExtension(fileExtension) Zip.removeCustomFileExtension(fileExtension) let result = Zip.isValidFileExtension(fileExtension) - XCTAssertFalse(result) + #expect(!result) } - func testDefaultFileExtensionsIsValid() { - XCTAssertTrue(Zip.isValidFileExtension("zip")) - XCTAssertTrue(Zip.isValidFileExtension("cbz")) + @Test("Default File Extensions Are Valid", arguments: ["zip", "cbz"]) + func defaultFileExtensionsIsValid(fileExtension: String) { + #expect(Zip.isValidFileExtension(fileExtension)) } - func testDefaultFileExtensionsIsNotRemoved() { - Zip.removeCustomFileExtension("zip") - Zip.removeCustomFileExtension("cbz") - XCTAssertTrue(Zip.isValidFileExtension("zip")) - XCTAssertTrue(Zip.isValidFileExtension("cbz")) + @Test("Default File Extensions Are Not Removed", arguments: ["zip", "cbz"]) + func defaultFileExtensionsAreNotRemoved(fileExtension: String) { + Zip.removeCustomFileExtension(fileExtension) + #expect(Zip.isValidFileExtension(fileExtension)) } - func testZipData() throws { + @Test("Zip Data") + func zipData() throws { let archiveFile1 = ArchiveFile(filename: "file1.txt", data: "Hello, World!".data(using: .utf8)!) let archiveFile2 = ArchiveFile( filename: "file2.txt", @@ -289,13 +314,15 @@ final class ZipTests: XCTestCase { modifiedTime: Date() ) let emptyArchiveFile = ArchiveFile(filename: "empty.txt", data: Data()) - let sandboxFolder = try autoRemovingSandbox() - let zipFilePath = sandboxFolder.appendingPathComponent("archive.zip") - try Zip.zipData(archiveFiles: [archiveFile1, archiveFile2, emptyArchiveFile], zipFilePath: zipFilePath) - XCTAssertTrue(FileManager.default.fileExists(atPath: zipFilePath.path)) + try withSandbox { sandboxFolder in + let zipFilePath = sandboxFolder.appendingPathComponent("archive.zip") + try Zip.zipData(archiveFiles: [archiveFile1, archiveFile2, emptyArchiveFile], zipFilePath: zipFilePath) + #expect(FileManager.default.fileExists(atPath: zipFilePath.path)) + } } - func testZipDataProgress() throws { + @Test("Zip Data with Progress") + func zipDataProgress() throws { let archiveFile1 = ArchiveFile(filename: "file1.txt", data: "Hello, World!".data(using: .utf8)!) let archiveFile2 = ArchiveFile( filename: "file2.txt", @@ -303,74 +330,87 @@ final class ZipTests: XCTestCase { modifiedTime: Date() ) let emptyArchiveFile = ArchiveFile(filename: "empty.txt", data: Data()) - let sandboxFolder = try autoRemovingSandbox() - let zipFilePath = sandboxFolder.appendingPathComponent("archive.zip") - try Zip.zipData(archiveFiles: [archiveFile1, archiveFile2, emptyArchiveFile], zipFilePath: zipFilePath) { progress in - XCTAssertFalse(progress.isNaN) + try withSandbox { sandboxFolder in + let zipFilePath = sandboxFolder.appendingPathComponent("archive.zip") + try Zip.zipData(archiveFiles: [archiveFile1, archiveFile2, emptyArchiveFile], zipFilePath: zipFilePath) { progress in + #expect(!progress.isNaN) + } + #expect(FileManager.default.fileExists(atPath: zipFilePath.path)) } - XCTAssertTrue(FileManager.default.fileExists(atPath: zipFilePath.path)) } - func testZipError() { - XCTAssertEqual(ZipError.fileNotFound.description, "File not found.") - XCTAssertEqual(ZipError.unzipFail.description, "Failed to unzip file.") - XCTAssertEqual(ZipError.zipFail.description, "Failed to zip file.") + @Test("Zip Error") + func zipError() { + #expect(ZipError.fileNotFound.description == "File not found.") + #expect(ZipError.unzipFail.description == "Failed to unzip file.") + #expect(ZipError.zipFail.description == "Failed to zip file.") } - func testZipCompression() { - XCTAssertEqual(ZipCompression.NoCompression.minizipCompression, 0) - XCTAssertEqual(ZipCompression.BestSpeed.minizipCompression, 1) - XCTAssertEqual(ZipCompression.DefaultCompression.minizipCompression, -1) - XCTAssertEqual(ZipCompression.BestCompression.minizipCompression, 9) + @Test("Zip Compression") + func zipCompression() { + #expect(ZipCompression.NoCompression.minizipCompression == 0) + #expect(ZipCompression.BestSpeed.minizipCompression == 1) + #expect(ZipCompression.DefaultCompression.minizipCompression == -1) + #expect(ZipCompression.BestCompression.minizipCompression == 9) } - func testDosDate() { + @Test("DOS Date") + func dosDate() { NSTimeZone.default = NSTimeZone(forSecondsFromGMT: 0) as TimeZone - XCTAssertEqual(0b10000011_00110001_10001100_00110001, Date(timeIntervalSince1970: 2_389_282_415).dosDate) - XCTAssertEqual(0b00000001_00110001_10001100_00110001, Date(timeIntervalSince1970: 338_060_015).dosDate) - XCTAssertEqual(0b00000000_00100001_00000000_00000000, Date(timeIntervalSince1970: 315_532_800).dosDate) + #expect(0b10000011_00110001_10001100_00110001 == Date(timeIntervalSince1970: 2_389_282_415).dosDate) + #expect(0b00000001_00110001_10001100_00110001 == Date(timeIntervalSince1970: 338_060_015).dosDate) + #expect(0b00000000_00100001_00000000_00000000 == Date(timeIntervalSince1970: 315_532_800).dosDate) } - func testInit() { + @Test("Zip Init") + func zipInit() { var zip: Zip? = Zip() - XCTAssertNotNil(zip) + #expect(zip != nil) zip = nil - XCTAssertNil(zip) + #expect(zip == nil) } - func testUnzipWithoutPassword() throws { + @Test("Unzip Without Password") + func unzipWithoutPassword() throws { let imageURL1 = url(forResource: "3crBXeO", withExtension: "gif")! let imageURL2 = url(forResource: "kYkLkPf", withExtension: "gif")! - let zipFilePath = try autoRemovingSandbox().appendingPathComponent("archive.zip") - try Zip.zipFiles(paths: [imageURL1, imageURL2], zipFilePath: zipFilePath, password: "password") - XCTAssertTrue(FileManager.default.fileExists(atPath: zipFilePath.path)) - let directoryName = zipFilePath.lastPathComponent.replacingOccurrences(of: ".\(zipFilePath.pathExtension)", with: "") - let destinationUrl = try autoRemovingSandbox().appendingPathComponent(directoryName, isDirectory: true) - XCTAssertThrowsError(try Zip.unzipFile(zipFilePath, destination: destinationUrl)) + try withSandbox { firstSandbox in + let zipFilePath = firstSandbox.appendingPathComponent("archive.zip") + try Zip.zipFiles(paths: [imageURL1, imageURL2], zipFilePath: zipFilePath, password: "password") + #expect(FileManager.default.fileExists(atPath: zipFilePath.path)) + let directoryName = zipFilePath.lastPathComponent.replacingOccurrences(of: ".\(zipFilePath.pathExtension)", with: "") + try withSandbox { secondSandbox in + let destinationUrl = secondSandbox.appendingPathComponent(directoryName, isDirectory: true) + #expect(throws: (any Error).self) { try Zip.unzipFile(zipFilePath, destination: destinationUrl) } + } + } } - func testFileHandler() throws { + @Test("File Handler") + func fileHandler() throws { let filePath = url(forResource: "bb8", withExtension: "zip")! - let destinationPath = try autoRemovingSandbox() - XCTAssertNoThrow( - try Zip.unzipFile( - filePath, destination: destinationPath, password: "password", - fileOutputHandler: { fileURL in - XCTAssertTrue(FileManager.default.fileExists(atPath: fileURL.path)) - } - ) - ) - XCTAssertTrue(FileManager.default.fileExists(atPath: destinationPath.path)) - try XCTAssertGreaterThan(Data(contentsOf: destinationPath.appendingPathComponent("3crBXeO.gif")).count, 0) - try XCTAssertGreaterThan(Data(contentsOf: destinationPath.appendingPathComponent("kYkLkPf.gif")).count, 0) + try withSandbox { destinationPath in + #expect(throws: Never.self) { + try Zip.unzipFile( + filePath, destination: destinationPath, password: "password", + fileOutputHandler: { fileURL in + #expect(FileManager.default.fileExists(atPath: fileURL.path)) + } + ) + } + #expect(FileManager.default.fileExists(atPath: destinationPath.path)) + try #expect(!Data(contentsOf: destinationPath.appendingPathComponent("3crBXeO.gif")).isEmpty) + try #expect(!Data(contentsOf: destinationPath.appendingPathComponent("kYkLkPf.gif")).isEmpty) + } } - // Tests if https://github.com/vapor-community/Zip/issues/4 does not occur anymore. - func testRoundTripping() throws { + @Test("Round Tripping", .bug("https://github.com/vapor-community/Zip/issues/4")) + func roundTripping() throws { // "prod-apple-swift-metrics-main-e6a00d36.zip" is the original zip file from the issue. let zipFilePath = url(forResource: "prod-apple-swift-metrics-main-e6a00d36", withExtension: "zip")! - let failDestinationPath = try autoRemovingSandbox() - XCTAssertThrowsError(try Zip.unzipFile(zipFilePath, destination: failDestinationPath, overwrite: true)) + try withSandbox { failDestinationPath in + #expect(throws: (any Error).self) { try Zip.unzipFile(zipFilePath, destination: failDestinationPath, overwrite: true) } + } // "prod-apple-swift-metrics-main-e6a00d36-finder.zip" is a zip file // that was created by unzipping the original zip file with Finder on macOS 14.6.1 @@ -380,50 +420,57 @@ final class ZipTests: XCTestCase { // that was created by unzipping the original zip file with Finder on macOS 14.6.1 // and then zipping it again using vapor-community/Zip v2.2.0. let testZipFilePath = url(forResource: "prod-apple-swift-metrics-main-e6a00d36-test", withExtension: "zip")! - let destinationPath = try autoRemovingSandbox() - XCTAssertNoThrow(try Zip.unzipFile(testZipFilePath, destination: destinationPath, overwrite: true)) - - let destinationFolder = destinationPath.appendingPathComponent("prod-apple-swift-metrics-main-e6a00d36") - XCTAssert(FileManager.default.fileExists(atPath: destinationFolder.appendingPathComponent("metadata.json").path)) - XCTAssert(FileManager.default.fileExists(atPath: destinationFolder.appendingPathComponent("main/index.html").path)) - XCTAssert(FileManager.default.fileExists(atPath: destinationFolder.appendingPathComponent("main/index/index.json").path)) - try XCTAssertGreaterThan(Data(contentsOf: destinationFolder.appendingPathComponent("metadata.json")).count, 0) - - let unzippedFiles = try FileManager.default.contentsOfDirectory(atPath: destinationFolder.path) - - let newZipFilePath = try autoRemovingSandbox().appendingPathComponent("new-archive.zip") - try Zip.zipFiles(paths: [destinationFolder], zipFilePath: newZipFilePath) - - let newDestinationPath = try autoRemovingSandbox() - try Zip.unzipFile(newZipFilePath, destination: newDestinationPath, overwrite: true) - - let newDestinationFolder = newDestinationPath.appendingPathComponent("prod-apple-swift-metrics-main-e6a00d36") - XCTAssert(FileManager.default.fileExists(atPath: newDestinationFolder.appendingPathComponent("metadata.json").path)) - XCTAssert(FileManager.default.fileExists(atPath: newDestinationFolder.appendingPathComponent("main/index.html").path)) - XCTAssert(FileManager.default.fileExists(atPath: newDestinationFolder.appendingPathComponent("main/index/index.json").path)) - try XCTAssertGreaterThan(Data(contentsOf: newDestinationFolder.appendingPathComponent("metadata.json")).count, 0) - - let newUnzippedFiles = try FileManager.default.contentsOfDirectory(atPath: newDestinationFolder.path) - XCTAssertEqual(unzippedFiles, newUnzippedFiles) + try withSandbox { destinationPath in + #expect(throws: Never.self) { try Zip.unzipFile(testZipFilePath, destination: destinationPath, overwrite: true) } + + let destinationFolder = destinationPath.appendingPathComponent("prod-apple-swift-metrics-main-e6a00d36") + #expect(FileManager.default.fileExists(atPath: destinationFolder.appendingPathComponent("metadata.json").path)) + #expect(FileManager.default.fileExists(atPath: destinationFolder.appendingPathComponent("main/index.html").path)) + #expect(FileManager.default.fileExists(atPath: destinationFolder.appendingPathComponent("main/index/index.json").path)) + #expect(try !Data(contentsOf: destinationFolder.appendingPathComponent("metadata.json")).isEmpty) + + let unzippedFiles = try FileManager.default.contentsOfDirectory(atPath: destinationFolder.path) + + try withSandbox { sandbox in + let newZipFilePath = sandbox.appendingPathComponent("new-archive.zip") + try Zip.zipFiles(paths: [destinationFolder], zipFilePath: newZipFilePath) + + try withSandbox { newDestinationPath in + try Zip.unzipFile(newZipFilePath, destination: newDestinationPath, overwrite: true) + + let newDestinationFolder = newDestinationPath.appendingPathComponent("prod-apple-swift-metrics-main-e6a00d36") + #expect(FileManager.default.fileExists(atPath: newDestinationFolder.appendingPathComponent("metadata.json").path)) + #expect(FileManager.default.fileExists(atPath: newDestinationFolder.appendingPathComponent("main/index.html").path)) + #expect( + FileManager.default.fileExists(atPath: newDestinationFolder.appendingPathComponent("main/index/index.json").path) + ) + #expect(try !Data(contentsOf: newDestinationFolder.appendingPathComponent("metadata.json")).isEmpty) + + let newUnzippedFiles = try FileManager.default.contentsOfDirectory(atPath: newDestinationFolder.path) + #expect(unzippedFiles == newUnzippedFiles) + } + } + } } #if os(Windows) - func testWindowsReservedChars() throws { - let txtFile = ArchiveFile(filename: "a_b.txt", data: "Hi Mom!".data(using: .utf8)!) - let txtFile1 = ArchiveFile(filename: "ab.txt", data: "Hello, Swift!".data(using: .utf8)!) - let txtFile3 = ArchiveFile(filename: "a:b.txt", data: "Hello, World!".data(using: .utf8)!) - let txtFile4 = ArchiveFile(filename: "a\"b.txt", data: "Hi Windows!".data(using: .utf8)!) - let txtFile5 = ArchiveFile(filename: "a|b.txt", data: "Hi Barbie!".data(using: .utf8)!) - let txtFile6 = ArchiveFile(filename: "a?b.txt", data: "Hi, Ken!".data(using: .utf8)!) - let txtFile7 = ArchiveFile(filename: "a*b.txt", data: "Hello Everyone!".data(using: .utf8)!) - - let file = ArchiveFile(filename: "a_b", data: "Hello, World!".data(using: .utf8)!) - let file1 = ArchiveFile(filename: "ab", data: "Hello, Swift!".data(using: .utf8)!) - let file3 = ArchiveFile(filename: "a:b", data: "Hello, World!".data(using: .utf8)!) - - let sandboxFolder = try autoRemovingSandbox() + @Test("Windows Reserved Characters") + func windowsReservedChars() throws { + let txtFile = ArchiveFile(filename: "a_b.txt", data: "Hi Mom!".data(using: .utf8)!) + let txtFile1 = ArchiveFile(filename: "ab.txt", data: "Hello, Swift!".data(using: .utf8)!) + let txtFile3 = ArchiveFile(filename: "a:b.txt", data: "Hello, World!".data(using: .utf8)!) + let txtFile4 = ArchiveFile(filename: "a\"b.txt", data: "Hi Windows!".data(using: .utf8)!) + let txtFile5 = ArchiveFile(filename: "a|b.txt", data: "Hi Barbie!".data(using: .utf8)!) + let txtFile6 = ArchiveFile(filename: "a?b.txt", data: "Hi, Ken!".data(using: .utf8)!) + let txtFile7 = ArchiveFile(filename: "a*b.txt", data: "Hello Everyone!".data(using: .utf8)!) + + let file = ArchiveFile(filename: "a_b", data: "Hello, World!".data(using: .utf8)!) + let file1 = ArchiveFile(filename: "ab", data: "Hello, Swift!".data(using: .utf8)!) + let file3 = ArchiveFile(filename: "a:b", data: "Hello, World!".data(using: .utf8)!) + + try withSandbox { sandboxFolder in let zipFilePath = sandboxFolder.appendingPathComponent("archive.zip") try Zip.zipData( archiveFiles: [ @@ -433,57 +480,62 @@ final class ZipTests: XCTestCase { zipFilePath: zipFilePath ) - let destinationPath = try autoRemovingSandbox() - try Zip.unzipFile(zipFilePath, destination: destinationPath) - - let txtFileURL = destinationPath.appendingPathComponent("a_b.txt") - let txtFile1URL = destinationPath.appendingPathComponent("a_b (1).txt") - let txtFile2URL = destinationPath.appendingPathComponent("a_b (2).txt") - let txtFile3URL = destinationPath.appendingPathComponent("a_b (3).txt") - let txtFile4URL = destinationPath.appendingPathComponent("a_b (4).txt") - let txtFile5URL = destinationPath.appendingPathComponent("a_b (5).txt") - let txtFile6URL = destinationPath.appendingPathComponent("a_b (6).txt") - let txtFile7URL = destinationPath.appendingPathComponent("a_b (7).txt") - - let fileURL = destinationPath.appendingPathComponent("a_b") - let file1URL = destinationPath.appendingPathComponent("a_b (1)") - let file2URL = destinationPath.appendingPathComponent("a_b (2)") - let file3URL = destinationPath.appendingPathComponent("a_b (3)") - - XCTAssertTrue(FileManager.default.fileExists(atPath: txtFileURL.path)) - XCTAssertTrue(FileManager.default.fileExists(atPath: txtFile1URL.path)) - XCTAssertTrue(FileManager.default.fileExists(atPath: txtFile2URL.path)) - XCTAssertTrue(FileManager.default.fileExists(atPath: txtFile3URL.path)) - XCTAssertTrue(FileManager.default.fileExists(atPath: txtFile4URL.path)) - XCTAssertTrue(FileManager.default.fileExists(atPath: txtFile5URL.path)) - XCTAssertTrue(FileManager.default.fileExists(atPath: txtFile6URL.path)) - XCTAssertTrue(FileManager.default.fileExists(atPath: txtFile7URL.path)) - - XCTAssertTrue(FileManager.default.fileExists(atPath: fileURL.path)) - XCTAssertTrue(FileManager.default.fileExists(atPath: file1URL.path)) - XCTAssertTrue(FileManager.default.fileExists(atPath: file2URL.path)) - XCTAssertTrue(FileManager.default.fileExists(atPath: file3URL.path)) + try withSandbox { destinationPath in + try Zip.unzipFile(zipFilePath, destination: destinationPath) + + let txtFileURL = destinationPath.appendingPathComponent("a_b.txt") + let txtFile1URL = destinationPath.appendingPathComponent("a_b (1).txt") + let txtFile2URL = destinationPath.appendingPathComponent("a_b (2).txt") + let txtFile3URL = destinationPath.appendingPathComponent("a_b (3).txt") + let txtFile4URL = destinationPath.appendingPathComponent("a_b (4).txt") + let txtFile5URL = destinationPath.appendingPathComponent("a_b (5).txt") + let txtFile6URL = destinationPath.appendingPathComponent("a_b (6).txt") + let txtFile7URL = destinationPath.appendingPathComponent("a_b (7).txt") + + let fileURL = destinationPath.appendingPathComponent("a_b") + let file1URL = destinationPath.appendingPathComponent("a_b (1)") + let file2URL = destinationPath.appendingPathComponent("a_b (2)") + let file3URL = destinationPath.appendingPathComponent("a_b (3)") + + #expect(FileManager.default.fileExists(atPath: txtFileURL.path)) + #expect(FileManager.default.fileExists(atPath: txtFile1URL.path)) + #expect(FileManager.default.fileExists(atPath: txtFile2URL.path)) + #expect(FileManager.default.fileExists(atPath: txtFile3URL.path)) + #expect(FileManager.default.fileExists(atPath: txtFile4URL.path)) + #expect(FileManager.default.fileExists(atPath: txtFile5URL.path)) + #expect(FileManager.default.fileExists(atPath: txtFile6URL.path)) + #expect(FileManager.default.fileExists(atPath: txtFile7URL.path)) + + #expect(FileManager.default.fileExists(atPath: fileURL.path)) + #expect(FileManager.default.fileExists(atPath: file1URL.path)) + #expect(FileManager.default.fileExists(atPath: file2URL.path)) + #expect(FileManager.default.fileExists(atPath: file3URL.path)) + } } + } #endif - func testPassKitExtensions() throws { + @Test("PassKit Extensions") + func passKitExtensions() throws { let pkpassURL = url(forResource: "PassKitTest", withExtension: "pkpass")! - let pkpassDestination = try autoRemovingSandbox() - Zip.addCustomFileExtension("pkpass") - XCTAssertNoThrow(try Zip.unzipFile(pkpassURL, destination: pkpassDestination)) - XCTAssert(FileManager.default.fileExists(atPath: pkpassDestination.appendingPathComponent("pass.json").path)) - XCTAssert(FileManager.default.fileExists(atPath: pkpassDestination.appendingPathComponent("manifest.json").path)) - XCTAssert(FileManager.default.fileExists(atPath: pkpassDestination.appendingPathComponent("signature").path)) - XCTAssert(FileManager.default.fileExists(atPath: pkpassDestination.appendingPathComponent("icon.png").path)) - XCTAssert(FileManager.default.fileExists(atPath: pkpassDestination.appendingPathComponent("logo.png").path)) + try withSandbox { pkpassDestination in + Zip.addCustomFileExtension("pkpass") + #expect(throws: Never.self) { try Zip.unzipFile(pkpassURL, destination: pkpassDestination) } + #expect(FileManager.default.fileExists(atPath: pkpassDestination.appendingPathComponent("pass.json").path)) + #expect(FileManager.default.fileExists(atPath: pkpassDestination.appendingPathComponent("manifest.json").path)) + #expect(FileManager.default.fileExists(atPath: pkpassDestination.appendingPathComponent("signature").path)) + #expect(FileManager.default.fileExists(atPath: pkpassDestination.appendingPathComponent("icon.png").path)) + #expect(FileManager.default.fileExists(atPath: pkpassDestination.appendingPathComponent("logo.png").path)) + } let orderURL = url(forResource: "PassKitTest", withExtension: "order")! - let orderDestination = try autoRemovingSandbox() - Zip.addCustomFileExtension("order") - XCTAssertNoThrow(try Zip.unzipFile(orderURL, destination: orderDestination)) - XCTAssert(FileManager.default.fileExists(atPath: orderDestination.appendingPathComponent("order.json").path)) - XCTAssert(FileManager.default.fileExists(atPath: orderDestination.appendingPathComponent("manifest.json").path)) - XCTAssert(FileManager.default.fileExists(atPath: orderDestination.appendingPathComponent("signature").path)) - XCTAssert(FileManager.default.fileExists(atPath: orderDestination.appendingPathComponent("icon.png").path)) + try withSandbox { orderDestination in + Zip.addCustomFileExtension("order") + #expect(throws: Never.self) { try Zip.unzipFile(orderURL, destination: orderDestination) } + #expect(FileManager.default.fileExists(atPath: orderDestination.appendingPathComponent("order.json").path)) + #expect(FileManager.default.fileExists(atPath: orderDestination.appendingPathComponent("manifest.json").path)) + #expect(FileManager.default.fileExists(atPath: orderDestination.appendingPathComponent("signature").path)) + #expect(FileManager.default.fileExists(atPath: orderDestination.appendingPathComponent("icon.png").path)) + } } }