mirror of
https://github.com/samsonjs/FileOtter.git
synced 2026-03-25 08:25:49 +00:00
497 lines
19 KiB
Swift
497 lines
19 KiB
Swift
//
|
|
// FileOtterTests.swift
|
|
// FileOtterTests
|
|
//
|
|
// Created by Sami Samhuri on 2024-04-24.
|
|
//
|
|
|
|
@testable import FileOtter
|
|
import XCTest
|
|
|
|
final class DirTests: XCTestCase {
|
|
var tempDir: URL!
|
|
|
|
override func setUpWithError() throws {
|
|
tempDir = URL.temporaryDirectory
|
|
.appendingPathComponent("FileOtterTests-\(UUID().uuidString)")
|
|
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
|
|
}
|
|
|
|
override func tearDownWithError() throws {
|
|
if FileManager.default.fileExists(atPath: tempDir.path) {
|
|
try FileManager.default.removeItem(at: tempDir)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|