FileOtter/FileOtterTests/FileInfoTests.swift

210 lines
6.7 KiB
Swift

//
// 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 {
let atime = try File.atime(testFile)
XCTAssertNotNil(atime)
// Access time should be recent (within last hour)
XCTAssertLessThan(Date().timeIntervalSince(atime), 3600)
}
func testMtime() throws {
// Get initial mtime
let initialMtime = try File.mtime(testFile)
// Wait a moment and modify the file
Thread.sleep(forTimeInterval: 0.1)
try "Modified content".write(to: testFile, atomically: true, encoding: .utf8)
// mtime should be updated
let newMtime = try File.mtime(testFile)
XCTAssertGreaterThan(newMtime, initialMtime)
}
func testCtime() throws {
let ctime = try File.ctime(testFile)
XCTAssertNotNil(ctime)
// Status change time should be recent
XCTAssertLessThan(Date().timeIntervalSince(ctime), 3600)
}
func testBirthtime() throws {
// Create a new file
let newFile = tempDir.appendingPathComponent("birthtime-test.txt")
let beforeCreation = Date()
Thread.sleep(forTimeInterval: 0.01)
try "content".write(to: newFile, atomically: true, encoding: .utf8)
Thread.sleep(forTimeInterval: 0.01)
let afterCreation = Date()
let birthtime = try File.birthtime(newFile)
XCTAssertGreaterThanOrEqual(birthtime, beforeCreation)
XCTAssertLessThanOrEqual(birthtime, afterCreation)
}
func testBirthtimeThrowsOnUnsupportedPlatform() throws {
// TODO: Implement if platform doesn't support birthtime
}
// MARK: - Size Tests
func testSize() throws {
// Test with known content
let content = "Test content"
let expectedSize = content.data(using: .utf8)!.count
XCTAssertEqual(try File.size(testFile), expectedSize)
// Test with larger file
let largeFile = tempDir.appendingPathComponent("large.txt")
let largeContent = String(repeating: "Hello World! ", count: 100)
try largeContent.write(to: largeFile, atomically: true, encoding: .utf8)
let largeExpectedSize = largeContent.data(using: .utf8)!.count
XCTAssertEqual(try File.size(largeFile), largeExpectedSize)
}
func testSizeThrowsForNonExistent() throws {
let nonExistent = tempDir.appendingPathComponent("no-such-file.txt")
XCTAssertThrowsError(try File.size(nonExistent))
}
func testSizeForEmptyFile() throws {
let emptyFile = tempDir.appendingPathComponent("empty.txt")
try "".write(to: emptyFile, atomically: true, encoding: .utf8)
XCTAssertEqual(try File.size(emptyFile), 0)
}
// MARK: - Stat Tests
func testStat() throws {
let stat = try File.fileStatus(testFile)
// Verify basic properties
XCTAssertGreaterThan(stat.ino, 0) // inode should be positive
XCTAssertGreaterThan(stat.uid, 0) // uid should be positive
XCTAssertGreaterThan(stat.gid, 0) // gid should be positive
XCTAssertEqual(stat.size, 12) // "Test content" is 12 bytes
// Verify times are reasonable
XCTAssertLessThan(Date().timeIntervalSince(stat.mtime), 3600)
XCTAssertLessThan(Date().timeIntervalSince(stat.atime), 3600)
XCTAssertNotNil(stat.birthtime)
}
func testStatThrowsForNonExistent() throws {
let nonExistent = tempDir.appendingPathComponent("nonexistent")
XCTAssertThrowsError(try File.fileStatus(nonExistent))
}
func testLstat() throws {
// For regular files, lstat should be same as stat
let lstat = try File.linkStatus(testFile)
let stat = try File.fileStatus(testFile)
XCTAssertEqual(lstat.size, stat.size)
XCTAssertEqual(lstat.ino, stat.ino)
XCTAssertEqual(lstat.mode, stat.mode)
}
func testLstatForSymlink() throws {
// Create a larger target file
let targetFile = tempDir.appendingPathComponent("target.txt")
let targetContent = "This is the target file content"
try targetContent.write(to: targetFile, atomically: true, encoding: .utf8)
// Create symlink
let symlinkURL = tempDir.appendingPathComponent("symlink.txt")
try FileManager.default.createSymbolicLink(at: symlinkURL, withDestinationURL: targetFile)
let lstat = try File.linkStatus(symlinkURL)
let stat = try File.fileStatus(symlinkURL)
// lstat should return symlink's own stats (smaller size)
// stat should follow the symlink to the target (larger size)
XCTAssertNotEqual(lstat.size, stat.size)
XCTAssertEqual(stat.size, Int64(targetContent.data(using: .utf8)!.count))
// lstat should indicate it's a symlink via mode
let isLink = (lstat.mode & 0o170000) == 0o120000 // S_IFLNK
XCTAssertTrue(isLink)
}
// 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
}
}