From f47364ab799a5a7ab9ef6fe962ca4d659bea3f66 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 11 Jun 2020 20:45:27 +0100 Subject: [PATCH] Improve FileSystemError by adding associated path I've recently stumbled upon this error when running `swift build`: ``` error: noEntry ``` This is the exact and only output of this `swift build` invocation. Unfortunately, I think it's impossible to diagnose without an attached debugger. It's also hard to pinpoint where exactly the error comes from in the source code, since `FileSystemError.noEntry` is thrown from multiple places. In my opinion, for an error value to be helpful it should have associated information attached to it. In this case, it would make sense for almost all cases of `FileSystemError` to have an associated `AbsolutePath` value. `FileSystemError` now also has to be explicitly declared `Equatable` to make the tests compile, but previously it was `Equatable` anyway, although implicitly by virtue of being an enum with no associated values. --- Sources/TSCBasic/FileSystem.swift | 214 ++++++++++++---------- Sources/TSCBasic/Lock.swift | 2 +- Sources/TSCBasic/WritableByteStream.swift | 31 ++-- Sources/TSCUtility/Archiver.swift | 4 +- Tests/TSCBasicTests/FileSystemTests.swift | 115 ++++++------ Tests/TSCUtilityTests/ArchiverTests.swift | 15 +- 6 files changed, 209 insertions(+), 172 deletions(-) diff --git a/Sources/TSCBasic/FileSystem.swift b/Sources/TSCBasic/FileSystem.swift index 91cbdb80..77170da7 100644 --- a/Sources/TSCBasic/FileSystem.swift +++ b/Sources/TSCBasic/FileSystem.swift @@ -12,74 +12,87 @@ import TSCLibc import Foundation import Dispatch -public enum FileSystemError: Swift.Error { - /// Access to the path is denied. - /// - /// This is used when an operation cannot be completed because a component of - /// the path cannot be accessed. - /// - /// Used in situations that correspond to the POSIX EACCES error code. - case invalidAccess - - /// IO Error encoding - /// - /// This is used when an operation cannot be completed due to an otherwise - /// unspecified IO error. - case ioError - - /// Is a directory - /// - /// This is used when an operation cannot be completed because a component - /// of the path which was expected to be a file was not. - /// - /// Used in situations that correspond to the POSIX EISDIR error code. - case isDirectory - - /// No such path exists. - /// - /// This is used when a path specified does not exist, but it was expected - /// to. - /// - /// Used in situations that correspond to the POSIX ENOENT error code. - case noEntry - - /// Not a directory - /// - /// This is used when an operation cannot be completed because a component - /// of the path which was expected to be a directory was not. - /// - /// Used in situations that correspond to the POSIX ENOTDIR error code. - case notDirectory - - /// Unsupported operation - /// - /// This is used when an operation is not supported by the concrete file - /// system implementation. - case unsupported - - /// An unspecific operating system error. - case unknownOSError - - /// File or folder already exists at destination. - /// - /// This is thrown when copying or moving a file or directory but the destination - /// path already contains a file or folder. - case alreadyExistsAtDestination +public struct FileSystemError: Swift.Error, Equatable { + public enum Kind: Equatable { + /// Access to the path is denied. + /// + /// This is used when an operation cannot be completed because a component of + /// the path cannot be accessed. + /// + /// Used in situations that correspond to the POSIX EACCES error code. + case invalidAccess + + /// IO Error encoding + /// + /// This is used when an operation cannot be completed due to an otherwise + /// unspecified IO error. + case ioError(code: Int32) + + /// Is a directory + /// + /// This is used when an operation cannot be completed because a component + /// of the path which was expected to be a file was not. + /// + /// Used in situations that correspond to the POSIX EISDIR error code. + case isDirectory + + /// No such path exists. + /// + /// This is used when a path specified does not exist, but it was expected + /// to. + /// + /// Used in situations that correspond to the POSIX ENOENT error code. + case noEntry + + /// Not a directory + /// + /// This is used when an operation cannot be completed because a component + /// of the path which was expected to be a directory was not. + /// + /// Used in situations that correspond to the POSIX ENOTDIR error code. + case notDirectory + + /// Unsupported operation + /// + /// This is used when an operation is not supported by the concrete file + /// system implementation. + case unsupported + + /// An unspecific operating system error at a given path. + case unknownOSError + + /// File or folder already exists at destination. + /// + /// This is thrown when copying or moving a file or directory but the destination + /// path already contains a file or folder. + case alreadyExistsAtDestination + } + + /// The kind of the error being raised. + public let kind: Kind + + /// The absolute path to the file associated with the error, if available. + public let path: AbsolutePath? + + public init(_ kind: Kind, _ path: AbsolutePath? = nil) { + self.kind = kind + self.path = path + } } public extension FileSystemError { - init(errno: Int32) { + init(errno: Int32, _ path: AbsolutePath) { switch errno { case TSCLibc.EACCES: - self = .invalidAccess + self.init(.invalidAccess, path) case TSCLibc.EISDIR: - self = .isDirectory + self.init(.isDirectory, path) case TSCLibc.ENOENT: - self = .noEntry + self.init(.noEntry, path) case TSCLibc.ENOTDIR: - self = .notDirectory + self.init(.notDirectory, path) default: - self = .unknownOSError + self.init(.unknownOSError, path) } } } @@ -238,7 +251,7 @@ public extension FileSystem { // if `atomically` is `true`, otherwise fall back to whatever implementation already exists. func writeFileContents(_ path: AbsolutePath, bytes: ByteString, atomically: Bool) throws { guard !atomically else { - throw FileSystemError.unsupported + throw FileSystemError(.unsupported, path) } try writeFileContents(path, bytes: bytes) } @@ -252,11 +265,11 @@ public extension FileSystem { } func getFileInfo(_ path: AbsolutePath) throws -> FileInfo { - throw FileSystemError.unsupported + throw FileSystemError(.unsupported, path) } func withLock(on path: AbsolutePath, type: FileLock.LockType, _ body: () throws -> T) throws -> T { - throw FileSystemError.unsupported + throw FileSystemError(.unsupported, path) } } @@ -316,11 +329,11 @@ private class LocalFileSystem: FileSystem { func changeCurrentWorkingDirectory(to path: AbsolutePath) throws { guard isDirectory(path) else { - throw FileSystemError.notDirectory + throw FileSystemError(.notDirectory, path) } guard FileManager.default.changeCurrentDirectoryPath(path.pathString) else { - throw FileSystemError.unknownOSError + throw FileSystemError(.unknownOSError, path) } } @@ -366,7 +379,7 @@ private class LocalFileSystem: FileSystem { // Open the file. let fp = fopen(path.pathString, "rb") if fp == nil { - throw FileSystemError(errno: errno) + throw FileSystemError(errno: errno, path) } defer { fclose(fp) } @@ -377,11 +390,12 @@ private class LocalFileSystem: FileSystem { let n = fread(&tmpBuffer, 1, tmpBuffer.count, fp) if n < 0 { if errno == EINTR { continue } - throw FileSystemError.ioError + throw FileSystemError(.ioError(code: errno), path) } if n == 0 { - if ferror(fp) != 0 { - throw FileSystemError.ioError + let errno = ferror(fp) + if errno != 0 { + throw FileSystemError(.ioError(code: errno), path) } break } @@ -395,7 +409,7 @@ private class LocalFileSystem: FileSystem { // Open the file. let fp = fopen(path.pathString, "wb") if fp == nil { - throw FileSystemError(errno: errno) + throw FileSystemError(errno: errno, path) } defer { fclose(fp) } @@ -405,10 +419,10 @@ private class LocalFileSystem: FileSystem { let n = fwrite(&contents, 1, contents.count, fp) if n < 0 { if errno == EINTR { continue } - throw FileSystemError.ioError + throw FileSystemError(.ioError(code: errno), path) } if n != contents.count { - throw FileSystemError.ioError + throw FileSystemError(.unknownOSError, path) } break } @@ -454,7 +468,7 @@ private class LocalFileSystem: FileSystem { guard let traverse = FileManager.default.enumerator( at: URL(fileURLWithPath: path.pathString), includingPropertiesForKeys: nil) else { - throw FileSystemError.noEntry + throw FileSystemError(.noEntry, path) } if !options.contains(.recursive) { @@ -467,14 +481,16 @@ private class LocalFileSystem: FileSystem { } func copy(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws { - guard exists(sourcePath) else { throw FileSystemError.noEntry } - guard !exists(destinationPath) else { throw FileSystemError.alreadyExistsAtDestination } + guard exists(sourcePath) else { throw FileSystemError(.noEntry, sourcePath) } + guard !exists(destinationPath) + else { throw FileSystemError(.alreadyExistsAtDestination, destinationPath) } try FileManager.default.copyItem(at: sourcePath.asURL, to: destinationPath.asURL) } func move(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws { - guard exists(sourcePath) else { throw FileSystemError.noEntry } - guard !exists(destinationPath) else { throw FileSystemError.alreadyExistsAtDestination } + guard exists(sourcePath) else { throw FileSystemError(.noEntry, sourcePath) } + guard !exists(destinationPath) + else { throw FileSystemError(.alreadyExistsAtDestination, destinationPath) } try FileManager.default.moveItem(at: sourcePath.asURL, to: destinationPath.asURL) } @@ -596,7 +612,7 @@ public class InMemoryFileSystem: FileSystem { // If we didn't find a directory, this is an error. guard case .directory(let contents) = parent.contents else { - throw FileSystemError.notDirectory + throw FileSystemError(.notDirectory, path.parentDirectory) } // Return the directory entry. @@ -683,7 +699,7 @@ public class InMemoryFileSystem: FileSystem { } public func changeCurrentWorkingDirectory(to path: AbsolutePath) throws { - throw FileSystemError.unsupported + throw FileSystemError(.unsupported, path) } public var homeDirectory: AbsolutePath { @@ -698,10 +714,10 @@ public class InMemoryFileSystem: FileSystem { public func getDirectoryContents(_ path: AbsolutePath) throws -> [String] { return try lock.withLock { guard let node = try getNode(path) else { - throw FileSystemError.noEntry + throw FileSystemError(.noEntry, path) } guard case .directory(let contents) = node.contents else { - throw FileSystemError.notDirectory + throw FileSystemError(.notDirectory, path) } // FIXME: Perhaps we should change the protocol to allow lazy behavior. @@ -728,14 +744,14 @@ public class InMemoryFileSystem: FileSystem { return try _createDirectory(path, recursive: false) } else { // Otherwise, we failed. - throw FileSystemError.noEntry + throw FileSystemError(.noEntry, parentPath) } } // Check that the parent is a directory. guard case .directory(let contents) = parent.contents else { // The parent isn't a directory, this is an error. - throw FileSystemError.notDirectory + throw FileSystemError(.notDirectory, parentPath) } // Check if the node already exists. @@ -743,7 +759,7 @@ public class InMemoryFileSystem: FileSystem { // Verify it is a directory. guard case .directory = node.contents else { // The path itself isn't a directory, this is an error. - throw FileSystemError.notDirectory + throw FileSystemError(.notDirectory, path) } // We are done. @@ -764,16 +780,16 @@ public class InMemoryFileSystem: FileSystem { return try lock.withLock { // Create directory to destination parent. guard let destinationParent = try getNode(path.parentDirectory) else { - throw FileSystemError.noEntry + throw FileSystemError(.noEntry, path.parentDirectory) } // Check that the parent is a directory. guard case .directory(let contents) = destinationParent.contents else { - throw FileSystemError.notDirectory + throw FileSystemError(.notDirectory, path.parentDirectory) } guard contents.entries[path.basename] == nil else { - throw FileSystemError.alreadyExistsAtDestination + throw FileSystemError(.alreadyExistsAtDestination, path) } let destination = relative ? destination.relative(to: path.parentDirectory).pathString : destination.pathString @@ -786,13 +802,13 @@ public class InMemoryFileSystem: FileSystem { return try lock.withLock { // Get the node. guard let node = try getNode(path) else { - throw FileSystemError.noEntry + throw FileSystemError(.noEntry, path) } // Check that the node is a file. guard case .file(let contents) = node.contents else { // The path is a directory, this is an error. - throw FileSystemError.isDirectory + throw FileSystemError(.isDirectory, path) } // Return the file contents. @@ -805,18 +821,18 @@ public class InMemoryFileSystem: FileSystem { // It is an error if this is the root node. let parentPath = path.parentDirectory guard path != parentPath else { - throw FileSystemError.isDirectory + throw FileSystemError(.isDirectory, path) } // Get the parent node. guard let parent = try getNode(parentPath) else { - throw FileSystemError.noEntry + throw FileSystemError(.noEntry, parentPath) } // Check that the parent is a directory. guard case .directory(let contents) = parent.contents else { // The parent isn't a directory, this is an error. - throw FileSystemError.notDirectory + throw FileSystemError(.notDirectory, parentPath) } // Check if the node exists. @@ -824,7 +840,7 @@ public class InMemoryFileSystem: FileSystem { // Verify it is a file. guard case .file = node.contents else { // The path is a directory, this is an error. - throw FileSystemError.isDirectory + throw FileSystemError(.isDirectory, path) } } @@ -861,21 +877,21 @@ public class InMemoryFileSystem: FileSystem { private func _copy(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws { // Get the source node. guard let source = try getNode(sourcePath) else { - throw FileSystemError.noEntry + throw FileSystemError(.noEntry, sourcePath) } // Create directory to destination parent. guard let destinationParent = try getNode(destinationPath.parentDirectory) else { - throw FileSystemError.noEntry + throw FileSystemError(.noEntry, destinationPath.parentDirectory) } // Check that the parent is a directory. guard case .directory(let contents) = destinationParent.contents else { - throw FileSystemError.notDirectory + throw FileSystemError(.notDirectory, destinationPath.parentDirectory) } guard contents.entries[destinationPath.basename] == nil else { - throw FileSystemError.alreadyExistsAtDestination + throw FileSystemError(.alreadyExistsAtDestination, destinationPath) } contents.entries[destinationPath.basename] = source @@ -891,12 +907,12 @@ public class InMemoryFileSystem: FileSystem { return try lock.withLock { // Get the source parent node. guard let sourceParent = try getNode(sourcePath.parentDirectory) else { - throw FileSystemError.noEntry + throw FileSystemError(.noEntry, sourcePath.parentDirectory) } // Check that the parent is a directory. guard case .directory(let contents) = sourceParent.contents else { - throw FileSystemError.notDirectory + throw FileSystemError(.notDirectory, sourcePath.parentDirectory) } try _copy(from: sourcePath, to: destinationPath) @@ -990,7 +1006,7 @@ public class RerootedFileSystemView: FileSystem { } public func changeCurrentWorkingDirectory(to path: AbsolutePath) throws { - throw FileSystemError.unsupported + throw FileSystemError(.unsupported, path) } public var homeDirectory: AbsolutePath { diff --git a/Sources/TSCBasic/Lock.swift b/Sources/TSCBasic/Lock.swift index d974d266..bbfadea4 100644 --- a/Sources/TSCBasic/Lock.swift +++ b/Sources/TSCBasic/Lock.swift @@ -110,7 +110,7 @@ public final class FileLock { if fileDescriptor == nil { let fd = TSCLibc.open(lockFile.pathString, O_WRONLY | O_CREAT | O_CLOEXEC, 0o666) if fd == -1 { - throw FileSystemError(errno: errno) + throw FileSystemError(errno: errno, lockFile) } self.fileDescriptor = fd } diff --git a/Sources/TSCBasic/WritableByteStream.swift b/Sources/TSCBasic/WritableByteStream.swift index 67528c16..3321e4a8 100644 --- a/Sources/TSCBasic/WritableByteStream.swift +++ b/Sources/TSCBasic/WritableByteStream.swift @@ -672,16 +672,20 @@ public final class LocalFileOutputByteStream: FileOutputByteStream { /// The pointer to the file. let filePointer: UnsafeMutablePointer - /// True if there were any IO error during writing. - private var error: Bool = false + /// Set to an error value if there were any IO error during writing. + private var error: FileSystemError? /// Closes the file on deinit if true. private var closeOnDeinit: Bool + /// Path to the file this stream should operate on. + private let path: AbsolutePath? + /// Instantiate using the file pointer. public init(filePointer: UnsafeMutablePointer, closeOnDeinit: Bool = true, buffered: Bool = true) throws { self.filePointer = filePointer self.closeOnDeinit = closeOnDeinit + self.path = nil super.init(buffered: buffered) } @@ -700,8 +704,9 @@ public final class LocalFileOutputByteStream: FileOutputByteStream { /// - Throws: FileSystemError public init(_ path: AbsolutePath, closeOnDeinit: Bool = true, buffered: Bool = true) throws { guard let filePointer = fopen(path.pathString, "wb") else { - throw FileSystemError(errno: errno) + throw FileSystemError(errno: errno, path) } + self.path = path self.filePointer = filePointer self.closeOnDeinit = closeOnDeinit super.init(buffered: buffered) @@ -713,8 +718,12 @@ public final class LocalFileOutputByteStream: FileOutputByteStream { } } - func errorDetected() { - error = true + func errorDetected(code: Int32?) { + if let code = code { + error = .init(.ioError(code: code), path) + } else { + error = .init(.unknownOSError, path) + } } override final func writeImpl(_ bytes: C) where C.Iterator.Element == UInt8 { @@ -724,9 +733,9 @@ public final class LocalFileOutputByteStream: FileOutputByteStream { let n = fwrite(&contents, 1, contents.count, filePointer) if n < 0 { if errno == EINTR { continue } - errorDetected() + errorDetected(code: errno) } else if n != contents.count { - errorDetected() + errorDetected(code: nil) } break } @@ -738,9 +747,9 @@ public final class LocalFileOutputByteStream: FileOutputByteStream { let n = fwrite(bytesPtr.baseAddress, 1, bytesPtr.count, filePointer) if n < 0 { if errno == EINTR { continue } - errorDetected() + errorDetected(code: errno) } else if n != bytesPtr.count { - errorDetected() + errorDetected(code: nil) } break } @@ -758,8 +767,8 @@ public final class LocalFileOutputByteStream: FileOutputByteStream { closeOnDeinit = false } // Throw if errors were found during writing. - if error { - throw FileSystemError.ioError + if let error = error { + throw error } } } diff --git a/Sources/TSCUtility/Archiver.swift b/Sources/TSCUtility/Archiver.swift index 5f5c034d..5d0f7db0 100644 --- a/Sources/TSCUtility/Archiver.swift +++ b/Sources/TSCUtility/Archiver.swift @@ -51,12 +51,12 @@ public struct ZipArchiver: Archiver { completion: @escaping (Result) -> Void ) { guard fileSystem.exists(archivePath) else { - completion(.failure(FileSystemError.noEntry)) + completion(.failure(FileSystemError(.noEntry, archivePath))) return } guard fileSystem.isDirectory(destinationPath) else { - completion(.failure(FileSystemError.notDirectory)) + completion(.failure(FileSystemError(.notDirectory, destinationPath))) return } diff --git a/Tests/TSCBasicTests/FileSystemTests.swift b/Tests/TSCBasicTests/FileSystemTests.swift index 5c724354..0495653f 100644 --- a/Tests/TSCBasicTests/FileSystemTests.swift +++ b/Tests/TSCBasicTests/FileSystemTests.swift @@ -245,42 +245,45 @@ class FileSystemTests: XCTestCase { XCTAssertEqual(try! fs.readFileContents(filePath), "Hello, new world!") // Check read/write of a directory. - XCTAssertThrows(FileSystemError.ioError) { + XCTAssertThrows(FileSystemError(.ioError(code: TSCLibc.EPERM), filePath.parentDirectory)) { _ = try fs.readFileContents(filePath.parentDirectory) } - XCTAssertThrows(FileSystemError.isDirectory) { + XCTAssertThrows(FileSystemError(.isDirectory, filePath.parentDirectory)) { try fs.writeFileContents(filePath.parentDirectory, bytes: []) } XCTAssertEqual(try! fs.readFileContents(filePath), "Hello, new world!") // Check read/write against root. - XCTAssertThrows(FileSystemError.ioError) { - #if os(Android) - _ = try fs.readFileContents(AbsolutePath("/system/")) - #else - _ = try fs.readFileContents(AbsolutePath("/")) - #endif + #if os(Android) + let root = AbsolutePath("/system/") + #else + let root = AbsolutePath("/") + #endif + XCTAssertThrows(FileSystemError(.ioError(code: TSCLibc.EPERM), root)) { + _ = try fs.readFileContents(root) + } - XCTAssertThrows(FileSystemError.isDirectory) { - try fs.writeFileContents(AbsolutePath("/"), bytes: []) + XCTAssertThrows(FileSystemError(.isDirectory, root)) { + try fs.writeFileContents(root, bytes: []) } XCTAssert(fs.exists(filePath)) // Check read/write into a non-directory. - XCTAssertThrows(FileSystemError.notDirectory) { - _ = try fs.readFileContents(filePath.appending(component: "not-possible")) + let notDirectoryPath = filePath.appending(component: "not-possible") + XCTAssertThrows(FileSystemError(.notDirectory, notDirectoryPath)) { + _ = try fs.readFileContents(notDirectoryPath) } - XCTAssertThrows(FileSystemError.notDirectory) { + XCTAssertThrows(FileSystemError(.notDirectory, notDirectoryPath)) { try fs.writeFileContents(filePath.appending(component: "not-possible"), bytes: []) } XCTAssert(fs.exists(filePath)) // Check read/write into a missing directory. let missingDir = tmpDirPath.appending(components: "does", "not", "exist") - XCTAssertThrows(FileSystemError.noEntry) { + XCTAssertThrows(FileSystemError(.noEntry, missingDir)) { _ = try fs.readFileContents(missingDir) } - XCTAssertThrows(FileSystemError.noEntry) { + XCTAssertThrows(FileSystemError(.noEntry, missingDir)) { try fs.writeFileContents(missingDir, bytes: []) } XCTAssert(!fs.exists(missingDir)) @@ -302,10 +305,10 @@ class FileSystemTests: XCTestCase { // Copy with no source - XCTAssertThrows(FileSystemError.noEntry) { + XCTAssertThrows(FileSystemError(.noEntry, source)) { try fs.copy(from: source, to: destination) } - XCTAssertThrows(FileSystemError.noEntry) { + XCTAssertThrows(FileSystemError(.noEntry, source)) { try fs.move(from: source, to: destination) } @@ -314,10 +317,10 @@ class FileSystemTests: XCTestCase { try fs.writeFileContents(source, bytes: "source1") try fs.writeFileContents(destination, bytes: "destination") - XCTAssertThrows(FileSystemError.alreadyExistsAtDestination) { + XCTAssertThrows(FileSystemError(.alreadyExistsAtDestination, destination)) { try fs.copy(from: source, to: destination) } - XCTAssertThrows(FileSystemError.alreadyExistsAtDestination) { + XCTAssertThrows(FileSystemError(.alreadyExistsAtDestination, destination)) { try fs.move(from: source, to: destination) } @@ -373,22 +376,23 @@ class FileSystemTests: XCTestCase { func testInMemoryBasics() throws { let fs = InMemoryFileSystem() + let doesNotExist = AbsolutePath("/does-not-exist") // exists() - XCTAssert(!fs.exists(AbsolutePath("/does-not-exist"))) + XCTAssert(!fs.exists(doesNotExist)) // isDirectory() - XCTAssert(!fs.isDirectory(AbsolutePath("/does-not-exist"))) + XCTAssert(!fs.isDirectory(doesNotExist)) // isFile() - XCTAssert(!fs.isFile(AbsolutePath("/does-not-exist"))) + XCTAssert(!fs.isFile(doesNotExist)) // isSymlink() - XCTAssert(!fs.isSymlink(AbsolutePath("/does-not-exist"))) + XCTAssert(!fs.isSymlink(doesNotExist)) // getDirectoryContents() - XCTAssertThrows(FileSystemError.noEntry) { - _ = try fs.getDirectoryContents(AbsolutePath("/does-not-exist")) + XCTAssertThrows(FileSystemError(.noEntry, doesNotExist)) { + _ = try fs.getDirectoryContents(doesNotExist) } // createDirectory() @@ -422,9 +426,10 @@ class FileSystemTests: XCTestCase { XCTAssert(fs.isDirectory(subsubdir)) // Check non-recursive failing subdir case. - let newsubdir = AbsolutePath("/very-new-dir/subdir") + let veryNewDir = AbsolutePath("/very-new-dir") + let newsubdir = veryNewDir.appending(component: "subdir") XCTAssert(!fs.isDirectory(newsubdir)) - XCTAssertThrows(FileSystemError.noEntry) { + XCTAssertThrows(FileSystemError(.noEntry, veryNewDir)) { try fs.createDirectory(newsubdir, recursive: false) } XCTAssert(!fs.isDirectory(newsubdir)) @@ -433,10 +438,10 @@ class FileSystemTests: XCTestCase { let filePath = AbsolutePath("/mach_kernel") try! fs.writeFileContents(filePath, bytes: [0xCD, 0x0D]) XCTAssert(fs.exists(filePath) && !fs.isDirectory(filePath)) - XCTAssertThrows(FileSystemError.notDirectory) { + XCTAssertThrows(FileSystemError(.notDirectory, filePath)) { try fs.createDirectory(filePath, recursive: true) } - XCTAssertThrows(FileSystemError.notDirectory) { + XCTAssertThrows(FileSystemError(.notDirectory, filePath)) { try fs.createDirectory(filePath.appending(component: "not-possible"), recursive: true) } XCTAssert(fs.exists(filePath) && !fs.isDirectory(filePath)) @@ -493,42 +498,45 @@ class FileSystemTests: XCTestCase { XCTAssertEqual(try! fs.readFileContents(filePath), "Hello, new world!") // Check read/write of a directory. - XCTAssertThrows(FileSystemError.isDirectory) { + XCTAssertThrows(FileSystemError(.isDirectory, filePath.parentDirectory)) { _ = try fs.readFileContents(filePath.parentDirectory) } - XCTAssertThrows(FileSystemError.isDirectory) { + XCTAssertThrows(FileSystemError(.isDirectory, filePath.parentDirectory)) { try fs.writeFileContents(filePath.parentDirectory, bytes: []) } XCTAssertEqual(try! fs.readFileContents(filePath), "Hello, new world!") // Check read/write against root. - XCTAssertThrows(FileSystemError.isDirectory) { - _ = try fs.readFileContents(AbsolutePath("/")) + let root = AbsolutePath("/") + XCTAssertThrows(FileSystemError(.isDirectory, root)) { + _ = try fs.readFileContents(root) } - XCTAssertThrows(FileSystemError.isDirectory) { - try fs.writeFileContents(AbsolutePath("/"), bytes: []) + XCTAssertThrows(FileSystemError(.isDirectory, root)) { + try fs.writeFileContents(root, bytes: []) } XCTAssert(fs.exists(filePath)) XCTAssertTrue(fs.isFile(filePath)) // Check read/write into a non-directory. - XCTAssertThrows(FileSystemError.notDirectory) { - _ = try fs.readFileContents(filePath.appending(component: "not-possible")) + let notDirectory = filePath.appending(component: "not-possible") + XCTAssertThrows(FileSystemError(.notDirectory, filePath)) { + _ = try fs.readFileContents(notDirectory) } - XCTAssertThrows(FileSystemError.notDirectory) { - try fs.writeFileContents(filePath.appending(component: "not-possible"), bytes: []) + XCTAssertThrows(FileSystemError(.notDirectory, filePath)) { + try fs.writeFileContents(notDirectory, bytes: []) } XCTAssert(fs.exists(filePath)) // Check read/write into a missing directory. - let missingDir = AbsolutePath("/does/not/exist") - XCTAssertThrows(FileSystemError.noEntry) { - _ = try fs.readFileContents(missingDir) + let missingParent = AbsolutePath("/does/not") + let missingFile = missingParent.appending(component: "exist") + XCTAssertThrows(FileSystemError(.noEntry, missingFile)) { + _ = try fs.readFileContents(missingFile) } - XCTAssertThrows(FileSystemError.noEntry) { - try fs.writeFileContents(missingDir, bytes: []) + XCTAssertThrows(FileSystemError(.noEntry, missingParent)) { + try fs.writeFileContents(missingFile, bytes: []) } - XCTAssert(!fs.exists(missingDir)) + XCTAssert(!fs.exists(missingFile)) } func testInMemoryFsCopy() throws { @@ -560,10 +568,10 @@ class FileSystemTests: XCTestCase { // Copy with no source - XCTAssertThrows(FileSystemError.noEntry) { + XCTAssertThrows(FileSystemError(.noEntry, source)) { try fs.copy(from: source, to: destination) } - XCTAssertThrows(FileSystemError.noEntry) { + XCTAssertThrows(FileSystemError(.noEntry, source)) { try fs.move(from: source, to: destination) } @@ -572,10 +580,10 @@ class FileSystemTests: XCTestCase { try fs.writeFileContents(source, bytes: "source1") try fs.writeFileContents(destination, bytes: "destination") - XCTAssertThrows(FileSystemError.alreadyExistsAtDestination) { + XCTAssertThrows(FileSystemError(.alreadyExistsAtDestination, destination)) { try fs.copy(from: source, to: destination) } - XCTAssertThrows(FileSystemError.alreadyExistsAtDestination) { + XCTAssertThrows(FileSystemError(.alreadyExistsAtDestination, destination)) { try fs.move(from: source, to: destination) } @@ -703,13 +711,13 @@ class FileSystemTests: XCTestCase { // Set foo to unwritable. try fs.chmod(.userUnWritable, path: foo) - XCTAssertThrows(FileSystemError.invalidAccess) { + XCTAssertThrows(FileSystemError(.invalidAccess, foo)) { try fs.writeFileContents(foo, bytes: "test") } // Set the directory as unwritable. try fs.chmod(.userUnWritable, path: dir, options: [.recursive, .onlyFiles]) - XCTAssertThrows(FileSystemError.invalidAccess) { + XCTAssertThrows(FileSystemError(.invalidAccess, bar)) { try fs.writeFileContents(bar, bytes: "test") } @@ -721,8 +729,9 @@ class FileSystemTests: XCTestCase { // But not anymore. try fs.chmod(.userUnWritable, path: dir, options: [.recursive]) - XCTAssertThrows(FileSystemError.invalidAccess) { - try fs.writeFileContents(dir.appending(component: "new2"), bytes: "") + let newFile = dir.appending(component: "new2") + XCTAssertThrows(FileSystemError(.invalidAccess, newFile)) { + try fs.writeFileContents(newFile, bytes: "") } try? fs.removeFileTree(bar) diff --git a/Tests/TSCUtilityTests/ArchiverTests.swift b/Tests/TSCUtilityTests/ArchiverTests.swift index bf98e396..d93d28e2 100644 --- a/Tests/TSCUtilityTests/ArchiverTests.swift +++ b/Tests/TSCUtilityTests/ArchiverTests.swift @@ -40,8 +40,9 @@ class ArchiverTests: XCTestCase { let fileSystem = InMemoryFileSystem() let archiver = ZipArchiver(fileSystem: fileSystem) - archiver.extract(from: AbsolutePath("/archive.zip"), to: AbsolutePath("/"), completion: { result in - XCTAssertResultFailure(result, equals: FileSystemError.noEntry) + let archive = AbsolutePath("/archive.zip") + archiver.extract(from: archive, to: AbsolutePath("/"), completion: { result in + XCTAssertResultFailure(result, equals: FileSystemError(.noEntry, archive)) expectation.fulfill() }) @@ -53,8 +54,9 @@ class ArchiverTests: XCTestCase { let fileSystem = InMemoryFileSystem(emptyFiles: "/archive.zip") let archiver = ZipArchiver(fileSystem: fileSystem) - archiver.extract(from: AbsolutePath("/archive.zip"), to: AbsolutePath("/destination"), completion: { result in - XCTAssertResultFailure(result, equals: FileSystemError.notDirectory) + let destination = AbsolutePath("/destination") + archiver.extract(from: AbsolutePath("/archive.zip"), to: destination, completion: { result in + XCTAssertResultFailure(result, equals: FileSystemError(.notDirectory, destination)) expectation.fulfill() }) @@ -66,8 +68,9 @@ class ArchiverTests: XCTestCase { let fileSystem = InMemoryFileSystem(emptyFiles: "/archive.zip", "/destination") let archiver = ZipArchiver(fileSystem: fileSystem) - archiver.extract(from: AbsolutePath("/archive.zip"), to: AbsolutePath("/destination"), completion: { result in - XCTAssertResultFailure(result, equals: FileSystemError.notDirectory) + let destination = AbsolutePath("/destination") + archiver.extract(from: AbsolutePath("/archive.zip"), to: destination, completion: { result in + XCTAssertResultFailure(result, equals: FileSystemError(.notDirectory, destination)) expectation.fulfill() })