From c4ccf828ee5197f09b3306e151d7736c62cc025c Mon Sep 17 00:00:00 2001 From: Eric Ernst Date: Mon, 28 Jul 2025 22:04:46 -0700 Subject: [PATCH] ContainerizationEXT4: Add additional test coverage Signed-off-by: Eric Ernst --- .../TestEXT4Reader.swift | 140 ++++++++++++++++ .../TestEXT4ReaderExport.swift | 143 +++++++++++++++++ .../TestFilePathExtensions.swift | 151 ++++++++++++++++++ .../TestFileTimestamps.swift | 84 ++++++++++ .../TestIntegerExtensions.swift | 121 ++++++++++++++ .../TestUnsafeLittleEndianBytes.swift | 127 +++++++++++++++ 6 files changed, 766 insertions(+) create mode 100644 Tests/ContainerizationEXT4Tests/TestEXT4Reader.swift create mode 100644 Tests/ContainerizationEXT4Tests/TestEXT4ReaderExport.swift create mode 100644 Tests/ContainerizationEXT4Tests/TestFilePathExtensions.swift create mode 100644 Tests/ContainerizationEXT4Tests/TestFileTimestamps.swift create mode 100644 Tests/ContainerizationEXT4Tests/TestIntegerExtensions.swift create mode 100644 Tests/ContainerizationEXT4Tests/TestUnsafeLittleEndianBytes.swift diff --git a/Tests/ContainerizationEXT4Tests/TestEXT4Reader.swift b/Tests/ContainerizationEXT4Tests/TestEXT4Reader.swift new file mode 100644 index 00000000..42e72210 --- /dev/null +++ b/Tests/ContainerizationEXT4Tests/TestEXT4Reader.swift @@ -0,0 +1,140 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import Foundation +import SystemPackage +import Testing + +@testable import ContainerizationEXT4 + +struct TestEXT4Reader { + @Test func testEXT4ReaderInitWithNonExistentFile() { + let nonExistentPath = FilePath("/non/existent/path.ext4") + + #expect(throws: EXT4.Error.self) { + try EXT4.EXT4Reader(blockDevice: nonExistentPath) + } + } + + @Test func testEXT4ReaderBlockSize() throws { + let fsPath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString, isDirectory: false)) + defer { try? FileManager.default.removeItem(at: fsPath.url) } + + let formatter = try EXT4.Formatter(fsPath, minDiskSize: 32.kib()) + try formatter.close() + + let reader = try EXT4.EXT4Reader(blockDevice: fsPath) + let blockSize = reader.blockSize + + #expect(blockSize == UInt64(1024 * (1 << reader.superBlock.logBlockSize))) + } + + @Test func testEXT4ReaderSuperBlockAccess() throws { + let fsPath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString, isDirectory: false)) + defer { try? FileManager.default.removeItem(at: fsPath.url) } + + let formatter = try EXT4.Formatter(fsPath, minDiskSize: 32.kib()) + try formatter.close() + + let reader = try EXT4.EXT4Reader(blockDevice: fsPath) + let superBlock = reader.superBlock + + #expect(superBlock.magic == EXT4.SuperBlockMagic) + #expect(superBlock.logBlockSize >= 0) + #expect(superBlock.blocksCountLow > 0) + } + + @Test func testEXT4ReaderGetGroupDescriptor() throws { + let fsPath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString, isDirectory: false)) + defer { try? FileManager.default.removeItem(at: fsPath.url) } + + let formatter = try EXT4.Formatter(fsPath, minDiskSize: 32.kib()) + try formatter.close() + + let reader = try EXT4.EXT4Reader(blockDevice: fsPath) + let groupDescriptor = try reader.getGroupDescriptor(0) + + #expect(groupDescriptor.blockBitmapLow > 0) + #expect(groupDescriptor.inodeBitmapLow > 0) + #expect(groupDescriptor.inodeTableLow > 0) + } + + @Test func testEXT4ReaderGetInode() throws { + let fsPath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString, isDirectory: false)) + defer { try? FileManager.default.removeItem(at: fsPath.url) } + + let formatter = try EXT4.Formatter(fsPath, minDiskSize: 32.kib()) + try formatter.create(path: FilePath("/test_file"), mode: EXT4.Inode.Mode(.S_IFREG, 0o755), buf: nil) + try formatter.close() + + let reader = try EXT4.EXT4Reader(blockDevice: fsPath) + let rootInode = try reader.getInode(number: EXT4.RootInode) + + #expect(rootInode.mode.isDir()) + #expect(rootInode.linksCount > 0) + } + + @Test func testEXT4ReaderChildren() throws { + let fsPath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString, isDirectory: false)) + defer { try? FileManager.default.removeItem(at: fsPath.url) } + + let formatter = try EXT4.Formatter(fsPath, minDiskSize: 32.kib()) + try formatter.create(path: FilePath("/test_dir"), mode: EXT4.Inode.Mode(.S_IFDIR, 0o755)) + try formatter.create(path: FilePath("/test_file"), mode: EXT4.Inode.Mode(.S_IFREG, 0o644), buf: nil) + try formatter.close() + + let reader = try EXT4.EXT4Reader(blockDevice: fsPath) + let children = try reader.children(of: EXT4.RootInode) + + let childNames = children.map { $0.0 } + #expect(childNames.contains("test_dir")) + #expect(childNames.contains("test_file")) + #expect(childNames.contains("lost+found")) + } + + @Test func testEXT4ReaderInvalidInodeNumber() throws { + let fsPath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString, isDirectory: false)) + defer { try? FileManager.default.removeItem(at: fsPath.url) } + + let formatter = try EXT4.Formatter(fsPath, minDiskSize: 32.kib()) + try formatter.close() + + let reader = try EXT4.EXT4Reader(blockDevice: fsPath) + + // Test with an inode number that's beyond the filesystem's capacity + let maxInodeNumber = reader.superBlock.inodesCount + let invalidInodeNumber = maxInodeNumber + 1000 + + let inode = try reader.getInode(number: invalidInodeNumber) + + // The inode should be empty/unallocated (all zeros) + #expect(inode.mode == 0) + #expect(inode.linksCount == 0) + #expect(inode.sizeLow == 0) + } +} diff --git a/Tests/ContainerizationEXT4Tests/TestEXT4ReaderExport.swift b/Tests/ContainerizationEXT4Tests/TestEXT4ReaderExport.swift new file mode 100644 index 00000000..2a0b40d1 --- /dev/null +++ b/Tests/ContainerizationEXT4Tests/TestEXT4ReaderExport.swift @@ -0,0 +1,143 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +#if os(macOS) +import Foundation +import SystemPackage +import Testing + +@testable import ContainerizationEXT4 + +struct TestEXT4ReaderExport { + @Test func testEXT4ReaderExportBasic() throws { + let fsPath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString + ".ext4", isDirectory: false)) + let archivePath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString + ".tar", isDirectory: false)) + + defer { + try? FileManager.default.removeItem(at: fsPath.url) + try? FileManager.default.removeItem(at: archivePath.url) + } + + let formatter = try EXT4.Formatter(fsPath, minDiskSize: 32.kib()) + try formatter.create(path: FilePath("/test_dir"), mode: EXT4.Inode.Mode(.S_IFDIR, 0o755)) + try formatter.create(path: FilePath("/test_file"), mode: EXT4.Inode.Mode(.S_IFREG, 0o644), buf: nil) + try formatter.create(path: FilePath("/test_symlink"), link: FilePath("test_file"), mode: EXT4.Inode.Mode(.S_IFLNK, 0o777)) + try formatter.close() + + let reader = try EXT4.EXT4Reader(blockDevice: fsPath) + try reader.export(archive: archivePath) + + #expect(FileManager.default.fileExists(atPath: archivePath.description)) + + let archiveSize = try FileManager.default.attributesOfItem(atPath: archivePath.description)[.size] as? Int64 ?? 0 + #expect(archiveSize > 0) + } + + @Test func testEXT4ReaderExportWithContent() throws { + let fsPath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString + ".ext4", isDirectory: false)) + let archivePath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString + ".tar", isDirectory: false)) + + defer { + try? FileManager.default.removeItem(at: fsPath.url) + try? FileManager.default.removeItem(at: archivePath.url) + } + + let formatter = try EXT4.Formatter(fsPath, minDiskSize: 32.kib()) + + let testContent = "Hello, World!" + let inputStream = InputStream(data: testContent.data(using: .utf8)!) + inputStream.open() + defer { inputStream.close() } + + try formatter.create(path: FilePath("/test_file"), mode: EXT4.Inode.Mode(.S_IFREG, 0o644), buf: inputStream) + try formatter.close() + + let reader = try EXT4.EXT4Reader(blockDevice: fsPath) + try reader.export(archive: archivePath) + + #expect(FileManager.default.fileExists(atPath: archivePath.description)) + + let archiveSize = try FileManager.default.attributesOfItem(atPath: archivePath.description)[.size] as? Int64 ?? 0 + #expect(archiveSize > 0) + } + + @Test func testEXT4ReaderExportWithHardlinks() throws { + let fsPath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString + ".ext4", isDirectory: false)) + let archivePath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString + ".tar", isDirectory: false)) + + defer { + try? FileManager.default.removeItem(at: fsPath.url) + try? FileManager.default.removeItem(at: archivePath.url) + } + + let formatter = try EXT4.Formatter(fsPath, minDiskSize: 32.kib()) + + let testContent = "Hardlink content" + let inputStream = InputStream(data: testContent.data(using: .utf8)!) + inputStream.open() + defer { inputStream.close() } + + try formatter.create(path: FilePath("/original_file"), mode: EXT4.Inode.Mode(.S_IFREG, 0o644), buf: inputStream) + try formatter.link(link: FilePath("/hardlink_file"), target: FilePath("/original_file")) + try formatter.close() + + let reader = try EXT4.EXT4Reader(blockDevice: fsPath) + try reader.export(archive: archivePath) + + #expect(FileManager.default.fileExists(atPath: archivePath.description)) + + let archiveSize = try FileManager.default.attributesOfItem(atPath: archivePath.description)[.size] as? Int64 ?? 0 + #expect(archiveSize > 0) + } + + @Test func testEXT4ReaderExportEmptyFilesystem() throws { + let fsPath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString + ".ext4", isDirectory: false)) + let archivePath = FilePath( + FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString + ".tar", isDirectory: false)) + + defer { + try? FileManager.default.removeItem(at: fsPath.url) + try? FileManager.default.removeItem(at: archivePath.url) + } + + let formatter = try EXT4.Formatter(fsPath, minDiskSize: 32.kib()) + try formatter.close() + + let reader = try EXT4.EXT4Reader(blockDevice: fsPath) + try reader.export(archive: archivePath) + + #expect(FileManager.default.fileExists(atPath: archivePath.description)) + + let archiveSize = try FileManager.default.attributesOfItem(atPath: archivePath.description)[.size] as? Int64 ?? 0 + #expect(archiveSize > 0) + } +} +#endif diff --git a/Tests/ContainerizationEXT4Tests/TestFilePathExtensions.swift b/Tests/ContainerizationEXT4Tests/TestFilePathExtensions.swift new file mode 100644 index 00000000..55b5bf02 --- /dev/null +++ b/Tests/ContainerizationEXT4Tests/TestFilePathExtensions.swift @@ -0,0 +1,151 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import Foundation +import SystemPackage +import Testing + +@testable import ContainerizationEXT4 + +struct TestFilePathExtensions { + @Test func testFilePathBytes() { + let path = FilePath("/test/path") + let bytes = path.bytes + let expected = "/test/path".utf8.map { UInt8($0) } + #expect(bytes == expected) + } + + @Test func testFilePathBase() { + let path1 = FilePath("/test/path/file.txt") + #expect(path1.base == "file.txt") + + let path2 = FilePath("/") + #expect(path2.base == "/") + + let path3 = FilePath("/single") + #expect(path3.base == "single") + } + + @Test func testFilePathDir() { + let path = FilePath("/test/path/file.txt") + #expect(path.dir == FilePath("/test/path")) + + let rootPath = FilePath("/") + #expect(rootPath.dir == FilePath("/")) + } + + @Test func testFilePathURL() { + let path = FilePath("/test/path") + let url = path.url + #expect(url == URL(fileURLWithPath: "/test/path")) + } + + @Test func testFilePathItems() { + let path = FilePath("/test/path/file.txt") + let items = path.items + #expect(items == ["test", "path", "file.txt"]) + + let rootPath = FilePath("/") + #expect(rootPath.items == []) + } + + @Test func testFilePathInitFromURL() { + let url = URL(fileURLWithPath: "/test/path") + let path = FilePath(url) + #expect(path == FilePath("/test/path")) + } + + @Test func testFilePathInitFromData() { + let data = "/test/path\0".data(using: .utf8)! + let path = FilePath(data) + #expect(path == FilePath("/test/path")) + + // Test with empty data - creates empty path, not nil + let emptyData = Data() + let emptyPath = FilePath(emptyData) + #expect(emptyPath == FilePath("")) + + // Test with null terminator only - should create empty path + let nullData = Data([0x00]) + let nullPath = FilePath(nullData) + #expect(nullPath == FilePath("")) + } + + @Test func testFilePathJoin() { + let base = FilePath("/test") + let joined1 = base.join(FilePath("path")) + #expect(joined1 == FilePath("/test/path")) + + let joined2 = base.join("file.txt") + #expect(joined2 == FilePath("/test/file.txt")) + } + + @Test func testFilePathSplit() { + let path = FilePath("/test/path/file.txt") + let (dir, base) = path.split() + #expect(dir == FilePath("/test/path")) + #expect(base == "file.txt") + } + + @Test func testFilePathClean() { + let path = FilePath("/test/../test/./path") + let cleaned = path.clean() + #expect(cleaned == FilePath("/test/path")) + } + + @Test func testFilePathRel() { + let base = "/test/common/base" + let target = "/test/common/target/file.txt" + let rel = FilePath.rel(base, target) + #expect(rel == FilePath("../target/file.txt")) + + let sameBase = "/test/path" + let sameTarget = "/test/path" + let sameRel = FilePath.rel(sameBase, sameTarget) + #expect(sameRel == FilePath(".")) + + let differentBase = "/completely/different/path" + let differentTarget = "/other/path" + let differentRel = FilePath.rel(differentBase, differentTarget) + #expect(differentRel == FilePath("../../../other/path")) + } + + @Test func testFileHandleExtensions() { + let tempDir = FileManager.default.temporaryDirectory + let testFile = tempDir.appendingPathComponent("test_file.txt") + let testPath = FilePath(testFile.path) + + // Create a test file + try! "test content".write(to: testFile, atomically: true, encoding: .utf8) + defer { try? FileManager.default.removeItem(at: testFile) } + + // Test reading + let readHandle = FileHandle(forReadingFrom: testPath) + #expect(readHandle != nil) + + let readHandle2 = FileHandle(forReadingAtPath: testPath) + #expect(readHandle2 != nil) + + // Test writing + let writeHandle = FileHandle(forWritingTo: testPath) + #expect(writeHandle != nil) + + // Test non-existent file + let nonExistentPath = FilePath("/non/existent/path") + let nonExistentHandle = FileHandle(forReadingFrom: nonExistentPath) + #expect(nonExistentHandle == nil) + } +} diff --git a/Tests/ContainerizationEXT4Tests/TestFileTimestamps.swift b/Tests/ContainerizationEXT4Tests/TestFileTimestamps.swift new file mode 100644 index 00000000..cc59586a --- /dev/null +++ b/Tests/ContainerizationEXT4Tests/TestFileTimestamps.swift @@ -0,0 +1,84 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import Foundation +import Testing + +@testable import ContainerizationEXT4 + +struct TestFileTimestamps { + @Test func testFileTimestampsInit() { + let now = Date() + let access = now.addingTimeInterval(-3600) // 1 hour ago + let modification = now.addingTimeInterval(-1800) // 30 minutes ago + let creation = now.addingTimeInterval(-7200) // 2 hours ago + + let timestamps = FileTimestamps(access: access, modification: modification, creation: creation) + + #expect(timestamps.access == access) + #expect(timestamps.modification == modification) + #expect(timestamps.creation == creation) + #expect(timestamps.now.timeIntervalSince1970 >= now.timeIntervalSince1970) + } + + @Test func testFileTimestampsDefaultInit() { + let timestamps = FileTimestamps() + let now = Date() + + #expect(timestamps.access.timeIntervalSince1970 >= now.timeIntervalSince1970 - 1) + #expect(timestamps.modification.timeIntervalSince1970 >= now.timeIntervalSince1970 - 1) + #expect(timestamps.creation.timeIntervalSince1970 >= now.timeIntervalSince1970 - 1) + #expect(timestamps.now.timeIntervalSince1970 >= now.timeIntervalSince1970 - 1) + } + + @Test func testFileTimestampsWithNilValues() { + let timestamps = FileTimestamps(access: nil, modification: nil, creation: nil) + let now = Date() + + #expect(timestamps.access.timeIntervalSince1970 >= now.timeIntervalSince1970 - 1) + #expect(timestamps.modification.timeIntervalSince1970 >= now.timeIntervalSince1970 - 1) + #expect(timestamps.creation.timeIntervalSince1970 >= now.timeIntervalSince1970 - 1) + } + + @Test func testFileTimestampsLoHiAccessors() { + let timestamp = Date(timeIntervalSince1970: 1234567890.123456) + let timestamps = FileTimestamps(access: timestamp, modification: timestamp, creation: timestamp) + + let fsTime = timestamp.fs() + + #expect(timestamps.accessLo == fsTime.lo) + #expect(timestamps.accessHi == fsTime.hi) + #expect(timestamps.modificationLo == fsTime.lo) + #expect(timestamps.modificationHi == fsTime.hi) + #expect(timestamps.creationLo == fsTime.lo) + #expect(timestamps.creationHi == fsTime.hi) + + let nowFsTime = timestamps.now.fs() + #expect(timestamps.nowLo == nowFsTime.lo) + #expect(timestamps.nowHi == nowFsTime.hi) + } + + @Test func testFileTimestampsPartialInit() { + let access = Date(timeIntervalSince1970: 1000) + let modification = Date(timeIntervalSince1970: 2000) + + let timestamps = FileTimestamps(access: access, modification: modification, creation: nil) + + #expect(timestamps.access == access) + #expect(timestamps.modification == modification) + #expect(timestamps.creation.timeIntervalSince1970 >= timestamps.now.timeIntervalSince1970 - 1) + } +} diff --git a/Tests/ContainerizationEXT4Tests/TestIntegerExtensions.swift b/Tests/ContainerizationEXT4Tests/TestIntegerExtensions.swift new file mode 100644 index 00000000..977dd6db --- /dev/null +++ b/Tests/ContainerizationEXT4Tests/TestIntegerExtensions.swift @@ -0,0 +1,121 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import Foundation +import Testing + +@testable import ContainerizationEXT4 + +struct TestIntegerExtensions { + @Test func testUInt64Extensions() { + let value: UInt64 = 0x1234_5678_abcd_ef01 + + #expect(value.lo == 0xabcd_ef01) + #expect(value.hi == 0x1234_5678) + + let result1 = value - UInt32(100) + #expect(result1 == value - 100) + + let result2 = value % UInt32(256) + #expect(result2 == value % 256) + + let result3 = value / UInt32(256) + #expect(result3 == UInt32((value / 256).lo)) + + let result4 = value * UInt32(2) + #expect(result4 == value * 2) + + let result5 = value * Int(3) + #expect(result5 == value * 3) + } + + @Test func testUInt32Extensions() { + let value: UInt32 = 0x1234_5678 + + #expect(value.lo == 0x5678) + #expect(value.hi == 0x1234) + + let result1 = value + 100 + #expect(result1 == value + UInt32(100)) + + let result2 = value - 50 + #expect(result2 == value - UInt32(50)) + + let result3 = value / 4 + #expect(result3 == value / UInt32(4)) + + let result4 = value - UInt16(10) + #expect(result4 == value - UInt32(10)) + + let result5 = value * 2 + #expect(result5 == Int(value) * 2) + } + + @Test func testIntExtensions() { + let value: Int = 100 + + let result1: Int = value + UInt32(50) + #expect(result1 == 150) + + let result2: UInt32 = value + UInt32(25) + #expect(result2 == 125) + } + + @Test func testUInt16FileModeExtensions() { + let dirMode: UInt16 = EXT4.FileModeFlag.S_IFDIR.rawValue | 0o755 + let regMode: UInt16 = EXT4.FileModeFlag.S_IFREG.rawValue | 0o644 + let linkMode: UInt16 = EXT4.FileModeFlag.S_IFLNK.rawValue | 0o777 + let chrMode: UInt16 = EXT4.FileModeFlag.S_IFCHR.rawValue | 0o600 + let blkMode: UInt16 = EXT4.FileModeFlag.S_IFBLK.rawValue | 0o600 + let fifoMode: UInt16 = EXT4.FileModeFlag.S_IFIFO.rawValue | 0o644 + let sockMode: UInt16 = EXT4.FileModeFlag.S_IFSOCK.rawValue | 0o644 + let unknownMode: UInt16 = 0o755 + + #expect(dirMode.isDir()) + #expect(!dirMode.isReg()) + #expect(!dirMode.isLink()) + + #expect(regMode.isReg()) + #expect(!regMode.isDir()) + #expect(!regMode.isLink()) + + #expect(linkMode.isLink()) + #expect(!linkMode.isDir()) + #expect(!linkMode.isReg()) + + #expect(dirMode.fileType() == EXT4.FileType.directory.rawValue) + #expect(regMode.fileType() == EXT4.FileType.regular.rawValue) + #expect(linkMode.fileType() == EXT4.FileType.symbolicLink.rawValue) + #expect(chrMode.fileType() == EXT4.FileType.character.rawValue) + #expect(blkMode.fileType() == EXT4.FileType.block.rawValue) + #expect(fifoMode.fileType() == EXT4.FileType.fifo.rawValue) + #expect(sockMode.fileType() == EXT4.FileType.socket.rawValue) + #expect(unknownMode.fileType() == EXT4.FileType.unknown.rawValue) + } + + @Test func testUInt8ArrayExtensions() { + let allZeros: [UInt8] = [0, 0, 0, 0] + let notAllZeros: [UInt8] = [0, 1, 0, 0] + let noZeros: [UInt8] = [1, 2, 3, 4] + + #expect(allZeros.allZeros) + #expect(!notAllZeros.allZeros) + #expect(!noZeros.allZeros) + + let empty: [UInt8] = [] + #expect(empty.allZeros) + } +} diff --git a/Tests/ContainerizationEXT4Tests/TestUnsafeLittleEndianBytes.swift b/Tests/ContainerizationEXT4Tests/TestUnsafeLittleEndianBytes.swift new file mode 100644 index 00000000..9e9f8424 --- /dev/null +++ b/Tests/ContainerizationEXT4Tests/TestUnsafeLittleEndianBytes.swift @@ -0,0 +1,127 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import Foundation +import Testing + +@testable import ContainerizationEXT4 + +struct TestUnsafeLittleEndianBytes { + @Test func testWithUnsafeLittleEndianBytes() { + let value: UInt32 = 0x1234_5678 + + let result = withUnsafeLittleEndianBytes(of: value) { bytes in + bytes.count + } + + #expect(result == MemoryLayout.size) + } + + @Test func testWithUnsafeLittleEndianBytesThrows() { + let value: UInt16 = 0x1234 + + do { + let _ = try withUnsafeLittleEndianBytes(of: value) { bytes -> Int in + throw NSError(domain: "TestError", code: 1) + } + #expect(Bool(false), "Should have thrown an error") + } catch { + #expect(error.localizedDescription.contains("TestError")) + } + } + + @Test func testWithUnsafeLittleEndianBuffer() { + let data: [UInt8] = [0x78, 0x56, 0x34, 0x12] + let result = data.withUnsafeBytes { buffer in + withUnsafeLittleEndianBuffer(of: buffer) { buf in + buf.count + } + } + + #expect(result == 4) + } + + @Test func testWithUnsafeLittleEndianBufferThrows() { + let data: [UInt8] = [0x12, 0x34] + + do { + let _ = try data.withUnsafeBytes { buffer in + try withUnsafeLittleEndianBuffer(of: buffer) { buf -> Int in + throw NSError(domain: "TestError", code: 1) + } + } + #expect(Bool(false), "Should have thrown an error") + } catch { + #expect(error.localizedDescription.contains("TestError")) + } + } + + @Test func testUnsafeRawBufferPointerLoadLittleEndian() { + let value: UInt32 = 0x1234_5678 + let data: [UInt8] = [0x78, 0x56, 0x34, 0x12] // Little endian representation + + let result = data.withUnsafeBytes { buffer in + buffer.loadLittleEndian(as: UInt32.self) + } + + #expect(result == value) + } + + @Test func testUnsafeRawBufferPointerLoadLittleEndianDifferentTypes() { + let data16: [UInt8] = [0x34, 0x12] // Little endian 0x1234 + let result16 = data16.withUnsafeBytes { buffer in + buffer.loadLittleEndian(as: UInt16.self) + } + #expect(result16 == 0x1234) + + let data64: [UInt8] = [0x78, 0x56, 0x34, 0x12, 0xBC, 0x9A, 0x78, 0x56] // Little endian + let result64 = data64.withUnsafeBytes { buffer in + buffer.loadLittleEndian(as: UInt64.self) + } + #expect(result64 == 0x5678_9ABC_1234_5678) + } + + @Test func testEndianness() { + let currentEndian = Endian + #expect(currentEndian == .little || currentEndian == .big) + + // On Apple Silicon and Intel Macs, we expect little endian + #expect(currentEndian == .little) + } + + @Test func testEndiannessConsistency() { + // Test that our endianness detection is consistent + let endian1 = Endian + let endian2 = Endian + #expect(endian1 == endian2) + } + + @Test func testWithUnsafeLittleEndianBytesLittleEndian() { + // Test behavior specifically on little endian systems + let value: UInt32 = 0x1234_5678 + let expected: [UInt8] = [0x78, 0x56, 0x34, 0x12] + + let result = withUnsafeLittleEndianBytes(of: value) { bytes in + Array(bytes) + } + + if Endian == .little { + #expect(result == expected) + } else { + #expect(result == expected.reversed()) + } + } +}