/* * Copyright (c) 2011-2015 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.core.impl.launcher.commands; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; /** * Test the watching service behavior. * * @author <a href="http://escoffier.me">Clement Escoffier</a> */ public class WatcherTest extends CommandTestBase { // Note about sleep time - the watcher is configured with a short scan period to avoid that this tests take too // much time. protected Watcher watcher; protected AtomicInteger deploy; protected AtomicInteger undeploy; protected File root; @Before public void prepare() { root = new File("target/junk/watcher"); deleteRecursive(root); root.mkdirs(); deploy = new AtomicInteger(); undeploy = new AtomicInteger(); watcher = new Watcher(root, Collections.unmodifiableList( Arrays.asList("**" + File.separator + "*.txt", "windows\\*.win", "unix/*.nix", "FOO.bar")), next -> { deploy.incrementAndGet(); if (next != null) { next.handle(null); } }, next -> { undeploy.incrementAndGet(); if (next != null) { next.handle(null); } }, null, 10, 10); } @After public void close() { watcher.close(); } @Test public void testFileAddition() throws IOException { watcher.watch(); // Initial deployment waitUntil(() -> deploy.get() == 1); File file = new File(root, "foo.txt"); file.createNewFile(); // undeployment followed by redeployment waitUntil(() -> undeploy.get() == 1 && deploy.get() == 2); } @Test public void testWithANonMatchingFile() throws IOException, InterruptedException { watcher.watch(); // Initial deployment waitUntil(() -> deploy.get() == 1); File file = new File(root, "foo.nope"); file.createNewFile(); Thread.sleep(500); assertThat(undeploy.get()).isEqualTo(0); assertThat(deploy.get()).isEqualTo(1); } @Test public void testFileModification() throws IOException, InterruptedException { File file = new File(root, "foo.txt"); watcher.watch(); // Initial deployment waitUntil(() -> deploy.get() == 1); file.createNewFile(); waitUntil(() -> deploy.get() == 2); // Simulate a 'touch', we wait more than a second to avoid being in the same second as the creation. This is because // some FS return the last modified date with a second of precision. Thread.sleep(1500); file.setLastModified(System.currentTimeMillis()); // undeployment followed by redeployment waitUntil(() -> undeploy.get() == 2 && deploy.get() == 3); } @Test public void testFileDeletion() throws IOException, InterruptedException { File file = new File(root, "foo.txt"); file.createNewFile(); watcher.watch(); // Initial deployment waitUntil(() -> deploy.get() == 1); // Wait until the file monitoring is set up (ugly, but I don't know any way to detect this). Thread.sleep(500); file.delete(); // undeployment followed by redeployment waitUntil(() -> undeploy.get() == 1 && deploy.get() == 2); } @Test public void testFileAdditionAndModificationInDirectory() throws IOException, InterruptedException { watcher.watch(); // Wait until the file monitoring is set up (ugly, but I don't know any way to detect this). Thread.sleep(500); // Initial deployment waitUntil(() -> deploy.get() == 1); File newDir = new File(root, "directory"); newDir.mkdir(); Thread.sleep(500); File file = new File(newDir, "foo.txt"); file.createNewFile(); // undeployment followed by redeployment waitUntil(() -> undeploy.get() == 1 && deploy.get() == 2); Thread.sleep(1000); // Update file // Simulate a 'touch' file.setLastModified(System.currentTimeMillis()); // undeployment followed by redeployment waitUntil(() -> undeploy.get() == 2 && deploy.get() == 3); Thread.sleep(1000); // delete directory deleteRecursive(newDir); waitUntil(() -> undeploy.get() == 3 && deploy.get() == 4); } @Test public void testOSSpecificIncludePaths() throws IOException, InterruptedException { watcher.watch(); // Initial deployment waitUntil(() -> deploy.get() == 1); // create a file to be matched using windows style include pattern File winDir = new File(root, "windows"); winDir.mkdir(); Thread.sleep(500); File winFile = new File(winDir, "foo.win"); winFile.createNewFile(); // undeployment followed by redeployment waitUntil(() -> undeploy.get() == 1 && deploy.get() == 2); Thread.sleep(1000); // create a file to be matched using *nix style include pattern File nixDir = new File(root, "unix"); nixDir.mkdir(); Thread.sleep(500); File nixFile = new File(nixDir, "foo.nix"); nixFile.createNewFile(); // undeployment followed by redeployment waitUntil(() -> undeploy.get() == 2 && deploy.get() == 3); } @Test public void testCaseSensitivity() throws IOException, InterruptedException { watcher.watch(); // Initial deployment waitUntil(() -> deploy.get() == 1); // create a file to be matched against "FOO.bar" pattern File file = new File(root, "fOo.BAr"); file.createNewFile(); if (ExecUtils.isWindows()) { // undeployment followed by redeployment. Windows is not case sensitive waitUntil(() -> undeploy.get() == 1 && deploy.get() == 2); } else { // It depends on the file system, we can't really test anything in a reliable way. } } public static boolean deleteRecursive(File path) { if (!path.exists()) { return false; } boolean ret = true; if (path.isDirectory()) { File[] files = path.listFiles(); if (files != null) { for (File f : files) { ret = ret && deleteRecursive(f); } } } return ret && path.delete(); } @Test public void testWatcherPerformances() throws IOException, InterruptedException { List<File> files = new ArrayList<>(); // Create structure - 3 sub-dir with each 10 sub dir, wich another level of directory with each 50 files for (int i = 0; i < 3; i++) { File dir = new File(root, Integer.toString(i)); dir.mkdirs(); for (int j = 0; j < 10; j++) { File sub = new File(root, Integer.toString(j)); sub.mkdirs(); File subsub = new File(sub, "sub"); subsub.mkdirs(); for (int k = 0; k < 1000; k++) { File txt = new File(subsub, k + ".txt"); files.add(txt); Files.write(txt.toPath(), Integer.toString(k).getBytes()); File java = new File(subsub, k + ".java"); Files.write(java.toPath(), Integer.toString(k).getBytes()); } } } System.out.println("Environment setup"); watcher.watch(); waitUntil(() -> deploy.get() == 1); long begin = System.currentTimeMillis(); Path path = files.get(0).toPath(); Thread.sleep(1000); // Need to wait to be sure we are not in the same second as the previous write Files.write(path, "booooo".getBytes()); waitUntil(() -> undeploy.get() == 1); waitUntil(() -> deploy.get() == 2); long end = System.currentTimeMillis(); System.out.println("Update change applied in " + (end - begin) + " ms"); begin = System.currentTimeMillis(); files.get(1).delete(); waitUntil(() -> undeploy.get() == 2); waitUntil(() -> deploy.get() == 3); end = System.currentTimeMillis(); System.out.println("Deletion change applied in " + (end - begin) + " ms"); begin = System.currentTimeMillis(); files.get(1).delete(); File newFile = new File(root, "test.txt"); Files.write(newFile.toPath(), "I'm a new file".getBytes()); waitUntil(() -> undeploy.get() == 3); waitUntil(() -> deploy.get() == 4); end = System.currentTimeMillis(); System.out.println("Creation change applied in " + (end - begin) + " ms"); } @Test public void testRootExtraction() { List<String> patterns = new ArrayList<>(); patterns.add("src/main/java/**/*.java"); List<File> results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getAbsolutePath()); patterns.clear(); patterns.add(root.getParentFile().getAbsolutePath()); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getParentFile().getAbsolutePath()); patterns.clear(); patterns.add("**/*.java"); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getAbsolutePath()); patterns.clear(); patterns.add(root.getParentFile().getAbsolutePath() + "/**/*.java"); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getParentFile().getAbsolutePath()); patterns.clear(); patterns.add(root.getParentFile().getAbsolutePath() + "/foo.txt"); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getParentFile().getAbsolutePath()); patterns.clear(); patterns.add("foo.txt"); results = Watcher.extractRoots(root, patterns); assertThat(results).hasSize(1); assertThat(results.get(0).getAbsolutePath()).contains(root.getParentFile().getAbsolutePath()); } }