|
| 1 | +// SPDX-FileCopyrightText: 2025 Zextras <https://www.zextras.com> |
| 2 | +// |
| 3 | +// SPDX-License-Identifier: GPL-2.0-only |
| 4 | + |
| 5 | +package com.zimbra.znative; |
| 6 | + |
| 7 | +import static org.junit.jupiter.api.Assertions.*; |
| 8 | + |
| 9 | +import java.io.FileNotFoundException; |
| 10 | +import java.io.IOException; |
| 11 | +import java.nio.file.Files; |
| 12 | +import java.nio.file.Path; |
| 13 | +import java.nio.file.attribute.PosixFilePermission; |
| 14 | +import org.junit.jupiter.api.Test; |
| 15 | +import org.junit.jupiter.api.io.TempDir; |
| 16 | + |
| 17 | +class IOTest { |
| 18 | + |
| 19 | + @TempDir Path tempDir; |
| 20 | + |
| 21 | + @Test |
| 22 | + void link_createsHardLink() throws IOException { |
| 23 | + Path src = tempDir.resolve("source.txt"); |
| 24 | + Files.writeString(src, "hello"); |
| 25 | + |
| 26 | + String destPath = tempDir.resolve("dest.txt").toString(); |
| 27 | + IO.link(src.toString(), destPath); |
| 28 | + |
| 29 | + assertEquals("hello", Files.readString(Path.of(destPath))); |
| 30 | + // Hard link means same inode |
| 31 | + assertEquals( |
| 32 | + Files.getAttribute(src, "unix:ino"), |
| 33 | + Files.getAttribute(Path.of(destPath), "unix:ino")); |
| 34 | + } |
| 35 | + |
| 36 | + @Test |
| 37 | + void link_throwsFileNotFoundForMissingSrc() { |
| 38 | + String missing = tempDir.resolve("nonexistent").toString(); |
| 39 | + String dest = tempDir.resolve("dest").toString(); |
| 40 | + |
| 41 | + assertThrows(FileNotFoundException.class, () -> IO.link(missing, dest)); |
| 42 | + } |
| 43 | + |
| 44 | + @Test |
| 45 | + void link_throwsIOExceptionForExistingDest() throws IOException { |
| 46 | + Path src = tempDir.resolve("src.txt"); |
| 47 | + Path dest = tempDir.resolve("dest.txt"); |
| 48 | + Files.writeString(src, "a"); |
| 49 | + Files.writeString(dest, "b"); |
| 50 | + |
| 51 | + assertThrows(IOException.class, () -> IO.link(src.toString(), dest.toString())); |
| 52 | + } |
| 53 | + |
| 54 | + @Test |
| 55 | + void fileInfo_returnsInodeSizeAndLinkCount() throws IOException { |
| 56 | + Path file = tempDir.resolve("test.txt"); |
| 57 | + Files.writeString(file, "content"); |
| 58 | + |
| 59 | + IO.FileInfo info = IO.fileInfo(file.toString()); |
| 60 | + |
| 61 | + assertNotNull(info); |
| 62 | + assertTrue(info.getInodeNum() > 0); |
| 63 | + assertEquals(7, info.getSize()); // "content" is 7 bytes |
| 64 | + assertEquals(1, info.getLinkCount()); |
| 65 | + } |
| 66 | + |
| 67 | + @Test |
| 68 | + void fileInfo_linkCountIncreasesAfterHardLink() throws IOException { |
| 69 | + Path file = tempDir.resolve("original.txt"); |
| 70 | + Files.writeString(file, "data"); |
| 71 | + |
| 72 | + IO.FileInfo before = IO.fileInfo(file.toString()); |
| 73 | + assertEquals(1, before.getLinkCount()); |
| 74 | + |
| 75 | + IO.link(file.toString(), tempDir.resolve("linked.txt").toString()); |
| 76 | + |
| 77 | + IO.FileInfo after = IO.fileInfo(file.toString()); |
| 78 | + assertEquals(2, after.getLinkCount()); |
| 79 | + assertEquals(before.getInodeNum(), after.getInodeNum()); |
| 80 | + } |
| 81 | + |
| 82 | + @Test |
| 83 | + void fileInfo_throwsFileNotFoundForMissingPath() { |
| 84 | + String missing = tempDir.resolve("nonexistent").toString(); |
| 85 | + assertThrows(FileNotFoundException.class, () -> IO.fileInfo(missing)); |
| 86 | + } |
| 87 | + |
| 88 | + @Test |
| 89 | + void linkCount_returnsCorrectCount() throws IOException { |
| 90 | + Path file = tempDir.resolve("file.txt"); |
| 91 | + Files.writeString(file, "x"); |
| 92 | + |
| 93 | + assertEquals(1, IO.linkCount(file.toString())); |
| 94 | + |
| 95 | + IO.link(file.toString(), tempDir.resolve("link1.txt").toString()); |
| 96 | + assertEquals(2, IO.linkCount(file.toString())); |
| 97 | + |
| 98 | + IO.link(file.toString(), tempDir.resolve("link2.txt").toString()); |
| 99 | + assertEquals(3, IO.linkCount(file.toString())); |
| 100 | + } |
| 101 | + |
| 102 | + @Test |
| 103 | + void chmod_setsPermissions() throws IOException { |
| 104 | + Path file = tempDir.resolve("perms.txt"); |
| 105 | + Files.writeString(file, "test"); |
| 106 | + |
| 107 | + IO.chmod(file.toString(), IO.S_IRUSR | IO.S_IWUSR); |
| 108 | + |
| 109 | + var perms = Files.getPosixFilePermissions(file); |
| 110 | + assertTrue(perms.contains(PosixFilePermission.OWNER_READ)); |
| 111 | + assertTrue(perms.contains(PosixFilePermission.OWNER_WRITE)); |
| 112 | + assertFalse(perms.contains(PosixFilePermission.OWNER_EXECUTE)); |
| 113 | + assertFalse(perms.contains(PosixFilePermission.GROUP_READ)); |
| 114 | + assertFalse(perms.contains(PosixFilePermission.OTHERS_READ)); |
| 115 | + } |
| 116 | + |
| 117 | + @Test |
| 118 | + void chmod_throwsFileNotFoundForMissingPath() { |
| 119 | + String missing = tempDir.resolve("nonexistent").toString(); |
| 120 | + assertThrows(FileNotFoundException.class, () -> IO.chmod(missing, IO.S_IRUSR)); |
| 121 | + } |
| 122 | + |
| 123 | + // setStdoutStderrTo() tests are intentionally excluded: |
| 124 | + // dup2() redirects stdout/stderr for the entire JVM process, which breaks |
| 125 | + // surefire's communication pipe and causes "forked VM terminated" errors. |
| 126 | + // This function is only used by MailboxServer output rotation and is tested |
| 127 | + // in production via systemd service startup. |
| 128 | + |
| 129 | + @Test |
| 130 | + void permissionConstants_haveCorrectValues() { |
| 131 | + assertEquals(0400, IO.S_IRUSR); |
| 132 | + assertEquals(0200, IO.S_IWUSR); |
| 133 | + assertEquals(0100, IO.S_IXUSR); |
| 134 | + assertEquals(0040, IO.S_IRGRP); |
| 135 | + assertEquals(0020, IO.S_IWGRP); |
| 136 | + assertEquals(0010, IO.S_IXGRP); |
| 137 | + assertEquals(0004, IO.S_IROTH); |
| 138 | + assertEquals(0002, IO.S_IWOTH); |
| 139 | + assertEquals(0001, IO.S_IXOTH); |
| 140 | + assertEquals(04000, IO.S_ISUID); |
| 141 | + assertEquals(02000, IO.S_ISGID); |
| 142 | + assertEquals(01000, IO.S_ISVTX); |
| 143 | + } |
| 144 | +} |
0 commit comments