WIP: Implement File

This commit is contained in:
Sami Samhuri 2025-08-25 08:59:50 -07:00
parent f85da3e7b5
commit f24ed714e6
No known key found for this signature in database
9 changed files with 1590 additions and 3 deletions

View file

@ -2,9 +2,458 @@
// File.swift
// FileOtter
//
// Created by Sami Samhuri on 2024-04-24.
// Created by Sami Samhuri on 2025-08-19.
//
import Foundation
public struct File {}
// MARK: - File Class
/// A File object represents an open file with automatic resource management.
/// The file handle is automatically closed when the File object is deallocated.
public class File: CustomStringConvertible, CustomDebugStringConvertible {
private let handle: FileHandle
public let url: URL
public let mode: Mode
// MARK: - Mode
public enum Mode {
case read // r
case write // w
case append // a
case readWrite // r+
case readWriteNew // w+
case readAppend // a+
case writeExclusive // wx (create, fail if exists)
}
// MARK: - Initialization
public init(url: URL, mode: Mode = .read, permissions: Int = 0o666) throws {
self.url = url
self.mode = mode
self.handle = FileHandle() // TODO: Implement proper opening
fatalError("Not implemented")
}
deinit {
try? handle.close()
}
// MARK: - Opening with blocks
public static func open(url: URL, mode: Mode = .read, permissions: Int = 0o666) throws -> File {
fatalError("Not implemented")
}
@discardableResult
public static func open<T>(url: URL, mode: Mode = .read, permissions: Int = 0o666, _ block: (File) throws -> T) rethrows -> T {
fatalError("Not implemented")
}
// MARK: - Instance Properties
public var atime: Date {
fatalError("Not implemented")
}
public var mtime: Date {
fatalError("Not implemented")
}
public var ctime: Date {
fatalError("Not implemented")
}
public var birthtime: Date {
fatalError("Not implemented")
}
public var size: Int {
fatalError("Not implemented")
}
// MARK: - Instance Methods
public func chmod(_ permissions: Int) throws {
fatalError("Not implemented")
}
public func chown(owner: Int? = nil, group: Int? = nil) throws {
fatalError("Not implemented")
}
public func truncate(to size: Int) throws {
fatalError("Not implemented")
}
public func flock(_ operation: LockOperation) throws {
fatalError("Not implemented")
}
public func stat() throws -> FileStat {
fatalError("Not implemented")
}
public func lstat() throws -> FileStat {
fatalError("Not implemented")
}
public func close() throws {
fatalError("Not implemented")
}
// MARK: - CustomStringConvertible
public var description: String {
url.path
}
public var debugDescription: String {
"<File:\(url.path) mode:\(mode)>"
}
}
// MARK: - Static Path Operations
public extension File {
static func basename(_ url: URL, suffix: String? = nil) -> String {
// Handle root path special case
if url.path == "/" {
return "/"
}
// Get the last path component using URL's built-in method
let base = url.lastPathComponent
// If no suffix specified, return the base
guard let suffix = suffix else {
return base
}
// Handle wildcard suffix ".*"
if suffix == ".*" {
// Use URL's pathExtension to remove any extension
let withoutExt = url.deletingPathExtension().lastPathComponent
return withoutExt
}
// Handle regular suffix
if base.hasSuffix(suffix) {
return String(base.dropLast(suffix.count))
}
return base
}
static func dirname(_ url: URL, level: Int = 1) -> URL {
var result = url
for _ in 0..<level {
result = result.deletingLastPathComponent()
}
return result
}
static func extname(_ url: URL) -> String {
let ext = url.pathExtension
return ext.isEmpty ? "" : ".\(ext)"
}
static func split(_ url: URL) -> (dir: URL, name: String) {
fatalError("Not implemented")
}
static func join(_ components: String...) -> URL {
fatalError("Not implemented")
}
static func join(_ components: [String]) -> URL {
fatalError("Not implemented")
}
static func absolutePath(_ url: URL, relativeTo base: URL? = nil) -> URL {
fatalError("Not implemented")
}
static func expandPath(_ path: String) -> URL {
fatalError("Not implemented")
}
static func realpath(_ url: URL) throws -> URL {
fatalError("Not implemented")
}
static func realdirpath(_ url: URL) throws -> URL {
fatalError("Not implemented")
}
}
// MARK: - Static File Info
public extension File {
static func atime(_ url: URL) throws -> Date {
fatalError("Not implemented")
}
static func mtime(_ url: URL) throws -> Date {
fatalError("Not implemented")
}
static func ctime(_ url: URL) throws -> Date {
fatalError("Not implemented")
}
static func birthtime(_ url: URL) throws -> Date {
fatalError("Not implemented")
}
static func size(_ url: URL) throws -> Int {
fatalError("Not implemented")
}
static func stat(_ url: URL) throws -> FileStat {
fatalError("Not implemented")
}
static func lstat(_ url: URL) throws -> FileStat {
fatalError("Not implemented")
}
}
// MARK: - Static File Type Checks
public extension File {
static func exists(_ url: URL) -> Bool {
FileManager.default.fileExists(atPath: url.path)
}
static func isFile(_ url: URL) -> Bool {
var isDirectory: ObjCBool = false
let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory)
return exists && !isDirectory.boolValue
}
static func isDirectory(_ url: URL) -> Bool {
var isDirectory: ObjCBool = false
let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory)
return exists && isDirectory.boolValue
}
static func isSymlink(_ url: URL) -> Bool {
fatalError("Not implemented")
}
static func isBlockDevice(_ url: URL) -> Bool {
fatalError("Not implemented")
}
static func isCharDevice(_ url: URL) -> Bool {
fatalError("Not implemented")
}
static func isPipe(_ url: URL) -> Bool {
fatalError("Not implemented")
}
static func isSocket(_ url: URL) -> Bool {
fatalError("Not implemented")
}
static func isEmpty(_ url: URL) throws -> Bool {
fatalError("Not implemented")
}
// Ruby aliases
static func isZero(_ url: URL) throws -> Bool {
try isEmpty(url)
}
}
// MARK: - Static Permission Checks
public extension File {
static func isReadable(_ url: URL) -> Bool {
fatalError("Not implemented")
}
static func isWritable(_ url: URL) -> Bool {
fatalError("Not implemented")
}
static func isExecutable(_ url: URL) -> Bool {
fatalError("Not implemented")
}
static func isOwned(_ url: URL) -> Bool {
fatalError("Not implemented")
}
static func isGroupOwned(_ url: URL) -> Bool {
fatalError("Not implemented")
}
static func isWorldReadable(_ url: URL) -> Int? {
fatalError("Not implemented")
}
static func isWorldWritable(_ url: URL) -> Int? {
fatalError("Not implemented")
}
static func isSetuid(_ url: URL) -> Bool {
fatalError("Not implemented")
}
static func isSetgid(_ url: URL) -> Bool {
fatalError("Not implemented")
}
static func isSticky(_ url: URL) -> Bool {
fatalError("Not implemented")
}
}
// MARK: - Static File Operations
public extension File {
static func chmod(_ url: URL, permissions: Int) throws {
fatalError("Not implemented")
}
static func chown(_ url: URL, owner: Int? = nil, group: Int? = nil) throws {
fatalError("Not implemented")
}
static func lchmod(_ url: URL, permissions: Int) throws {
fatalError("Not implemented")
}
static func lchown(_ url: URL, owner: Int? = nil, group: Int? = nil) throws {
fatalError("Not implemented")
}
static func link(source: URL, destination: URL) throws {
fatalError("Not implemented")
}
static func symlink(source: URL, destination: URL) throws {
fatalError("Not implemented")
}
static func readlink(_ url: URL) throws -> URL {
fatalError("Not implemented")
}
static func unlink(_ url: URL) throws {
fatalError("Not implemented")
}
static func delete(_ url: URL) throws {
try unlink(url)
}
static func rename(source: URL, destination: URL) throws {
fatalError("Not implemented")
}
static func truncate(_ url: URL, to size: Int) throws {
fatalError("Not implemented")
}
static func touch(_ url: URL) throws {
fatalError("Not implemented")
}
static func utime(_ url: URL, atime: Date, mtime: Date) throws {
fatalError("Not implemented")
}
static func lutime(_ url: URL, atime: Date, mtime: Date) throws {
fatalError("Not implemented")
}
static func mkfifo(_ url: URL, permissions: Int = 0o666) throws {
fatalError("Not implemented")
}
static func identical(_ url1: URL, _ url2: URL) throws -> Bool {
fatalError("Not implemented")
}
static func umask() -> Int {
fatalError("Not implemented")
}
static func umask(_ mask: Int) -> Int {
fatalError("Not implemented")
}
}
// MARK: - Pattern Matching
public extension File {
static func fnmatch(pattern: String, path: String, flags: FnmatchFlags = []) -> Bool {
fatalError("Not implemented")
}
}
// MARK: - Supporting Types
public struct FileStat {
public let dev: Int // Device ID
public let ino: Int // Inode number
public let mode: Int // File mode (permissions + type)
public let nlink: Int // Number of hard links
public let uid: Int // User ID of owner
public let gid: Int // Group ID of owner
public let rdev: Int // Device ID (if special file)
public let size: Int64 // Total size in bytes
public let blksize: Int // Block size for filesystem I/O
public let blocks: Int64 // Number of 512B blocks allocated
public let atime: Date // Last access time
public let mtime: Date // Last modification time
public let ctime: Date // Last status change time
public let birthtime: Date? // Creation time (if available)
}
public struct FnmatchFlags: OptionSet {
public let rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public static let pathname = FnmatchFlags(rawValue: 1 << 0) // FNM_PATHNAME
public static let noescape = FnmatchFlags(rawValue: 1 << 1) // FNM_NOESCAPE
public static let period = FnmatchFlags(rawValue: 1 << 2) // FNM_PERIOD
public static let casefold = FnmatchFlags(rawValue: 1 << 3) // FNM_CASEFOLD
public static let extglob = FnmatchFlags(rawValue: 1 << 4) // FNM_EXTGLOB
public static let dotmatch = FnmatchFlags(rawValue: 1 << 5) // FNM_DOTMATCH (custom)
}
public enum LockOperation {
case shared // LOCK_SH
case exclusive // LOCK_EX
case unlock // LOCK_UN
case nonBlocking // LOCK_NB (can be OR'd with others)
}
// MARK: - File Type enum
public enum FileType: String {
case file = "file"
case directory = "directory"
case characterSpecial = "characterSpecial"
case blockSpecial = "blockSpecial"
case fifo = "fifo"
case link = "link"
case socket = "socket"
case unknown = "unknown"
}
public extension File {
static func ftype(_ url: URL) -> FileType {
fatalError("Not implemented")
}
}

View file

@ -17,7 +17,7 @@ import Foundation
/// Examples:
/// "src/**/*.swift"
/// "/var/log/**/app*.log"
func globstar(_ pattern: String, base: URL? = nil) -> [URL] {
func globstar(_ pattern: String, base: URL? = nil) -> [String] {
// Normalize and split into path components
let comps = pattern.split(separator: "/", omittingEmptySubsequences: true).map(String.init)

View file

@ -0,0 +1,129 @@
//
// FileFnmatchTests.swift
// FileOtterTests
//
// Created by Sami Samhuri on 2025-08-19.
//
@testable import FileOtter
import XCTest
final class FileFnmatchTests: XCTestCase {
// MARK: - Basic Pattern Tests
func testExactMatch() throws {
// TODO: Implement
// File.fnmatch("cat", "cat") => true
// File.fnmatch("cat", "dog") => false
}
func testPartialMatch() throws {
// TODO: Implement
// File.fnmatch("cat", "category") => false (must match entire string)
}
// MARK: - Wildcard Tests
func testStarWildcard() throws {
// TODO: Implement
// File.fnmatch("c*", "cats") => true
// File.fnmatch("c*t", "cat") => true
// File.fnmatch("c*t", "c/a/b/t") => true
}
func testQuestionMarkWildcard() throws {
// TODO: Implement
// File.fnmatch("c?t", "cat") => true
// File.fnmatch("c??t", "cat") => false
}
func testDoubleStarWildcard() throws {
// TODO: Implement
// File.fnmatch("**/*.rb", "main.rb") => false
// File.fnmatch("**/*.rb", "lib/song.rb") => true
// File.fnmatch("**.rb", "main.rb") => true
}
// MARK: - Character Set Tests
func testCharacterSet() throws {
// TODO: Implement
// File.fnmatch("ca[a-z]", "cat") => true
// File.fnmatch("ca[0-9]", "cat") => false
}
func testNegatedCharacterSet() throws {
// TODO: Implement
// File.fnmatch("ca[^t]", "cat") => false
// File.fnmatch("ca[^t]", "cab") => true
}
// MARK: - Escape Tests
func testEscapedWildcard() throws {
// TODO: Implement
// File.fnmatch("\\?", "?") => true
// File.fnmatch("\\*", "*") => true
}
func testEscapeInBrackets() throws {
// TODO: Implement
// File.fnmatch("[\\?]", "?") => true
}
// MARK: - Flag Tests
func testCaseFoldFlag() throws {
// TODO: Implement
// File.fnmatch("cat", "CAT", flags: []) => false
// File.fnmatch("cat", "CAT", flags: .casefold) => true
}
func testPathnameFlag() throws {
// TODO: Implement
// File.fnmatch("*", "/", flags: []) => true
// File.fnmatch("*", "/", flags: .pathname) => false
// File.fnmatch("?", "/", flags: .pathname) => false
}
func testPeriodFlag() throws {
// TODO: Implement
// File.fnmatch("*", ".profile", flags: []) => false (FNM_PERIOD by default)
// File.fnmatch(".*", ".profile", flags: []) => true
}
func testDotmatchFlag() throws {
// TODO: Implement
// File.fnmatch("*", ".profile", flags: .dotmatch) => true
}
func testNoescapeFlag() throws {
// TODO: Implement
// File.fnmatch("\\a", "a", flags: []) => true
// File.fnmatch("\\a", "\\a", flags: .noescape) => true
}
func testExtglobFlag() throws {
// TODO: Implement
// File.fnmatch("c{at,ub}s", "cats", flags: []) => false
// File.fnmatch("c{at,ub}s", "cats", flags: .extglob) => true
// File.fnmatch("c{at,ub}s", "cubs", flags: .extglob) => true
}
// MARK: - Complex Pattern Tests
func testComplexGlobPatterns() throws {
// TODO: Implement
// File.fnmatch("**/foo", "a/b/c/foo", flags: .pathname) => true
// File.fnmatch("**/foo", "/a/b/c/foo", flags: .pathname) => true
// File.fnmatch("**/foo", "a/.b/c/foo", flags: .pathname) => false
// File.fnmatch("**/foo", "a/.b/c/foo", flags: [.pathname, .dotmatch]) => true
}
func testHiddenFileMatching() throws {
// TODO: Implement
// File.fnmatch("*", "dave/.profile", flags: []) => true
// File.fnmatch("**/.*", "a/.hidden", flags: .pathname) => true
}
}

View file

@ -0,0 +1,143 @@
//
// FileInfoTests.swift
// FileOtterTests
//
// Created by Sami Samhuri on 2025-08-19.
//
@testable import FileOtter
import XCTest
final class FileInfoTests: XCTestCase {
var tempDir: URL!
var testFile: URL!
override func setUpWithError() throws {
tempDir = URL.temporaryDirectory
.appendingPathComponent("FileInfoTests-\(UUID().uuidString)")
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
testFile = tempDir.appendingPathComponent("test.txt")
try "Test content".write(to: testFile, atomically: true, encoding: .utf8)
}
override func tearDownWithError() throws {
if FileManager.default.fileExists(atPath: tempDir.path) {
try FileManager.default.removeItem(at: tempDir)
}
}
// MARK: - Time-based Tests
func testAtime() throws {
// TODO: Implement
// File.atime(url) returns last access time
}
func testMtime() throws {
// TODO: Implement
// File.mtime(url) returns last modification time
}
func testCtime() throws {
// TODO: Implement
// File.ctime(url) returns last status change time
}
func testBirthtime() throws {
// TODO: Implement
// File.birthtime(url) returns creation time
}
func testBirthtimeThrowsOnUnsupportedPlatform() throws {
// TODO: Implement if platform doesn't support birthtime
}
// MARK: - Size Tests
func testSize() throws {
// TODO: Implement
// File.size(url) returns file size in bytes
}
func testSizeThrowsForNonExistent() throws {
// TODO: Implement
}
func testSizeForEmptyFile() throws {
// TODO: Implement
}
// MARK: - Stat Tests
func testStat() throws {
// TODO: Implement
// File.stat(url) returns FileStat object
}
func testStatThrowsForNonExistent() throws {
// TODO: Implement
}
func testLstat() throws {
// TODO: Implement
// File.lstat(url) doesn't follow symlinks
}
func testLstatForSymlink() throws {
// TODO: Implement
// Create symlink and verify lstat returns symlink stats, not target
}
// MARK: - Instance Method Tests
func testInstanceAtime() throws {
// TODO: Implement
// file.atime returns access time
}
func testInstanceMtime() throws {
// TODO: Implement
// file.mtime returns modification time
}
func testInstanceCtime() throws {
// TODO: Implement
// file.ctime returns status change time
}
func testInstanceBirthtime() throws {
// TODO: Implement
// file.birthtime returns creation time
}
func testInstanceSize() throws {
// TODO: Implement
// file.size returns file size
}
func testInstanceStat() throws {
// TODO: Implement
// file.stat() returns FileStat object
}
func testInstanceLstat() throws {
// TODO: Implement
// file.lstat() doesn't follow symlinks
}
// MARK: - FileStat Tests
func testFileStatProperties() throws {
// TODO: Implement
// Verify all FileStat properties are populated correctly
}
func testFileStatForDirectory() throws {
// TODO: Implement
}
func testFileStatForSymlink() throws {
// TODO: Implement
}
}

View file

@ -0,0 +1,151 @@
//
// FileOpenTests.swift
// FileOtterTests
//
// Created by Sami Samhuri on 2025-08-19.
//
@testable import FileOtter
import XCTest
final class FileOpenTests: XCTestCase {
var tempDir: URL!
var testFile: URL!
override func setUpWithError() throws {
tempDir = URL.temporaryDirectory
.appendingPathComponent("FileOpenTests-\(UUID().uuidString)")
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
testFile = tempDir.appendingPathComponent("test.txt")
try "Initial content\nSecond line\n".write(to: testFile, atomically: true, encoding: .utf8)
}
override func tearDownWithError() throws {
if FileManager.default.fileExists(atPath: tempDir.path) {
try FileManager.default.removeItem(at: tempDir)
}
}
// MARK: - Open Mode Tests
func testOpenReadMode() throws {
// TODO: Implement
// File(url, mode: .read) opens for reading only
}
func testOpenWriteMode() throws {
// TODO: Implement
// File(url, mode: .write) truncates and opens for writing
}
func testOpenAppendMode() throws {
// TODO: Implement
// File(url, mode: .append) opens for appending
}
func testOpenReadWriteMode() throws {
// TODO: Implement
// File(url, mode: .readWrite) opens for read and write
}
func testOpenReadWriteNewMode() throws {
// TODO: Implement
// File(url, mode: .readWriteNew) truncates and opens for read/write
}
func testOpenReadAppendMode() throws {
// TODO: Implement
// File(url, mode: .readAppend) opens for read and append
}
func testOpenWriteExclusiveMode() throws {
// TODO: Implement
// File(url, mode: .writeExclusive) creates new file, fails if exists
}
// MARK: - Open Method Tests
func testStaticOpen() throws {
// TODO: Implement
// File.open(url) returns File object
}
func testStaticOpenWithBlock() throws {
// TODO: Implement
// File.open(url) { file in ... } auto-closes file
}
func testStaticOpenWithBlockThrowingError() throws {
// TODO: Implement
// File.open with block closes file even on error
}
// MARK: - File Creation Tests
func testOpenCreatesFileInWriteMode() throws {
// TODO: Implement
// Opening non-existent file in write mode creates it
}
func testOpenFailsInReadModeForNonExistent() throws {
// TODO: Implement
// Opening non-existent file in read mode throws error
}
func testOpenWithPermissions() throws {
// TODO: Implement
// File(url, mode: .write, permissions: 0o644) creates with permissions
}
// MARK: - Auto-close Tests
func testDeinitClosesHandle() throws {
// TODO: Implement
// File handle is closed when File object is deallocated
}
func testExplicitClose() throws {
// TODO: Implement
// file.close() explicitly closes handle
}
func testDoubleCloseIsOK() throws {
// TODO: Implement
// Calling close() twice doesn't throw
}
// MARK: - Locking Tests
func testFlockShared() throws {
// TODO: Implement
// file.flock(.shared) acquires shared lock
}
func testFlockExclusive() throws {
// TODO: Implement
// file.flock(.exclusive) acquires exclusive lock
}
func testFlockUnlock() throws {
// TODO: Implement
// file.flock(.unlock) releases lock
}
func testFlockNonBlocking() throws {
// TODO: Implement
// file.flock(.nonBlocking) returns immediately if can't lock
}
// MARK: - Description Tests
func testDescription() throws {
// TODO: Implement
// file.description returns path
}
func testDebugDescription() throws {
// TODO: Implement
// file.debugDescription includes path and mode
}
}

View file

@ -0,0 +1,187 @@
//
// FileOperationTests.swift
// FileOtterTests
//
// Created by Sami Samhuri on 2025-08-19.
//
@testable import FileOtter
import XCTest
final class FileOperationTests: XCTestCase {
var tempDir: URL!
var sourceFile: URL!
var destFile: URL!
override func setUpWithError() throws {
tempDir = URL.temporaryDirectory
.appendingPathComponent("FileOperationTests-\(UUID().uuidString)")
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
sourceFile = tempDir.appendingPathComponent("source.txt")
try "Source content".write(to: sourceFile, atomically: true, encoding: .utf8)
destFile = tempDir.appendingPathComponent("dest.txt")
}
override func tearDownWithError() throws {
if FileManager.default.fileExists(atPath: tempDir.path) {
try FileManager.default.removeItem(at: tempDir)
}
}
// MARK: - Link Tests
func testLink() throws {
// TODO: Implement
// File.link(source, destination) creates hard link
}
func testLinkThrowsIfDestExists() throws {
// TODO: Implement
// File.link should not overwrite existing file
}
func testSymlink() throws {
// TODO: Implement
// File.symlink(source, destination) creates symbolic link
}
func testSymlinkThrowsIfDestExists() throws {
// TODO: Implement
}
func testReadlink() throws {
// TODO: Implement
// File.readlink(url) returns target of symlink
}
func testReadlinkThrowsForNonSymlink() throws {
// TODO: Implement
}
// MARK: - Delete Tests
func testUnlink() throws {
// TODO: Implement
// File.unlink(url) deletes file
}
func testUnlinkThrowsForNonExistent() throws {
// TODO: Implement
}
func testDelete() throws {
// TODO: Implement
// File.delete(url) is alias for unlink
}
// MARK: - Rename Tests
func testRename() throws {
// TODO: Implement
// File.rename(source, destination) moves/renames file
}
func testRenameOverwritesExisting() throws {
// TODO: Implement
// rename should overwrite if dest exists
}
func testRenameThrowsForNonExistent() throws {
// TODO: Implement
}
// MARK: - Truncate Tests
func testTruncate() throws {
// TODO: Implement
// File.truncate(url, size) truncates file to size
}
func testTruncateExpands() throws {
// TODO: Implement
// truncate can expand file with zero padding
}
func testTruncateThrowsForNonExistent() throws {
// TODO: Implement
}
func testInstanceTruncate() throws {
// TODO: Implement
// file.truncate(size) truncates open file
}
// MARK: - Touch Tests
func testTouch() throws {
// TODO: Implement
// File.touch(url) updates access/modification times
}
func testTouchCreatesFile() throws {
// TODO: Implement
// touch creates file if it doesn't exist
}
// MARK: - utime Tests
func testUtime() throws {
// TODO: Implement
// File.utime(url, atime, mtime) sets specific times
}
func testUtimeFollowsSymlinks() throws {
// TODO: Implement
// utime affects target of symlink
}
func testLutime() throws {
// TODO: Implement
// File.lutime(url, atime, mtime) sets symlink times
}
// MARK: - mkfifo Tests
func testMkfifo() throws {
// TODO: Implement
// File.mkfifo(url) creates named pipe
}
func testMkfifoWithPermissions() throws {
// TODO: Implement
// File.mkfifo(url, permissions) creates with specific perms
}
func testMkfifoThrowsIfExists() throws {
// TODO: Implement
}
// MARK: - identical Tests
func testIdentical() throws {
// TODO: Implement
// File.identical(url1, url2) returns true for same file
}
func testIdenticalForHardLink() throws {
// TODO: Implement
// identical returns true for hard links to same file
}
func testIdenticalForSymlink() throws {
// TODO: Implement
// identical returns true for symlink and target
}
func testIdenticalForDifferent() throws {
// TODO: Implement
// identical returns false for different files
}
func testIdenticalForSameContent() throws {
// TODO: Implement
// identical returns false for files with same content but different inodes
}
}

View file

@ -0,0 +1,167 @@
//
// FilePathTests.swift
// FileOtterTests
//
// Created by Sami Samhuri on 2025-08-19.
//
@testable import FileOtter
import XCTest
final class FilePathTests: XCTestCase {
var tempDir: URL!
override func setUpWithError() throws {
tempDir = URL.temporaryDirectory
.appendingPathComponent("FilePathTests-\(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: - basename Tests
func testBasename() throws {
let url1 = URL(fileURLWithPath: "/home/user/file.txt")
XCTAssertEqual(File.basename(url1), "file.txt")
let url2 = URL(fileURLWithPath: "/home/user/dir/")
XCTAssertEqual(File.basename(url2), "dir")
let url3 = URL(fileURLWithPath: "/")
XCTAssertEqual(File.basename(url3), "/")
let url4 = URL(fileURLWithPath: "file.rb")
XCTAssertEqual(File.basename(url4), "file.rb")
}
func testBasenameWithSuffix() throws {
let url = URL(fileURLWithPath: "/home/user/file.txt")
XCTAssertEqual(File.basename(url, suffix: ".txt"), "file")
XCTAssertEqual(File.basename(url, suffix: ".rb"), "file.txt")
let url2 = URL(fileURLWithPath: "/home/user/archive.tar.gz")
XCTAssertEqual(File.basename(url2, suffix: ".gz"), "archive.tar")
XCTAssertEqual(File.basename(url2, suffix: ".tar.gz"), "archive")
}
func testBasenameWithWildcardSuffix() throws {
let url = URL(fileURLWithPath: "/home/user/file.txt")
XCTAssertEqual(File.basename(url, suffix: ".*"), "file")
let url2 = URL(fileURLWithPath: "/home/user/archive.tar.gz")
XCTAssertEqual(File.basename(url2, suffix: ".*"), "archive.tar")
let url3 = URL(fileURLWithPath: "/home/user/noext")
XCTAssertEqual(File.basename(url3, suffix: ".*"), "noext")
}
// MARK: - dirname Tests
func testDirname() throws {
let url1 = URL(fileURLWithPath: "/home/user/file.txt")
XCTAssertEqual(File.dirname(url1).path, "/home/user")
let url2 = URL(fileURLWithPath: "/home/user/dir/")
XCTAssertEqual(File.dirname(url2).path, "/home/user")
let url3 = URL(fileURLWithPath: "/file.txt")
XCTAssertEqual(File.dirname(url3).path, "/")
let url4 = URL(fileURLWithPath: "file.txt")
XCTAssertEqual(File.dirname(url4).path, ".")
}
func testDirnameWithLevel() throws {
let url = URL(fileURLWithPath: "/home/user/dir/file.txt")
XCTAssertEqual(File.dirname(url, level: 1).path, "/home/user/dir")
XCTAssertEqual(File.dirname(url, level: 2).path, "/home/user")
XCTAssertEqual(File.dirname(url, level: 3).path, "/home")
XCTAssertEqual(File.dirname(url, level: 4).path, "/")
XCTAssertEqual(File.dirname(url, level: 5).path, "/") // Can't go beyond root
}
// MARK: - extname Tests
func testExtname() throws {
XCTAssertEqual(File.extname(URL(fileURLWithPath: "test.rb")), ".rb")
XCTAssertEqual(File.extname(URL(fileURLWithPath: "a/b/d/test.rb")), ".rb")
XCTAssertEqual(File.extname(URL(fileURLWithPath: ".a/b/d/test.rb")), ".rb")
XCTAssertEqual(File.extname(URL(fileURLWithPath: "test")), "")
XCTAssertEqual(File.extname(URL(fileURLWithPath: "test.tar.gz")), ".gz")
}
func testExtnameWithDotfile() throws {
XCTAssertEqual(File.extname(URL(fileURLWithPath: ".profile")), "")
XCTAssertEqual(File.extname(URL(fileURLWithPath: ".profile.sh")), ".sh")
XCTAssertEqual(File.extname(URL(fileURLWithPath: "/home/user/.bashrc")), "")
XCTAssertEqual(File.extname(URL(fileURLWithPath: "/home/user/.config.bak")), ".bak")
}
// MARK: - split Tests
func testSplit() throws {
// TODO: Implement
// File.split("/home/user/file.txt") => ("/home/user", "file.txt")
}
// MARK: - join Tests
func testJoin() throws {
// TODO: Implement
// File.join("usr", "mail", "gumby") => "usr/mail/gumby"
}
func testJoinWithArray() throws {
// TODO: Implement
}
// MARK: - absolutePath Tests
func testAbsolutePath() throws {
// TODO: Implement
// Converts relative to absolute
}
func testAbsolutePathWithBase() throws {
// TODO: Implement
}
// MARK: - expandPath Tests
func testExpandPath() throws {
// TODO: Implement
// File.expandPath("~") => home directory
// File.expandPath("~/Documents") => home/Documents
}
func testExpandPathWithRelative() throws {
// TODO: Implement
}
// MARK: - realpath Tests
func testRealpath() throws {
// TODO: Implement
// Resolves symlinks, all components must exist
}
func testRealpathThrowsForNonExistent() throws {
// TODO: Implement
}
// MARK: - realdirpath Tests
func testRealdirpath() throws {
// TODO: Implement
// Resolves symlinks, last component may not exist
}
func testRealdirpathWithNonExistentLast() throws {
// TODO: Implement
}
}

View file

@ -0,0 +1,166 @@
//
// FilePermissionTests.swift
// FileOtterTests
//
// Created by Sami Samhuri on 2025-08-19.
//
@testable import FileOtter
import XCTest
final class FilePermissionTests: XCTestCase {
var tempDir: URL!
var testFile: URL!
var readOnlyFile: URL!
var executableFile: URL!
override func setUpWithError() throws {
tempDir = URL.temporaryDirectory
.appendingPathComponent("FilePermissionTests-\(UUID().uuidString)")
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
testFile = tempDir.appendingPathComponent("test.txt")
try "Test content".write(to: testFile, atomically: true, encoding: .utf8)
readOnlyFile = tempDir.appendingPathComponent("readonly.txt")
try "Read only".write(to: readOnlyFile, atomically: true, encoding: .utf8)
executableFile = tempDir.appendingPathComponent("script.sh")
try "#!/bin/sh\necho hello".write(to: executableFile, atomically: true, encoding: .utf8)
}
override func tearDownWithError() throws {
if FileManager.default.fileExists(atPath: tempDir.path) {
try FileManager.default.removeItem(at: tempDir)
}
}
// MARK: - Basic Permission Tests
func testIsReadable() throws {
// TODO: Implement
// File.isReadable(url) returns true for readable files
}
func testIsWritable() throws {
// TODO: Implement
// File.isWritable(url) returns true for writable files
}
func testIsExecutable() throws {
// TODO: Implement
// File.isExecutable(url) returns true for executable files
}
func testIsExecutableForNonExecutable() throws {
// TODO: Implement
// File.isExecutable(url) returns false for non-executable
}
// MARK: - Ownership Tests
func testIsOwned() throws {
// TODO: Implement
// File.isOwned(url) returns true for files owned by effective user
}
func testIsGroupOwned() throws {
// TODO: Implement
// File.isGroupOwned(url) returns true for files owned by effective group
}
// MARK: - World Permission Tests
func testIsWorldReadable() throws {
// TODO: Implement
// File.isWorldReadable(url) returns permission bits if world readable
}
func testIsWorldReadableForPrivate() throws {
// TODO: Implement
// File.isWorldReadable(url) returns nil if not world readable
}
func testIsWorldWritable() throws {
// TODO: Implement
// File.isWorldWritable(url) returns permission bits if world writable
}
func testIsWorldWritableForProtected() throws {
// TODO: Implement
// File.isWorldWritable(url) returns nil if not world writable
}
// MARK: - Special Bit Tests
func testIsSetuid() throws {
// TODO: Implement
// File.isSetuid(url) returns true if setuid bit is set
}
func testIsSetgid() throws {
// TODO: Implement
// File.isSetgid(url) returns true if setgid bit is set
}
func testIsSticky() throws {
// TODO: Implement
// File.isSticky(url) returns true if sticky bit is set
}
// MARK: - chmod Tests
func testChmod() throws {
// TODO: Implement
// File.chmod(url, permissions) changes file permissions
}
func testChmodThrowsForNonExistent() throws {
// TODO: Implement
}
func testLchmod() throws {
// TODO: Implement
// File.lchmod(url, permissions) changes symlink permissions
}
func testInstanceChmod() throws {
// TODO: Implement
// file.chmod(permissions) changes open file permissions
}
// MARK: - chown Tests
func testChown() throws {
// TODO: Implement
// File.chown(url, owner, group) changes ownership
// Note: May require special privileges
}
func testChownWithNilValues() throws {
// TODO: Implement
// nil owner or group means don't change that value
}
func testLchown() throws {
// TODO: Implement
// File.lchown(url, owner, group) changes symlink ownership
}
func testInstanceChown() throws {
// TODO: Implement
// file.chown(owner, group) changes open file ownership
}
// MARK: - umask Tests
func testUmask() throws {
// TODO: Implement
// File.umask() returns current umask
}
func testUmaskSet() throws {
// TODO: Implement
// File.umask(mask) sets umask and returns previous value
}
}

View file

@ -0,0 +1,195 @@
//
// FileTypeTests.swift
// FileOtterTests
//
// Created by Sami Samhuri on 2025-08-19.
//
@testable import FileOtter
import XCTest
final class FileTypeTests: XCTestCase {
var tempDir: URL!
var testFile: URL!
var testDir: URL!
override func setUpWithError() throws {
tempDir = URL.temporaryDirectory
.appendingPathComponent("FileTypeTests-\(UUID().uuidString)")
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
testFile = tempDir.appendingPathComponent("test.txt")
try "Test content".write(to: testFile, atomically: true, encoding: .utf8)
testDir = tempDir.appendingPathComponent("subdir")
try FileManager.default.createDirectory(at: testDir, withIntermediateDirectories: true)
}
override func tearDownWithError() throws {
if FileManager.default.fileExists(atPath: tempDir.path) {
try FileManager.default.removeItem(at: tempDir)
}
}
// MARK: - Existence Tests
func testExists() throws {
// Test with existing file
XCTAssertTrue(File.exists(testFile))
// Test with existing directory
XCTAssertTrue(File.exists(testDir))
XCTAssertTrue(File.exists(tempDir))
}
func testExistsForNonExistent() throws {
let nonExistent = tempDir.appendingPathComponent("does-not-exist.txt")
XCTAssertFalse(File.exists(nonExistent))
let nonExistentDir = tempDir.appendingPathComponent("no-such-dir")
XCTAssertFalse(File.exists(nonExistentDir))
}
func testExistsForDirectory() throws {
// File.exists returns true for directories (like Ruby)
XCTAssertTrue(File.exists(tempDir))
XCTAssertTrue(File.exists(testDir))
// Also test system directories
XCTAssertTrue(File.exists(URL(fileURLWithPath: "/tmp")))
XCTAssertTrue(File.exists(URL(fileURLWithPath: "/")))
}
// MARK: - File Type Tests
func testIsFile() throws {
// Returns true for regular files
XCTAssertTrue(File.isFile(testFile))
// Create another test file
let anotherFile = tempDir.appendingPathComponent("another.txt")
try "content".write(to: anotherFile, atomically: true, encoding: .utf8)
XCTAssertTrue(File.isFile(anotherFile))
}
func testIsFileForDirectory() throws {
// Returns false for directories
XCTAssertFalse(File.isFile(testDir))
XCTAssertFalse(File.isFile(tempDir))
XCTAssertFalse(File.isFile(URL(fileURLWithPath: "/")))
// Returns false for non-existent paths
let nonExistent = tempDir.appendingPathComponent("no-such-file.txt")
XCTAssertFalse(File.isFile(nonExistent))
}
func testIsDirectory() throws {
// Returns true for directories
XCTAssertTrue(File.isDirectory(testDir))
XCTAssertTrue(File.isDirectory(tempDir))
XCTAssertTrue(File.isDirectory(URL(fileURLWithPath: "/")))
XCTAssertTrue(File.isDirectory(URL(fileURLWithPath: "/tmp")))
}
func testIsDirectoryForFile() throws {
// Returns false for files
XCTAssertFalse(File.isDirectory(testFile))
// Returns false for non-existent paths
let nonExistent = tempDir.appendingPathComponent("no-such-dir")
XCTAssertFalse(File.isDirectory(nonExistent))
}
func testIsSymlink() throws {
// TODO: Implement
// Create symlink and test File.isSymlink(url)
}
func testIsSymlinkForRegularFile() throws {
// TODO: Implement
// File.isSymlink(url) returns false for regular files
}
func testIsBlockDevice() throws {
// TODO: Implement
// File.isBlockDevice(url) - may need special test file
}
func testIsCharDevice() throws {
// TODO: Implement
// File.isCharDevice(url) - test with /dev/null or similar
}
func testIsPipe() throws {
// TODO: Implement
// File.isPipe(url) - create FIFO and test
}
func testIsSocket() throws {
// TODO: Implement
// File.isSocket(url) - create socket and test
}
// MARK: - Empty/Zero Tests
func testIsEmpty() throws {
// TODO: Implement
// File.isEmpty(url) returns true for empty file
}
func testIsEmptyForNonEmpty() throws {
// TODO: Implement
// File.isEmpty(url) returns false for non-empty file
}
func testIsEmptyThrowsForNonExistent() throws {
// TODO: Implement
}
func testIsZero() throws {
// TODO: Implement
// File.isZero(url) is alias for isEmpty
}
// MARK: - ftype Tests
func testFtypeForFile() throws {
// TODO: Implement
// File.ftype(url) returns .file
}
func testFtypeForDirectory() throws {
// TODO: Implement
// File.ftype(url) returns .directory
}
func testFtypeForSymlink() throws {
// TODO: Implement
// File.ftype(url) returns .link
}
func testFtypeForCharDevice() throws {
// TODO: Implement
// File.ftype(url) returns .characterSpecial
}
func testFtypeForBlockDevice() throws {
// TODO: Implement
// File.ftype(url) returns .blockSpecial
}
func testFtypeForFifo() throws {
// TODO: Implement
// File.ftype(url) returns .fifo
}
func testFtypeForSocket() throws {
// TODO: Implement
// File.ftype(url) returns .socket
}
func testFtypeForUnknown() throws {
// TODO: Implement
// File.ftype(url) returns .unknown for unrecognized types
}
}