package peergos.server.tests; import org.junit.*; import static org.junit.Assert.*; import static java.util.UUID.*; import peergos.shared.*; import peergos.shared.crypto.*; import peergos.server.fuse.*; import peergos.server.Start; import peergos.shared.user.*; import peergos.shared.util.*; import java.io.*; import java.lang.*; import java.lang.reflect.*; import java.nio.file.*; import java.nio.file.attribute.FileTime; import java.time.ZonedDateTime; import java.util.*; import java.util.stream.*; public class FuseTests { public static int WEB_PORT = 8888; public static int CORE_PORT = 7777; public static String username = "test02"; public static String password = username; public static Path mountPoint, home; public static FuseProcess fuseProcess; public static Random RANDOM = new Random(666); public static void setWebPort(int webPort) { WEB_PORT = webPort; } public static void setCorePort(int corePort) { CORE_PORT = corePort; } static void setFinalStatic(Field field, Object newValue) throws Exception { field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, newValue); } @BeforeClass public static void init() throws Exception { Random random = new Random(); int offset = random.nextInt(100); // int offset = 0; setWebPort(8888 + offset); setCorePort(7777 + offset); System.out.println("Using web-port "+ WEB_PORT); System.out.flush(); // use insecure random otherwise tests take ages setFinalStatic(TweetNaCl.class.getDeclaredField("prng"), new Random(1)); Args args = Args.parse(new String[]{"useIPFS", "false", "-port", Integer.toString(WEB_PORT), "-corenodePort", Integer.toString(CORE_PORT)}); Start.local(args); NetworkAccess network = NetworkAccess.buildJava(WEB_PORT).get(); UserContext userContext = UserContext.ensureSignedUp(username, password, network, Crypto.initJava()).get(); String mountPath = args.getArg("mountPoint", "/tmp/peergos/tmp"); mountPoint = Paths.get(mountPath); mountPoint = mountPoint.resolve(UUID.randomUUID().toString()); mountPoint.toFile().mkdirs(); home = mountPoint.resolve(username); System.out.println("\n\nMountpoint "+ mountPoint +"\n\n"); // PeergosFS peergosFS = new PeergosFS(userContext); PeergosFS peergosFS = new CachingPeergosFS(userContext); fuseProcess = new FuseProcess(peergosFS, mountPoint); Runtime.getRuntime().addShutdownHook(new Thread(() -> fuseProcess.close())); fuseProcess.start(); } public static String readStdout(Process p) throws IOException { return new String(Serialize.readFully(p.getInputStream())).trim(); } @Test public void createFileTest() throws IOException { Path resolve = home.resolve(UUID.randomUUID().toString()); assertFalse("file already exists", resolve.toFile().exists()); resolve.toFile().createNewFile(); assertTrue("file exists after creation", resolve.toFile().exists()); } @Test public void moveTest() throws IOException { Path initial = createRandomFile(0x1000); byte[] initialData = Files.readAllBytes(initial); String[] stem = Stream.generate(() -> randomUUID().toString()) .limit(2) .toArray(String[]::new); Path targetDir = Paths.get(initial.getParent().toString(), stem); targetDir.toFile().mkdirs(); assertTrue("target dir exists", targetDir.toFile().isDirectory()); Path target = targetDir.resolve(randomUUID().toString()); assertFalse("target exists before move", target.toFile().exists()); Files.move(initial, target); assertFalse("initial still exists", initial.toFile().exists()); assertTrue("target exists after move", target.toFile().exists()); byte[] targetData = Files.readAllBytes(target); assertTrue("target contents equal to initial contents", Arrays.equals(initialData, targetData)); } @Test public void copyFileTest() throws IOException { Path initial = createRandomFile(1024*1024*10); Path target = initial.getParent().resolve(randomUUID().toString()); assertFalse("target exists", target.toFile().exists()); Files.copy(initial, target); assertTrue("initial exists", initial.toFile().exists()); assertTrue("target exists", target.toFile().exists()); byte[] original = Files.readAllBytes(initial); byte[] copy = Files.readAllBytes(target); boolean contentEquals = Arrays.equals(original, copy); int firstDifferentIndex = firstDifferentindex(original, copy, 0); int lastDifferentIndex = lastDifferentindex(original, copy, original.length); byte[] diff = firstDifferentIndex > 0 ? Arrays.copyOfRange(copy, firstDifferentIndex, lastDifferentIndex) : new byte[0]; assertTrue("initial and target contents equal", contentEquals); } @Test public void copyFileFromHostTest() throws IOException { Path initial = Files.createTempFile(UUID.randomUUID().toString(), "rw"); byte[] data = new byte[6*1024*1024]; new Random(0).nextBytes(data); Files.write(initial, data); Path target = home.resolve(randomUUID().toString()); assertFalse("target exists", target.toFile().exists()); Files.copy(initial, target); assertTrue("initial exists", initial.toFile().exists()); assertTrue("target exists", target.toFile().exists()); boolean contentEquals = Arrays.equals( Files.readAllBytes(initial), Files.readAllBytes(target)); assertTrue("initial and target contents equal", contentEquals); } @Test public void randomReadTest() throws IOException { Path initial = Files.createTempFile(UUID.randomUUID().toString(), "rw"); byte[] data = new byte[6*1024*1024]; new Random(0).nextBytes(data); Files.write(initial, data); Path target = home.resolve(randomUUID().toString()); assertFalse("target exists", target.toFile().exists()); Files.copy(initial, target); Random r = new Random(0); for (int i=0; i < 20; i++) { int size = r.nextInt(100*1024); int offset = r.nextInt(data.length - size); byte[] original = readBytes(initial, offset, size); byte[] copy = readBytes(target, offset, size); boolean equal = Arrays.equals(original, copy); Assert.assertTrue("Same contents from " + offset, equal); } } private byte[] readBytes(Path file, long offset, int size) throws IOException { try (RandomAccessFile raf = new RandomAccessFile(file.toFile(), "r")) { raf.seek(offset); byte[] res = new byte[size]; int read = raf.read(res); if (read != size) throw new IllegalStateException("Only read " + read + " not " + size); return res; } } @Test public void removeTest() throws IOException { Path path = createRandomFile(); assertTrue("path exists before delete", path.toFile().exists()); Files.delete(path); assertFalse("path exists after delete", path.toFile().exists()); } @Test public void writePastEnd() throws IOException { int length = 10 * 1024; Path path = createRandomFile(length); byte[] initial = Files.readAllBytes(path); RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rw"); raf.seek(2*length); byte[] tmp = new byte[length]; Random rnd = new Random(666); rnd.nextBytes(tmp); raf.write(tmp); raf.close(); byte[] expected = Arrays.copyOfRange(initial, 0, 3*length); System.arraycopy(tmp, 0, expected, 2*length, length); byte[] extendedContents = Files.readAllBytes(path); assertTrue("Correct contents", Arrays.equals(expected, extendedContents)); } @Test public void truncateTest() throws IOException { int initialLength = 0x1000; Path path = createRandomFile(initialLength); try (RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rw")) { assertEquals("initial size", initialLength, raf.length()); for (int pow = -1; pow < 4; pow++) { int newSize = (int) (Math.pow(2, pow) * initialLength); raf.setLength(newSize); assertEquals("truncated size equals", newSize, raf.length()); } } } @Test public void lastModifiedTimeTest() throws IOException { Path path = createRandomFile(); ZonedDateTime now = ZonedDateTime.now(); ZonedDateTime limit = now.minusMonths(1); for (ZonedDateTime zdt = now; zdt.isAfter(limit); zdt = zdt.minusDays(1)) { FileTime fileTime = FileTime.from(zdt.toInstant()); Path path1 = Files.setLastModifiedTime(path, fileTime); FileTime found = Files.getLastModifiedTime(path); assertEquals("get(set(time)) = time for time = "+ zdt, fileTime.toMillis() / 1000, found.toMillis() / 1000); } } @Test public void mkdirsTest() throws IOException { String[] stem = Stream.generate(() -> randomUUID().toString()) .limit(10) .toArray(String[]::new); Path path = Paths.get(home.toString(), stem); assertFalse("path exists initially", path.toFile().exists()); path.toFile().mkdirs(); assertTrue("path is directory", path.toFile().isDirectory()); } @Test public void rmdirTest() throws IOException { Path path = home .resolve(randomUUID().toString()) .resolve(randomUUID().toString()); assertFalse("dir exists initially", path.toFile().exists()); path.toFile().mkdirs(); assertTrue("dir exists after creation", path.toFile().exists()); path.toFile().delete(); assertFalse("dir exists after deletion", path.toFile().exists()); } private Path createRandomFile() throws IOException { return createRandomFile(0); } private Path createRandomFile(int length) throws IOException { Path resolve = home.resolve(UUID.randomUUID().toString()); resolve.toFile().createNewFile(); if (length > 0) { byte[] data = new byte[length]; RANDOM.nextBytes(data); Files.write(resolve, data); } return resolve; } private void fileTest(int length, Random random) throws IOException { byte[] data = new byte[length]; random.nextBytes(data); String filename = randomUUID().toString(); Path path = home.resolve(filename); Files.write(path, data); byte[] contents = Files.readAllBytes(path); boolean equals = Arrays.equals(data, contents); String diff = equals ? "" : "Different at index " + firstDifferentindex(data, contents, 0); Assert.assertTrue("Correct file contents: length("+ contents.length +") expected("+length+") "+ diff, equals); } public static int lastDifferentindex(byte[] src, byte[] target, int start) { for (int i=start-1; i >= 0; i--) { if (i >= target.length) return i; if (src[i] != target[i]) return i; } return -1; } public static int firstDifferentindex(byte[] src, byte[] target, int start) { for (int i=start; i < src.length; i++) { if (i >= target.length) return i; if (src[i] != target[i]) return i; } return -1; } @Test public void readWriteTest() throws IOException { Random random = new Random(3); // repeatable with same seed 3 leads to failure with bulk upload at size of 137 for (int power = 5; power < 20; power++) { int length = (int) Math.pow(2, power); length += random.nextInt(length); fileTest(length, random); } } @AfterClass public static void shutdown() { if (fuseProcess != null) fuseProcess.close(); } }