mirror of
https://github.com/samsonjs/FileOtter.git
synced 2026-04-27 14:57:41 +00:00
Flesh out Dir a lot more
This commit is contained in:
parent
c74842ef76
commit
f85da3e7b5
5 changed files with 695 additions and 72 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
build
|
||||||
|
xcuserdata
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
7B1B71E12E52784D008EDC0E /* Glob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1B71E02E52784D008EDC0E /* Glob.swift */; };
|
||||||
7B5064B92BD9F236009CEFF9 /* FileOtter.docc in Sources */ = {isa = PBXBuildFile; fileRef = 7B5064B82BD9F236009CEFF9 /* FileOtter.docc */; };
|
7B5064B92BD9F236009CEFF9 /* FileOtter.docc in Sources */ = {isa = PBXBuildFile; fileRef = 7B5064B82BD9F236009CEFF9 /* FileOtter.docc */; };
|
||||||
7B5064BF2BD9F236009CEFF9 /* FileOtter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B5064B42BD9F236009CEFF9 /* FileOtter.framework */; };
|
7B5064BF2BD9F236009CEFF9 /* FileOtter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B5064B42BD9F236009CEFF9 /* FileOtter.framework */; };
|
||||||
7B5064C42BD9F236009CEFF9 /* FileOtterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5064C32BD9F236009CEFF9 /* FileOtterTests.swift */; };
|
7B5064C42BD9F236009CEFF9 /* FileOtterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5064C32BD9F236009CEFF9 /* FileOtterTests.swift */; };
|
||||||
|
|
@ -27,6 +28,7 @@
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
7B1B71E02E52784D008EDC0E /* Glob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Glob.swift; sourceTree = "<group>"; };
|
||||||
7B5064B42BD9F236009CEFF9 /* FileOtter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileOtter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
7B5064B42BD9F236009CEFF9 /* FileOtter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FileOtter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
7B5064B72BD9F236009CEFF9 /* FileOtter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileOtter.h; sourceTree = "<group>"; };
|
7B5064B72BD9F236009CEFF9 /* FileOtter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileOtter.h; sourceTree = "<group>"; };
|
||||||
7B5064B82BD9F236009CEFF9 /* FileOtter.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = FileOtter.docc; sourceTree = "<group>"; };
|
7B5064B82BD9F236009CEFF9 /* FileOtter.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = FileOtter.docc; sourceTree = "<group>"; };
|
||||||
|
|
@ -82,6 +84,7 @@
|
||||||
7B5064B82BD9F236009CEFF9 /* FileOtter.docc */,
|
7B5064B82BD9F236009CEFF9 /* FileOtter.docc */,
|
||||||
7B5064D02BD9F322009CEFF9 /* File.swift */,
|
7B5064D02BD9F322009CEFF9 /* File.swift */,
|
||||||
7B5064D22BD9F339009CEFF9 /* Dir.swift */,
|
7B5064D22BD9F339009CEFF9 /* Dir.swift */,
|
||||||
|
7B1B71E02E52784D008EDC0E /* Glob.swift */,
|
||||||
);
|
);
|
||||||
path = FileOtter;
|
path = FileOtter;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -205,6 +208,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7B5064B92BD9F236009CEFF9 /* FileOtter.docc in Sources */,
|
7B5064B92BD9F236009CEFF9 /* FileOtter.docc in Sources */,
|
||||||
|
7B1B71E12E52784D008EDC0E /* Glob.swift in Sources */,
|
||||||
7B5064D12BD9F322009CEFF9 /* File.swift in Sources */,
|
7B5064D12BD9F322009CEFF9 /* File.swift in Sources */,
|
||||||
7B5064D32BD9F339009CEFF9 /* Dir.swift in Sources */,
|
7B5064D32BD9F339009CEFF9 /* Dir.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Dir: Equatable, Hashable, RandomAccessCollection {
|
public struct Dir: Equatable, Hashable, RandomAccessCollection, CustomStringConvertible, CustomDebugStringConvertible {
|
||||||
public let startIndex: Int
|
public let startIndex: Int
|
||||||
|
|
||||||
public let endIndex: Int
|
public let endIndex: Int
|
||||||
|
|
@ -15,7 +15,7 @@ public struct Dir: Equatable, Hashable, RandomAccessCollection {
|
||||||
public let url: URL
|
public let url: URL
|
||||||
|
|
||||||
public init(url: URL) throws {
|
public init(url: URL) throws {
|
||||||
self.init(url: url, children: try Dir.children(url))
|
try self.init(url: url, children: Dir.children(url))
|
||||||
}
|
}
|
||||||
|
|
||||||
private let children: [URL]
|
private let children: [URL]
|
||||||
|
|
@ -26,96 +26,124 @@ public struct Dir: Equatable, Hashable, RandomAccessCollection {
|
||||||
startIndex = children.startIndex
|
startIndex = children.startIndex
|
||||||
endIndex = children.endIndex
|
endIndex = children.endIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
url.path
|
||||||
|
}
|
||||||
|
|
||||||
|
public var debugDescription: String {
|
||||||
|
"<Dir:\(url.path)>"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Well-known Directories
|
// MARK: - Well-known Directories
|
||||||
extension Dir {
|
|
||||||
public static var caches: URL {
|
public extension Dir {
|
||||||
|
static var caches: URL {
|
||||||
URL.cachesDirectory
|
URL.cachesDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var current: URL {
|
static var documents: URL {
|
||||||
URL.currentDirectory()
|
|
||||||
}
|
|
||||||
|
|
||||||
public static var documents: URL {
|
|
||||||
URL.documentsDirectory
|
URL.documentsDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var home: URL {
|
static var home: URL {
|
||||||
URL.homeDirectory
|
URL.homeDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var library: URL {
|
static var library: URL {
|
||||||
URL.libraryDirectory
|
URL.libraryDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var pwd: URL {
|
static var pwd: URL {
|
||||||
.currentDirectory()
|
URL.currentDirectory()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var getwd: URL {
|
static var tmp: URL {
|
||||||
.currentDirectory()
|
URL.temporaryDirectory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Mutations
|
// MARK: - Mutations
|
||||||
extension Dir {
|
|
||||||
@discardableResult
|
public extension Dir {
|
||||||
public static func chdir(_ url: URL) -> Bool {
|
static func chdir(_ url: URL) throws {
|
||||||
FileManager.default.changeCurrentDirectoryPath(url.path)
|
guard FileManager.default.changeCurrentDirectoryPath(url.path) else {
|
||||||
|
throw CocoaError(.fileNoSuchFile, userInfo: [NSFilePathErrorKey: url.path])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public static func chdir<T>(_ url: URL, block: (URL) -> T) -> T {
|
static func chdir<T>(_ url: URL, block: (URL) throws -> T) rethrows -> T {
|
||||||
let previousDir = pwd
|
let previousDir = pwd
|
||||||
FileManager.default.changeCurrentDirectoryPath(url.path)
|
FileManager.default.changeCurrentDirectoryPath(url.path)
|
||||||
defer {
|
defer {
|
||||||
FileManager.default.changeCurrentDirectoryPath(previousDir.path)
|
FileManager.default.changeCurrentDirectoryPath(previousDir.path)
|
||||||
}
|
}
|
||||||
return block(url)
|
return try block(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func unlink(_ url: URL) throws {
|
||||||
|
try FileManager.default.removeItem(at: url)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func rmdir(_ url: URL) throws {
|
||||||
|
try unlink(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func mkdir(_ url: URL, permissions: Int = 0o755) throws {
|
||||||
|
let attributes: [FileAttributeKey: Any] = [
|
||||||
|
.posixPermissions: permissions,
|
||||||
|
]
|
||||||
|
try FileManager.default.createDirectory(
|
||||||
|
at: url,
|
||||||
|
withIntermediateDirectories: false,
|
||||||
|
attributes: attributes
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public static func unlink(_ url: URL) -> Bool {
|
static func mktmpdir(prefix: String = "d", suffix: String = "") throws -> URL {
|
||||||
do {
|
let tmpBase = URL.temporaryDirectory
|
||||||
try FileManager.default.removeItem(at: url)
|
let dirName = suffix.isEmpty ? "\(prefix)-\(UUID().uuidString)" : "\(prefix)-\(UUID().uuidString)-\(suffix)"
|
||||||
return true
|
let tmpDir = tmpBase.appendingPathComponent(dirName)
|
||||||
} catch {
|
try FileManager.default.createDirectory(
|
||||||
return false
|
at: tmpDir,
|
||||||
|
withIntermediateDirectories: true,
|
||||||
|
attributes: [.posixPermissions: 0o700]
|
||||||
|
)
|
||||||
|
return tmpDir
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
static func mktmpdir<T>(
|
||||||
|
prefix: String = "d",
|
||||||
|
suffix: String = "",
|
||||||
|
_ block: (URL) throws -> T
|
||||||
|
) throws -> T {
|
||||||
|
let tmpDir = try mktmpdir(prefix: prefix, suffix: suffix)
|
||||||
|
defer {
|
||||||
|
try? FileManager.default.removeItem(at: tmpDir)
|
||||||
}
|
}
|
||||||
}
|
return try block(tmpDir)
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
public static func rmdir(_ url: URL) -> Bool {
|
|
||||||
unlink(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
public static func delete(_ url: URL) -> Bool {
|
|
||||||
unlink(url)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Reading Contents
|
// MARK: - Reading Contents
|
||||||
extension Dir {
|
|
||||||
public static func children(_ url: URL) throws -> [URL] {
|
public extension Dir {
|
||||||
|
static func children(_ url: URL) throws -> [URL] {
|
||||||
try FileManager.default
|
try FileManager.default
|
||||||
.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
|
.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func entries(_ url: URL) throws -> [String] {
|
static func exists(_ url: URL) throws -> Bool {
|
||||||
#warning("TODO: implement this ... maybe, it's dumb")
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func exists(_ url: URL) throws -> Bool {
|
|
||||||
var isDirectory: ObjCBool = false
|
var isDirectory: ObjCBool = false
|
||||||
let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory)
|
let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory)
|
||||||
return exists && isDirectory.boolValue
|
return exists && isDirectory.boolValue
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func isEmpty(_ url: URL) throws -> Bool {
|
static func isEmpty(_ url: URL) throws -> Bool {
|
||||||
try FileManager.default
|
try FileManager.default
|
||||||
.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
|
.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
|
||||||
.isEmpty
|
.isEmpty
|
||||||
|
|
@ -123,28 +151,35 @@ extension Dir {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Globbing
|
// MARK: - Globbing
|
||||||
extension Dir {
|
|
||||||
public static func glob(base: URL? = nil, _ patterns: String...) -> [URL] {
|
public extension Dir {
|
||||||
|
static func glob(base: URL? = nil, _ patterns: String...) -> [URL] {
|
||||||
_glob(base: base, patterns: patterns)
|
_glob(base: base, patterns: patterns)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static subscript(base: URL? = nil, _ patterns: String...) -> [URL] {
|
static subscript(base: URL?, _ patterns: String...) -> [URL] {
|
||||||
_glob(base: base, patterns: patterns)
|
_glob(base: base, patterns: patterns)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static subscript(_ patterns: String...) -> [URL] {
|
||||||
|
_glob(base: nil, patterns: Array(patterns))
|
||||||
|
}
|
||||||
|
|
||||||
private static func _glob(base: URL?, patterns: [String]) -> [URL] {
|
private static func _glob(base: URL?, patterns: [String]) -> [URL] {
|
||||||
#warning("TODO: implement me")
|
patterns.flatMap { pattern in
|
||||||
return []
|
globstar(pattern, base: base)
|
||||||
|
}.map { URL(fileURLWithPath: $0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - RandomAccessCollection
|
// MARK: - RandomAccessCollection
|
||||||
extension Dir {
|
|
||||||
public func makeIterator() -> any IteratorProtocol<URL> {
|
public extension Dir {
|
||||||
|
func makeIterator() -> any IteratorProtocol<URL> {
|
||||||
children.makeIterator()
|
children.makeIterator()
|
||||||
}
|
}
|
||||||
|
|
||||||
public subscript(position: Int) -> URL {
|
subscript(position: Int) -> URL {
|
||||||
children[position]
|
children[position]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
121
FileOtter/Glob.swift
Normal file
121
FileOtter/Glob.swift
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
//
|
||||||
|
// Glob.swift
|
||||||
|
// FileOtter
|
||||||
|
//
|
||||||
|
// Created by Sami Samhuri on 2025-08-17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
#if os(Linux)
|
||||||
|
import Glibc
|
||||||
|
#else
|
||||||
|
import Darwin
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Expand a glob pattern supporting ** (recursive), *, ?, and [].
|
||||||
|
/// Examples:
|
||||||
|
/// "src/**/*.swift"
|
||||||
|
/// "/var/log/**/app*.log"
|
||||||
|
func globstar(_ pattern: String, base: URL? = nil) -> [URL] {
|
||||||
|
// Normalize and split into path components
|
||||||
|
let comps = pattern.split(separator: "/", omittingEmptySubsequences: true).map(String.init)
|
||||||
|
|
||||||
|
var results: [String] = []
|
||||||
|
var seenDirs = Set<String>() // canonical paths to avoid cycles if symlinks appear
|
||||||
|
|
||||||
|
// Cache frequently used objects
|
||||||
|
let fm = FileManager.default
|
||||||
|
let globMetaChars = CharacterSet(charactersIn: "*?[")
|
||||||
|
|
||||||
|
func isDir(_ path: String) -> Bool {
|
||||||
|
var isDirectory: ObjCBool = false
|
||||||
|
if fm.fileExists(atPath: path, isDirectory: &isDirectory) {
|
||||||
|
return isDirectory.boolValue
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func listDir(_ path: String) -> [String] {
|
||||||
|
(try? fm.contentsOfDirectory(atPath: path)) ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
// fnmatch against a single path segment (no '/')
|
||||||
|
@inline(__always)
|
||||||
|
func matchSegment(_ name: String, pat: String) -> Bool {
|
||||||
|
name.withCString { nPtr in
|
||||||
|
pat.withCString { pPtr in
|
||||||
|
// FNM_PERIOD -> leading '.' must be matched explicitly (shell-like)
|
||||||
|
// FNM_NOESCAPE -> backslashes are treated literally
|
||||||
|
fnmatch(pPtr, nPtr, FNM_PERIOD | FNM_NOESCAPE) == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func real(_ path: String) -> String {
|
||||||
|
URL(fileURLWithPath: path).standardizedFileURL.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func walk(_ base: String, _ idx: Int) {
|
||||||
|
if idx == comps.count {
|
||||||
|
// Only return existing paths
|
||||||
|
if FileManager.default.fileExists(atPath: base) {
|
||||||
|
results.append(real(base))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let part = comps[idx]
|
||||||
|
|
||||||
|
if part == "**" {
|
||||||
|
// Option 1: ** matches zero segments
|
||||||
|
walk(base, idx + 1)
|
||||||
|
|
||||||
|
// Option 2: ** matches one or more directory segments
|
||||||
|
// Recurse into subdirs breadth-first
|
||||||
|
let dirPath = base.isEmpty ? "/" : base
|
||||||
|
let key = real(dirPath)
|
||||||
|
if seenDirs.contains(key) { return }
|
||||||
|
seenDirs.insert(key)
|
||||||
|
|
||||||
|
if isDir(dirPath) {
|
||||||
|
let dirPathNS = dirPath as NSString // Cache the NSString conversion
|
||||||
|
for entry in listDir(dirPath) {
|
||||||
|
let child = dirPathNS.appendingPathComponent(entry)
|
||||||
|
if isDir(child) {
|
||||||
|
// Keep idx the same to allow ** to consume multiple levels
|
||||||
|
walk(child, idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-** component. If it has no glob metachar, fast-path.
|
||||||
|
let hasMeta = part.rangeOfCharacter(from: globMetaChars) != nil
|
||||||
|
if !hasMeta {
|
||||||
|
let next = (base as NSString).appendingPathComponent(part)
|
||||||
|
walk(next, idx + 1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Segment glob (*, ?, []) matches names in this directory level only
|
||||||
|
let dirPath = base.isEmpty ? "/" : base
|
||||||
|
if !isDir(dirPath) { return }
|
||||||
|
let dirPathNS = dirPath as NSString // Cache the NSString conversion
|
||||||
|
for entry in listDir(dirPath) {
|
||||||
|
if matchSegment(entry, pat: part) {
|
||||||
|
let next = dirPathNS.appendingPathComponent(entry)
|
||||||
|
walk(next, idx + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kick off
|
||||||
|
let isAbs = pattern.hasPrefix("/")
|
||||||
|
let cwd = (base ?? URL.currentDirectory()).path
|
||||||
|
walk(isAbs ? "/" : cwd, 0)
|
||||||
|
|
||||||
|
// De-dup and sort for stability
|
||||||
|
return Array(Set(results)).sorted()
|
||||||
|
}
|
||||||
|
|
@ -5,32 +5,493 @@
|
||||||
// Created by Sami Samhuri on 2024-04-24.
|
// Created by Sami Samhuri on 2024-04-24.
|
||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
|
||||||
@testable import FileOtter
|
@testable import FileOtter
|
||||||
|
import XCTest
|
||||||
|
|
||||||
class FileOtterTests: XCTestCase {
|
final class DirTests: XCTestCase {
|
||||||
|
var tempDir: URL!
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
tempDir = URL.temporaryDirectory
|
||||||
|
.appendingPathComponent("FileOtterTests-\(UUID().uuidString)")
|
||||||
|
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
override func tearDownWithError() throws {
|
||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
if FileManager.default.fileExists(atPath: tempDir.path) {
|
||||||
}
|
try FileManager.default.removeItem(at: tempDir)
|
||||||
|
|
||||||
func testExample() throws {
|
|
||||||
// This is an example of a functional test case.
|
|
||||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
|
||||||
// Any test you write for XCTest can be annotated as throws and async.
|
|
||||||
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
|
|
||||||
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPerformanceExample() throws {
|
|
||||||
// This is an example of a performance test case.
|
|
||||||
self.measure {
|
|
||||||
// Put the code you want to measure the time of here.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Well-known Directories Tests
|
||||||
|
|
||||||
|
func testCachesDirectory() {
|
||||||
|
let caches = Dir.caches
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: caches.path))
|
||||||
|
XCTAssertTrue(caches.path.contains("Caches"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPwd() {
|
||||||
|
let pwd = Dir.pwd
|
||||||
|
|
||||||
|
XCTAssertEqual(pwd, URL.currentDirectory())
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: pwd.path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDocumentsDirectory() {
|
||||||
|
let documents = Dir.documents
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: documents.path))
|
||||||
|
XCTAssertTrue(documents.path.contains("Documents"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHomeDirectory() {
|
||||||
|
let home = Dir.home
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: home.path))
|
||||||
|
XCTAssertEqual(home.path, URL.homeDirectory.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLibraryDirectory() {
|
||||||
|
let library = Dir.library
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: library.path))
|
||||||
|
XCTAssertTrue(library.path.contains("Library"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - chdir Tests
|
||||||
|
|
||||||
|
func testChdirChangesDirectory() throws {
|
||||||
|
let originalDir = Dir.pwd
|
||||||
|
|
||||||
|
XCTAssertNoThrow(try Dir.chdir(tempDir))
|
||||||
|
XCTAssertEqual(Dir.pwd.resolvingSymlinksInPath().path, tempDir.resolvingSymlinksInPath().path)
|
||||||
|
|
||||||
|
try Dir.chdir(originalDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testChdirWithBlock() {
|
||||||
|
let originalDir = Dir.pwd
|
||||||
|
let testFile = tempDir.appendingPathComponent("test.txt")
|
||||||
|
|
||||||
|
let result = Dir.chdir(tempDir) { url in
|
||||||
|
XCTAssertEqual(Dir.pwd.resolvingSymlinksInPath().path, tempDir.resolvingSymlinksInPath().path)
|
||||||
|
XCTAssertEqual(url.resolvingSymlinksInPath().path, tempDir.resolvingSymlinksInPath().path)
|
||||||
|
|
||||||
|
try? "Testing chdir block".write(to: testFile, atomically: true, encoding: .utf8)
|
||||||
|
return "success"
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(result, "success")
|
||||||
|
XCTAssertEqual(Dir.pwd.resolvingSymlinksInPath().path, originalDir.resolvingSymlinksInPath().path)
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: testFile.path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testChdirWithBlockRestoresDirectoryOnError() {
|
||||||
|
let originalDir = Dir.pwd
|
||||||
|
|
||||||
|
do {
|
||||||
|
try Dir.chdir(tempDir) { url in
|
||||||
|
// Verify we're in the temp directory
|
||||||
|
let currentPath = Dir.pwd.resolvingSymlinksInPath().path
|
||||||
|
let expectedPath = tempDir.resolvingSymlinksInPath().path
|
||||||
|
XCTAssertEqual(currentPath, expectedPath)
|
||||||
|
|
||||||
|
// Also verify the passed URL matches
|
||||||
|
XCTAssertEqual(url.resolvingSymlinksInPath().path, expectedPath)
|
||||||
|
|
||||||
|
throw NSError(domain: "TestError", code: 1)
|
||||||
|
}
|
||||||
|
XCTFail("Should have thrown an error")
|
||||||
|
} catch {
|
||||||
|
// Verify we're back in the original directory
|
||||||
|
XCTAssertEqual(Dir.pwd.resolvingSymlinksInPath().path, originalDir.resolvingSymlinksInPath().path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - unlink/rmdir Tests
|
||||||
|
|
||||||
|
func testUnlinkRemovesDirectory() throws {
|
||||||
|
let dirToRemove = tempDir.appendingPathComponent("dir-to-remove")
|
||||||
|
try FileManager.default.createDirectory(at: dirToRemove, withIntermediateDirectories: true)
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: dirToRemove.path))
|
||||||
|
|
||||||
|
XCTAssertNoThrow(try Dir.unlink(dirToRemove))
|
||||||
|
XCTAssertFalse(FileManager.default.fileExists(atPath: dirToRemove.path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRmdirRemovesDirectory() throws {
|
||||||
|
let dirToRemove = tempDir.appendingPathComponent("dir-to-rmdir")
|
||||||
|
try FileManager.default.createDirectory(at: dirToRemove, withIntermediateDirectories: true)
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: dirToRemove.path))
|
||||||
|
|
||||||
|
XCTAssertNoThrow(try Dir.rmdir(dirToRemove))
|
||||||
|
XCTAssertFalse(FileManager.default.fileExists(atPath: dirToRemove.path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUnlinkThrowsForNonExistentDirectory() {
|
||||||
|
let nonExistent = tempDir.appendingPathComponent("does-not-exist")
|
||||||
|
XCTAssertThrowsError(try Dir.unlink(nonExistent))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Reading Contents Tests
|
||||||
|
|
||||||
|
func testChildren() throws {
|
||||||
|
let file1 = tempDir.appendingPathComponent("file1.txt")
|
||||||
|
let file2 = tempDir.appendingPathComponent("file2.txt")
|
||||||
|
let subdir = tempDir.appendingPathComponent("subdir")
|
||||||
|
|
||||||
|
try "Content 1".write(to: file1, atomically: true, encoding: .utf8)
|
||||||
|
try "Content 2".write(to: file2, atomically: true, encoding: .utf8)
|
||||||
|
try FileManager.default.createDirectory(at: subdir, withIntermediateDirectories: true)
|
||||||
|
|
||||||
|
let children = try Dir.children(tempDir)
|
||||||
|
XCTAssertEqual(children.count, 3)
|
||||||
|
|
||||||
|
let childNames = children.map { $0.lastPathComponent }.sorted()
|
||||||
|
XCTAssertEqual(childNames, ["file1.txt", "file2.txt", "subdir"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExists() throws {
|
||||||
|
XCTAssertTrue(try Dir.exists(tempDir))
|
||||||
|
XCTAssertTrue(try Dir.exists(Dir.home))
|
||||||
|
|
||||||
|
let nonExistent = tempDir.appendingPathComponent("not-a-directory")
|
||||||
|
XCTAssertFalse(try Dir.exists(nonExistent))
|
||||||
|
|
||||||
|
let fileNotDir = tempDir.appendingPathComponent("file.txt")
|
||||||
|
try "I'm a file".write(to: fileNotDir, atomically: true, encoding: .utf8)
|
||||||
|
XCTAssertFalse(try Dir.exists(fileNotDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIsEmpty() throws {
|
||||||
|
XCTAssertTrue(try Dir.isEmpty(tempDir))
|
||||||
|
|
||||||
|
let file = tempDir.appendingPathComponent("file.txt")
|
||||||
|
try "Content".write(to: file, atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
|
XCTAssertFalse(try Dir.isEmpty(tempDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Dir Struct and Collection Tests
|
||||||
|
|
||||||
|
func testDirInitialization() throws {
|
||||||
|
let file1 = tempDir.appendingPathComponent("a.txt")
|
||||||
|
let file2 = tempDir.appendingPathComponent("b.txt")
|
||||||
|
try "A".write(to: file1, atomically: true, encoding: .utf8)
|
||||||
|
try "B".write(to: file2, atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
|
let dir = try Dir(url: tempDir)
|
||||||
|
XCTAssertEqual(dir.url, tempDir)
|
||||||
|
XCTAssertEqual(dir.count, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDirSubscript() throws {
|
||||||
|
let file1 = tempDir.appendingPathComponent("1.txt")
|
||||||
|
let file2 = tempDir.appendingPathComponent("2.txt")
|
||||||
|
let file3 = tempDir.appendingPathComponent("3.txt")
|
||||||
|
try "One".write(to: file1, atomically: true, encoding: .utf8)
|
||||||
|
try "Two".write(to: file2, atomically: true, encoding: .utf8)
|
||||||
|
try "Three".write(to: file3, atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
|
let dir = try Dir(url: tempDir)
|
||||||
|
XCTAssertEqual(dir.count, 3)
|
||||||
|
|
||||||
|
let firstItem = dir[0]
|
||||||
|
XCTAssertTrue(["1.txt", "2.txt", "3.txt"].contains(firstItem.lastPathComponent))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDirIteration() throws {
|
||||||
|
let files = ["rock.mp3", "jazz.mp3", "punk.mp3"]
|
||||||
|
for filename in files {
|
||||||
|
let file = tempDir.appendingPathComponent(filename)
|
||||||
|
try filename.write(to: file, atomically: true, encoding: .utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir = try Dir(url: tempDir)
|
||||||
|
var foundFiles: [String] = []
|
||||||
|
|
||||||
|
for url in dir {
|
||||||
|
foundFiles.append(url.lastPathComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(foundFiles.sorted(), files.sorted())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDirEquality() throws {
|
||||||
|
let dir1 = try Dir(url: tempDir)
|
||||||
|
let dir2 = try Dir(url: tempDir)
|
||||||
|
XCTAssertEqual(dir1, dir2)
|
||||||
|
|
||||||
|
let otherDir = URL.temporaryDirectory
|
||||||
|
.appendingPathComponent("other-\(UUID().uuidString)")
|
||||||
|
try FileManager.default.createDirectory(at: otherDir, withIntermediateDirectories: true)
|
||||||
|
defer { try? FileManager.default.removeItem(at: otherDir) }
|
||||||
|
|
||||||
|
let dir3 = try Dir(url: otherDir)
|
||||||
|
XCTAssertNotEqual(dir1, dir3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Glob Tests
|
||||||
|
|
||||||
|
func testGlobBasicWildcard() throws {
|
||||||
|
// Create test files
|
||||||
|
try "content1".write(to: tempDir.appendingPathComponent("file1.txt"), atomically: true, encoding: .utf8)
|
||||||
|
try "content2".write(to: tempDir.appendingPathComponent("file2.txt"), atomically: true, encoding: .utf8)
|
||||||
|
try "content3".write(to: tempDir.appendingPathComponent("file3.md"), atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
|
let results = Dir.glob(base: tempDir, "*.txt")
|
||||||
|
XCTAssertEqual(results.count, 2)
|
||||||
|
|
||||||
|
let filenames = results.map { $0.lastPathComponent }.sorted()
|
||||||
|
XCTAssertEqual(filenames, ["file1.txt", "file2.txt"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGlobSubscript() throws {
|
||||||
|
// Create test files
|
||||||
|
try "swift1".write(to: tempDir.appendingPathComponent("app.swift"), atomically: true, encoding: .utf8)
|
||||||
|
try "swift2".write(to: tempDir.appendingPathComponent("lib.swift"), atomically: true, encoding: .utf8)
|
||||||
|
try "objc".write(to: tempDir.appendingPathComponent("bridge.m"), atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
|
let swiftFiles = Dir[tempDir, "*.swift"]
|
||||||
|
XCTAssertEqual(swiftFiles.count, 2)
|
||||||
|
|
||||||
|
let allFiles = Dir[tempDir, "*.*"]
|
||||||
|
XCTAssertEqual(allFiles.count, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGlobQuestionMark() throws {
|
||||||
|
// Create test files with pattern
|
||||||
|
try "a".write(to: tempDir.appendingPathComponent("a1.txt"), atomically: true, encoding: .utf8)
|
||||||
|
try "b".write(to: tempDir.appendingPathComponent("b2.txt"), atomically: true, encoding: .utf8)
|
||||||
|
try "c".write(to: tempDir.appendingPathComponent("abc.txt"), atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
|
let results = Dir.glob(base: tempDir, "??.txt")
|
||||||
|
XCTAssertEqual(results.count, 2)
|
||||||
|
|
||||||
|
let filenames = results.map { $0.lastPathComponent }.sorted()
|
||||||
|
XCTAssertEqual(filenames, ["a1.txt", "b2.txt"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGlobCharacterClass() throws {
|
||||||
|
// Create test files
|
||||||
|
try "1".write(to: tempDir.appendingPathComponent("test1.txt"), atomically: true, encoding: .utf8)
|
||||||
|
try "2".write(to: tempDir.appendingPathComponent("test2.txt"), atomically: true, encoding: .utf8)
|
||||||
|
try "a".write(to: tempDir.appendingPathComponent("testa.txt"), atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
|
let numberFiles = Dir.glob(base: tempDir, "test[0-9].txt")
|
||||||
|
XCTAssertEqual(numberFiles.count, 2)
|
||||||
|
|
||||||
|
let letterFiles = Dir.glob(base: tempDir, "test[a-z].txt")
|
||||||
|
XCTAssertEqual(letterFiles.count, 1)
|
||||||
|
XCTAssertEqual(letterFiles.first?.lastPathComponent, "testa.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGlobRecursive() throws {
|
||||||
|
// Create nested directory structure
|
||||||
|
let subdir1 = tempDir.appendingPathComponent("src")
|
||||||
|
let subdir2 = subdir1.appendingPathComponent("lib")
|
||||||
|
try FileManager.default.createDirectory(at: subdir2, withIntermediateDirectories: true)
|
||||||
|
|
||||||
|
try "root".write(to: tempDir.appendingPathComponent("root.swift"), atomically: true, encoding: .utf8)
|
||||||
|
try "src".write(to: subdir1.appendingPathComponent("main.swift"), atomically: true, encoding: .utf8)
|
||||||
|
try "lib".write(to: subdir2.appendingPathComponent("utils.swift"), atomically: true, encoding: .utf8)
|
||||||
|
try "lib2".write(to: subdir2.appendingPathComponent("helpers.swift"), atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
|
// Test ** for recursive matching
|
||||||
|
let allSwiftFiles = Dir.glob(base: tempDir, "**/*.swift")
|
||||||
|
XCTAssertEqual(allSwiftFiles.count, 4)
|
||||||
|
|
||||||
|
// Test ** matching zero segments
|
||||||
|
let srcSwiftFiles = Dir.glob(base: tempDir, "src/**/*.swift")
|
||||||
|
XCTAssertEqual(srcSwiftFiles.count, 3) // main.swift, utils.swift, helpers.swift
|
||||||
|
|
||||||
|
// Test specific path with **
|
||||||
|
let libSwiftFiles = Dir.glob(base: tempDir, "**/lib/*.swift")
|
||||||
|
XCTAssertEqual(libSwiftFiles.count, 2) // utils.swift, helpers.swift
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGlobNoMatches() {
|
||||||
|
let results = Dir.glob(base: tempDir, "*.nonexistent")
|
||||||
|
XCTAssertTrue(results.isEmpty)
|
||||||
|
|
||||||
|
let subscriptResults = Dir[tempDir, "no-such-file.*"]
|
||||||
|
XCTAssertTrue(subscriptResults.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGlobHiddenFiles() throws {
|
||||||
|
// Create hidden file
|
||||||
|
try "hidden".write(to: tempDir.appendingPathComponent(".hidden.txt"), atomically: true, encoding: .utf8)
|
||||||
|
try "visible".write(to: tempDir.appendingPathComponent("visible.txt"), atomically: true, encoding: .utf8)
|
||||||
|
|
||||||
|
// By default, * should not match hidden files (FNM_PERIOD flag)
|
||||||
|
let starResults = Dir.glob(base: tempDir, "*.txt")
|
||||||
|
XCTAssertEqual(starResults.count, 1)
|
||||||
|
XCTAssertEqual(starResults.first?.lastPathComponent, "visible.txt")
|
||||||
|
|
||||||
|
// Explicitly matching hidden files
|
||||||
|
let hiddenResults = Dir.glob(base: tempDir, ".*.txt")
|
||||||
|
XCTAssertEqual(hiddenResults.count, 1)
|
||||||
|
XCTAssertEqual(hiddenResults.first?.lastPathComponent, ".hidden.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Debug Description Tests
|
||||||
|
|
||||||
|
func testDebugDescription() throws {
|
||||||
|
let dir = try Dir(url: tempDir)
|
||||||
|
let debugDescription = String(reflecting: dir)
|
||||||
|
XCTAssertEqual(debugDescription, "<Dir:\(tempDir.path)>")
|
||||||
|
|
||||||
|
let homeDir = try Dir(url: Dir.home)
|
||||||
|
let homeDebugDescription = String(reflecting: homeDir)
|
||||||
|
XCTAssertEqual(homeDebugDescription, "<Dir:\(Dir.home.path)>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDescription() throws {
|
||||||
|
let dir = try Dir(url: tempDir)
|
||||||
|
let description = String(describing: dir)
|
||||||
|
XCTAssertEqual(description, tempDir.path)
|
||||||
|
|
||||||
|
let homeDir = try Dir(url: Dir.home)
|
||||||
|
let homeDescription = String(describing: homeDir)
|
||||||
|
XCTAssertEqual(homeDescription, Dir.home.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - tmpdir Tests
|
||||||
|
|
||||||
|
func testTmp() {
|
||||||
|
let tmp = Dir.tmp
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: tmp.path))
|
||||||
|
|
||||||
|
var isDirectory: ObjCBool = false
|
||||||
|
FileManager.default.fileExists(atPath: tmp.path, isDirectory: &isDirectory)
|
||||||
|
XCTAssertTrue(isDirectory.boolValue)
|
||||||
|
|
||||||
|
XCTAssertEqual(tmp, URL.temporaryDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - mkdir Tests
|
||||||
|
|
||||||
|
func testMkdir() throws {
|
||||||
|
let newDir = tempDir.appendingPathComponent("test-mkdir")
|
||||||
|
XCTAssertFalse(FileManager.default.fileExists(atPath: newDir.path))
|
||||||
|
|
||||||
|
try Dir.mkdir(newDir)
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: newDir.path))
|
||||||
|
|
||||||
|
var isDirectory: ObjCBool = false
|
||||||
|
FileManager.default.fileExists(atPath: newDir.path, isDirectory: &isDirectory)
|
||||||
|
XCTAssertTrue(isDirectory.boolValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMkdirWithPermissions() throws {
|
||||||
|
let newDir = tempDir.appendingPathComponent("test-mkdir-perms")
|
||||||
|
|
||||||
|
try Dir.mkdir(newDir, permissions: 0o700)
|
||||||
|
|
||||||
|
let attributes = try FileManager.default.attributesOfItem(atPath: newDir.path)
|
||||||
|
let permissions = attributes[.posixPermissions] as? Int
|
||||||
|
XCTAssertNotNil(permissions)
|
||||||
|
|
||||||
|
#if os(macOS) || os(Linux)
|
||||||
|
XCTAssertEqual(permissions! & 0o777, 0o700)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMkdirFailsIfDirectoryExists() throws {
|
||||||
|
let existingDir = tempDir.appendingPathComponent("existing")
|
||||||
|
try Dir.mkdir(existingDir)
|
||||||
|
|
||||||
|
XCTAssertThrowsError(try Dir.mkdir(existingDir)) { error in
|
||||||
|
XCTAssertTrue(error is CocoaError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - mktmpdir Tests
|
||||||
|
|
||||||
|
func testMktmpdir() throws {
|
||||||
|
let tmpDir = try Dir.mktmpdir()
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: tmpDir.path))
|
||||||
|
XCTAssertTrue(tmpDir.path.contains("d-"))
|
||||||
|
|
||||||
|
var isDirectory: ObjCBool = false
|
||||||
|
FileManager.default.fileExists(atPath: tmpDir.path, isDirectory: &isDirectory)
|
||||||
|
XCTAssertTrue(isDirectory.boolValue)
|
||||||
|
|
||||||
|
let attributes = try FileManager.default.attributesOfItem(atPath: tmpDir.path)
|
||||||
|
let permissions = attributes[.posixPermissions] as? Int
|
||||||
|
XCTAssertNotNil(permissions)
|
||||||
|
|
||||||
|
#if os(macOS) || os(Linux)
|
||||||
|
XCTAssertEqual(permissions! & 0o777, 0o700)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
try FileManager.default.removeItem(at: tmpDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMktempdirWithPrefix() throws {
|
||||||
|
let tmpDir = try Dir.mktmpdir(prefix: "fileotter")
|
||||||
|
XCTAssertTrue(tmpDir.path.contains("fileotter-"))
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: tmpDir.path))
|
||||||
|
|
||||||
|
try FileManager.default.removeItem(at: tmpDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMktempdirWithPrefixAndSuffix() throws {
|
||||||
|
let tmpDir = try Dir.mktmpdir(prefix: "test", suffix: "tmp")
|
||||||
|
XCTAssertTrue(tmpDir.lastPathComponent.hasPrefix("test-"))
|
||||||
|
XCTAssertTrue(tmpDir.lastPathComponent.hasSuffix("-tmp"))
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: tmpDir.path))
|
||||||
|
|
||||||
|
try FileManager.default.removeItem(at: tmpDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMktempdirWithBlock() throws {
|
||||||
|
var blockExecuted = false
|
||||||
|
var tmpDirInBlock: URL?
|
||||||
|
var fileCreated = false
|
||||||
|
|
||||||
|
let result = try Dir.mktmpdir(prefix: "block") { tmpDir in
|
||||||
|
blockExecuted = true
|
||||||
|
tmpDirInBlock = tmpDir
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: tmpDir.path))
|
||||||
|
|
||||||
|
let testFile = tmpDir.appendingPathComponent("test.txt")
|
||||||
|
try "Hello from mktmpdir block".write(to: testFile, atomically: true, encoding: .utf8)
|
||||||
|
fileCreated = FileManager.default.fileExists(atPath: testFile.path)
|
||||||
|
|
||||||
|
return "block result"
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertTrue(blockExecuted)
|
||||||
|
XCTAssertEqual(result, "block result")
|
||||||
|
XCTAssertTrue(fileCreated)
|
||||||
|
|
||||||
|
if let tmpDirInBlock = tmpDirInBlock {
|
||||||
|
XCTAssertFalse(FileManager.default.fileExists(atPath: tmpDirInBlock.path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMktempdirWithBlockThrowingError() {
|
||||||
|
do {
|
||||||
|
try Dir.mktmpdir { tmpDir in
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: tmpDir.path))
|
||||||
|
throw NSError(domain: "TestError", code: 42)
|
||||||
|
}
|
||||||
|
XCTFail("Should have thrown an error")
|
||||||
|
} catch {
|
||||||
|
let nsError = error as NSError
|
||||||
|
XCTAssertEqual(nsError.domain, "TestError")
|
||||||
|
XCTAssertEqual(nsError.code, 42)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMktempdirEachCallCreatesUniqueDirectory() throws {
|
||||||
|
let tmpDir1 = try Dir.mktmpdir()
|
||||||
|
let tmpDir2 = try Dir.mktmpdir()
|
||||||
|
|
||||||
|
XCTAssertNotEqual(tmpDir1, tmpDir2)
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: tmpDir1.path))
|
||||||
|
XCTAssertTrue(FileManager.default.fileExists(atPath: tmpDir2.path))
|
||||||
|
|
||||||
|
try FileManager.default.removeItem(at: tmpDir1)
|
||||||
|
try FileManager.default.removeItem(at: tmpDir2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue