/*
* 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());
}
}