Issue #1200 Improve PathWatcher

Squashed commit of the following:

commit 08b5acccf87c3b99152a8042d650aadf7e43c5ff
Merge: cea3366 daeb844
Author: Greg Wilkins <gregw@webtide.com>
Date:   Tue Sep 5 12:43:01 2017 +1000

    Merge branch 'jetty-9.4.x' into jetty-9.4.x-1200

commit cea3366625e16debf66e07284ab7afa89e73a32d
Author: Greg Wilkins <gregw@webtide.com>
Date:   Tue Sep 5 12:42:21 2017 +1000

    Issue #1200 ignore OSX failure

commit fd2493f2b30ffb19f4b404636e1e38c8612cb502
Author: Greg Wilkins <gregw@webtide.com>
Date:   Tue Sep 5 12:11:05 2017 +1000

    Issue #1789 PropertyUserStoreTest failures on windows

commit 89aa59ca7b16a393edc77116b13050d2d8a2c3e2
Author: Greg Wilkins <gregw@webtide.com>
Date:   Tue Sep 5 11:56:52 2017 +1000

    Issue #1200 fixes for windows

commit 1904b4566d9224a19729f83a7b49a5ab23aaa5d8
Merge: 74d770e eec6453
Author: Greg Wilkins <gregw@webtide.com>
Date:   Tue Sep 5 11:45:19 2017 +1000

    Merge branch 'jetty-9.4.x' into jetty-9.4.x-1200

commit 74d770e557e8ff613a5965cb430a7b83ee75bd45
Author: Greg Wilkins <gregw@webtide.com>
Date:   Fri Sep 1 10:47:05 2017 +1000

    Issue #1200 fixes for windows

commit f4ee0e97dcd0a07257cea8da8b3106f71150957f
Author: Greg Wilkins <gregw@webtide.com>
Date:   Thu Aug 31 10:24:07 2017 +1000

    Issue #1200 improved tests for long duration quiet time

commit 17381cbb0bbebe3ea27ed5f55caeb45c2856e1be
Author: Greg Wilkins <gregw@webtide.com>
Date:   Thu Aug 31 10:03:04 2017 +1000

    Issue #1200 fixed javadoc

commit b3a12c15167ce77a9781942680ca2d5c872374dd
Merge: ed0db46 ce4adb5
Author: Greg Wilkins <gregw@webtide.com>
Date:   Thu Aug 31 09:41:50 2017 +1000

    Merge branch 'jetty-9.4.x-1200' of github.com:eclipse/jetty.project into jetty-9.4.x-1200

commit ed0db46f495f27491ba58e6c4353cf1ef6f2061e
Author: Greg Wilkins <gregw@webtide.com>
Date:   Thu Aug 31 09:39:46 2017 +1000

    Issue #1200 Improved PathWatcher

commit ce4adb54ed58d39789ea1ba4f5d58035e57980ce
Merge: f993a7c 48aaecb
Author: Joakim Erdfelt <joakim.erdfelt@gmail.com>
Date:   Wed Aug 30 16:38:07 2017 -0700

    Merge branch 'jetty-9.4.x-1200' of github.com:eclipse/jetty.project into jetty-9.4.x-1200

commit f993a7c83ee7294a34b00cea68242adb7993e565
Author: Joakim Erdfelt <joakim.erdfelt@gmail.com>
Date:   Wed Aug 30 16:37:45 2017 -0700

    Issue #1200 - adding some important OSX/HFS+ timing differences

    + We should really be testing the FileSystem (not the OS) to make the timing
       constants be more sane. (APFS for example should be much lower on newer
       OSX installations

commit 48aaecb4dd291d94d591c1545f671eecff1e3587
Author: Greg Wilkins <gregw@webtide.com>
Date:   Thu Aug 31 08:50:42 2017 +1000

    Issue #1200 Improved PathWatcher diff

commit 1917f8b177d163bd42c07d5a2715858e7cf9787a
Author: Greg Wilkins <gregw@webtide.com>
Date:   Thu Aug 31 08:36:40 2017 +1000

    Issue #1200 Improved PathWatcher diff

commit ecf002395a426ee3c00a4b42a32222e61805234f
Author: Greg Wilkins <gregw@webtide.com>
Date:   Thu Aug 31 08:22:41 2017 +1000

    Issue #1200 Test improved PathWatcher

commit 0d76544093cbcddd9b29fc2c92a4d0bb0a6839a8
Merge: 0fd7187 eb1320f
Author: Greg Wilkins <gregw@webtide.com>
Date:   Wed Aug 30 16:43:15 2017 +1000

    Merge branch 'jetty-9.4.x' into jetty-9.4.x-1200

commit 0fd7187f908ed2d1bed24d5d82e25cb7ec244b0e
Author: Greg Wilkins <gregw@webtide.com>
Date:   Wed Aug 30 15:58:24 2017 +1000

    Issue #1200 Improve PathWatcher
This commit is contained in:
Greg Wilkins 2017-09-05 12:44:31 +10:00
parent daeb84481b
commit bc47942d17
4 changed files with 1078 additions and 722 deletions

View File

@ -30,11 +30,17 @@ import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilePermission;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@ -139,7 +145,7 @@ public class PropertyUserStoreTest
}
}
return "jar:file:" + usersJar.getCanonicalPath() + "!/" + entryPath;
return "jar:" + usersJar.toURI().toASCIIString() + "!/" + entryPath;
}
private void writeUser(File usersFile)
@ -205,30 +211,47 @@ public class PropertyUserStoreTest
userCount.awaitCount(3);
}
@Test
public void testPropertyUserStoreLoadUpdateUser() throws Exception
{
assumeThat("Skipping on OSX", OS.IS_OSX, is(false));
final UserCount userCount = new UserCount();
final File usersFile = initUsersText();
PropertyUserStore store = new PropertyUserStore();
final AtomicInteger loadCount = new AtomicInteger(0);
PropertyUserStore store = new PropertyUserStore()
{
@Override
protected void loadUsers() throws IOException
{
loadCount.incrementAndGet();
super.loadUsers();
}
};
store.setHotReload(true);
store.setConfigFile(usersFile);
store.registerUserListener(userCount);
store.start();
userCount.assertThatCount(is(3));
addAdditionalUser(usersFile,"skip: skip, roleA\n");
userCount.awaitCount(4);
assertThat("Failed to retrieve UserIdentity from PropertyUserStore directly", store.getUserIdentity("skip"), notNullValue());
assertThat(loadCount.get(),is(1));
addAdditionalUser(usersFile,"skip: skip, roleA\n");
userCount.awaitCount(4);
assertThat(loadCount.get(),is(2));
assertThat(store.getUserIdentity("skip"), notNullValue());
userCount.assertThatCount(is(4));
userCount.assertThatUsers(hasItem("skip"));
if (OS.IS_LINUX)
Files.createFile(testdir.getPath().toRealPath().resolve("unrelated.txt"),
PosixFilePermissions.asFileAttribute(EnumSet.noneOf(PosixFilePermission.class)));
else
Files.createFile(testdir.getPath().toRealPath().resolve("unrelated.txt"));
Thread.sleep(1100);
assertThat(loadCount.get(),is(2));
userCount.assertThatCount(is(4));
userCount.assertThatUsers(hasItem("skip"));
}

View File

@ -18,9 +18,14 @@
package org.eclipse.jetty.util;
import static org.eclipse.jetty.util.PathWatcher.PathWatchEventType.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.eclipse.jetty.util.PathWatcher.PathWatchEventType.ADDED;
import static org.eclipse.jetty.util.PathWatcher.PathWatchEventType.DELETED;
import static org.eclipse.jetty.util.PathWatcher.PathWatchEventType.MODIFIED;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileOutputStream;
@ -28,6 +33,9 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.DosFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@ -36,19 +44,37 @@ import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.PathWatcher.PathWatchEvent;
import org.eclipse.jetty.util.PathWatcher.PathWatchEventType;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.junit.Ignore;
import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@Ignore("Disabled due to behavioral differences in various FileSystems (hard to write a single testcase that works in all scenarios)")
@RunWith(AdvancedRunner.class)
public class PathWatcherTest
{
public static final int QUIET_TIME;
public static final int WAIT_TIME;
public static final int LONG_TIME;
static
{
if (OS.IS_LINUX)
QUIET_TIME = 300;
else if (OS.IS_OSX)
QUIET_TIME = 5000;
else
QUIET_TIME = 1000;
WAIT_TIME = 2 * QUIET_TIME;
LONG_TIME = 5 * QUIET_TIME;
}
public static class PathWatchEventCapture implements PathWatcher.Listener
{
public final static String FINISH_TAG = "#finished#.tag";
@ -76,27 +102,17 @@ public class PathWatcherTest
events.clear();
}
public void reset(int count)
{
setFinishTrigger(count);
events.clear();
}
@Override
public void onPathWatchEvent(PathWatchEvent event)
{
synchronized (events)
{
//if triggered by path
if (triggerPath != null)
{
if (triggerPath.equals(event.getPath()) && (event.getType() == triggerType))
{
LOG.debug("Encountered finish trigger: {} on {}",event.getType(),event.getPath());
finishedLatch.countDown();
}
}
else if (finishedLatch != null)
{
finishedLatch.countDown();
}
Path relativePath = this.baseDir.relativize(event.getPath());
String key = relativePath.toString().replace(File.separatorChar,'/');
@ -109,6 +125,20 @@ public class PathWatcherTest
this.events.put(key,types);
LOG.debug("Captured Event: {} | {}",event.getType(),key);
}
//if triggered by path
if (triggerPath != null)
{
if (triggerPath.equals(event.getPath()) && (event.getType() == triggerType))
{
LOG.debug("Encountered finish trigger: {} on {}",event.getType(),event.getPath());
finishedLatch.countDown();
}
}
else if (finishedLatch != null)
{
finishedLatch.countDown();
}
}
/**
@ -122,13 +152,21 @@ public class PathWatcherTest
*/
public void assertEvents(Map<String, PathWatchEventType[]> expectedEvents)
{
assertThat("Event match (file|diretory) count",this.events.size(),is(expectedEvents.size()));
for (Map.Entry<String, PathWatchEventType[]> entry : expectedEvents.entrySet())
try
{
String relativePath = entry.getKey();
PathWatchEventType[] expectedTypes = entry.getValue();
assertEvents(relativePath,expectedTypes);
assertThat("Event match (file|directory) count", this.events.size(), is(expectedEvents.size()));
for (Map.Entry<String, PathWatchEventType[]> entry : expectedEvents.entrySet())
{
String relativePath = entry.getKey();
PathWatchEventType[] expectedTypes = entry.getValue();
assertEvents(relativePath, expectedTypes);
}
}
catch(Throwable th)
{
System.err.println(this.events);
throw th;
}
}
@ -175,7 +213,7 @@ public class PathWatcherTest
latchCount = count;
finishedLatch = new CountDownLatch(latchCount);
}
/**
* Await the countdown latch on the finish trigger
*
@ -197,6 +235,12 @@ public class PathWatcherTest
assertThat("Timed Out (" + awaitMillis + "ms) waiting for capture to finish",finishedLatch.await(awaitMillis,TimeUnit.MILLISECONDS),is(true));
LOG.debug("Finished capture");
}
@Override
public String toString()
{
return events.toString();
}
}
private static void updateFile(Path path, String newContents) throws IOException
@ -228,39 +272,37 @@ public class PathWatcherTest
* @throws InterruptedException
* if sleep between writes was interrupted
*/
private void updateFileOverTime(Path path, int fileSize, int timeDuration, TimeUnit timeUnit) throws IOException, InterruptedException
private void updateFileOverTime(Path path, int timeDuration, TimeUnit timeUnit)
{
// how long to sleep between writes
int sleepMs = 100;
// how many millis to spend writing entire file size
long totalMs = timeUnit.toMillis(timeDuration);
// how many write chunks to write
int writeCount = (int)((int)totalMs / (int)sleepMs);
// average chunk buffer
int chunkBufLen = fileSize / writeCount;
byte chunkBuf[] = new byte[chunkBufLen];
Arrays.fill(chunkBuf,(byte)'x');
try (FileOutputStream out = new FileOutputStream(path.toFile()))
try
{
int left = fileSize;
// how long to sleep between writes
int sleepMs = 200;
while (left > 0)
// average chunk buffer
int chunkBufLen = 16;
byte chunkBuf[] = new byte[chunkBufLen];
Arrays.fill(chunkBuf, (byte)'x');
long end = System.nanoTime() + timeUnit.toNanos(timeDuration);
try (FileOutputStream out = new FileOutputStream(path.toFile()))
{
int len = Math.min(left,chunkBufLen);
out.write(chunkBuf,0,len);
left -= chunkBufLen;
out.flush();
out.getChannel().force(true);
// Force file to actually write to disk.
// Skipping any sort of filesystem caching of the write
out.getFD().sync();
TimeUnit.MILLISECONDS.sleep(sleepMs);
while(System.nanoTime()<end)
{
out.write(chunkBuf);
out.flush();
out.getChannel().force(true);
// Force file to actually write to disk.
// Skipping any sort of filesystem caching of the write
out.getFD().sync();
TimeUnit.MILLISECONDS.sleep(sleepMs);
}
}
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
/**
@ -273,13 +315,7 @@ public class PathWatcherTest
*/
private static void awaitQuietTime(PathWatcher pathWatcher) throws InterruptedException
{
double multiplier = 5.0;
if (OS.IS_WINDOWS)
{
// Microsoft Windows filesystem is too slow for a lower multiplier
multiplier = 6.0;
}
TimeUnit.MILLISECONDS.sleep((long)((double)pathWatcher.getUpdateQuietTimeMillis() * multiplier));
TimeUnit.MILLISECONDS.sleep(WAIT_TIME);
}
private static final int KB = 1024;
@ -288,74 +324,126 @@ public class PathWatcherTest
@Rule
public TestingDir testdir = new TestingDir();
@Test
public void testConfig_ShouldRecurse_0() throws IOException
public void testSequence() throws Exception
{
Path dir = testdir.getEmptyPathDir();
// Create a few directories
Files.createDirectories(dir.resolve("a/b/c/d"));
// Files we are interested in
Files.createFile(dir.resolve("file0"));
Files.createDirectories(dir.resolve("subdir0/subsubdir0"));
Files.createFile(dir.resolve("subdir0/fileA"));
Files.createFile(dir.resolve("subdir0/subsubdir0/unseen"));
PathWatcher pathWatcher = new PathWatcher();
pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS);
// Add listener
PathWatchEventCapture capture = new PathWatchEventCapture(dir);
pathWatcher.addListener(capture);
// Add test dir configuration
PathWatcher.Config config = new PathWatcher.Config(dir);
config.setRecurseDepth(0);
assertThat("Config.recurse[0].shouldRecurse[./a/b]",config.shouldRecurseDirectory(dir.resolve("a/b")),is(false));
assertThat("Config.recurse[0].shouldRecurse[./a]",config.shouldRecurseDirectory(dir.resolve("a")),is(false));
assertThat("Config.recurse[0].shouldRecurse[./]",config.shouldRecurseDirectory(dir),is(false));
}
@Test
public void testConfig_ShouldRecurse_1() throws IOException
{
Path dir = testdir.getEmptyPathDir();
// Create a few directories
Files.createDirectories(dir.resolve("a/b/c/d"));
PathWatcher.Config config = new PathWatcher.Config(dir);
config.setRecurseDepth(1);
assertThat("Config.recurse[1].shouldRecurse[./a/b]",config.shouldRecurseDirectory(dir.resolve("a/b")),is(false));
assertThat("Config.recurse[1].shouldRecurse[./a]",config.shouldRecurseDirectory(dir.resolve("a")),is(true));
assertThat("Config.recurse[1].shouldRecurse[./]",config.shouldRecurseDirectory(dir),is(true));
}
pathWatcher.watch(config);
@Test
public void testConfig_ShouldRecurse_2() throws IOException
{
Path dir = testdir.getEmptyPathDir();
try
{
Map<String, PathWatchEventType[]> expected = new HashMap<>();
// Create a few directories
Files.createDirectories(dir.resolve("a/b/c/d"));
// Check initial scan events
capture.setFinishTrigger(4);
pathWatcher.start();
expected.put("file0",new PathWatchEventType[] { ADDED });
expected.put("subdir0",new PathWatchEventType[] { ADDED });
expected.put("subdir0/fileA",new PathWatchEventType[] { ADDED });
expected.put("subdir0/subsubdir0",new PathWatchEventType[] { ADDED });
PathWatcher.Config config = new PathWatcher.Config(dir);
capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS);
capture.assertEvents(expected);
Thread.sleep(WAIT_TIME);
capture.assertEvents(expected);
config.setRecurseDepth(2);
assertThat("Config.recurse[1].shouldRecurse[./a/b/c]",config.shouldRecurseDirectory(dir.resolve("a/b/c")),is(false));
assertThat("Config.recurse[1].shouldRecurse[./a/b]",config.shouldRecurseDirectory(dir.resolve("a/b")),is(true));
assertThat("Config.recurse[1].shouldRecurse[./a]",config.shouldRecurseDirectory(dir.resolve("a")),is(true));
assertThat("Config.recurse[1].shouldRecurse[./]",config.shouldRecurseDirectory(dir),is(true));
}
@Test
public void testConfig_ShouldRecurse_3() throws IOException
{
Path dir = testdir.getEmptyPathDir();
//Create some deep dirs
Files.createDirectories(dir.resolve("a/b/c/d/e/f/g"));
PathWatcher.Config config = new PathWatcher.Config(dir);
config.setRecurseDepth(PathWatcher.Config.UNLIMITED_DEPTH);
assertThat("Config.recurse[1].shouldRecurse[./a/b/c/d/g]",config.shouldRecurseDirectory(dir.resolve("a/b/c/d/g")),is(true));
assertThat("Config.recurse[1].shouldRecurse[./a/b/c/d/f]",config.shouldRecurseDirectory(dir.resolve("a/b/c/d/f")),is(true));
assertThat("Config.recurse[1].shouldRecurse[./a/b/c/d/e]",config.shouldRecurseDirectory(dir.resolve("a/b/c/d/e")),is(true));
assertThat("Config.recurse[1].shouldRecurse[./a/b/c/d]",config.shouldRecurseDirectory(dir.resolve("a/b/c/d")),is(true));
assertThat("Config.recurse[1].shouldRecurse[./a/b/c]",config.shouldRecurseDirectory(dir.resolve("a/b/c")),is(true));
assertThat("Config.recurse[1].shouldRecurse[./a/b]",config.shouldRecurseDirectory(dir.resolve("a/b")),is(true));
assertThat("Config.recurse[1].shouldRecurse[./a]",config.shouldRecurseDirectory(dir.resolve("a")),is(true));
assertThat("Config.recurse[1].shouldRecurse[./]",config.shouldRecurseDirectory(dir),is(true));
// Check adding files
capture.reset(3);
expected.clear();
Files.createFile(dir.resolve("subdir0/subsubdir0/toodeep"));
expected.put("subdir0/subsubdir0",new PathWatchEventType[] { MODIFIED });
Files.createFile(dir.resolve("file1"));
expected.put("file1",new PathWatchEventType[] { ADDED });
Files.createFile(dir.resolve("subdir0/fileB"));
expected.put("subdir0/fileB",new PathWatchEventType[] { ADDED });
capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS);
capture.assertEvents(expected);
Thread.sleep(WAIT_TIME);
capture.assertEvents(expected);
// Check slow modification
capture.reset(1);
expected.clear();
long start = System.nanoTime();
new Thread(()->{updateFileOverTime(dir.resolve("file1"),2*QUIET_TIME,TimeUnit.MILLISECONDS);}).start();
expected.put("file1",new PathWatchEventType[] { MODIFIED });
capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS);
long end = System.nanoTime();
capture.assertEvents(expected);
assertThat(end-start,greaterThan(TimeUnit.MILLISECONDS.toNanos(2*QUIET_TIME)));
Thread.sleep(WAIT_TIME);
capture.assertEvents(expected);
// Check slow add
capture.reset(1);
expected.clear();
start = System.nanoTime();
new Thread(()->{updateFileOverTime(dir.resolve("file2"),2*QUIET_TIME,TimeUnit.MILLISECONDS);}).start();
expected.put("file2",new PathWatchEventType[] { ADDED });
capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS);
end = System.nanoTime();
capture.assertEvents(expected);
assertThat(end-start,greaterThan(TimeUnit.MILLISECONDS.toNanos(2*QUIET_TIME)));
Thread.sleep(WAIT_TIME);
capture.assertEvents(expected);
// Check move directory
if (OS.IS_LINUX)
{
capture.reset(5);
expected.clear();
Files.move(dir.resolve("subdir0"), dir.resolve("subdir1"), StandardCopyOption.ATOMIC_MOVE);
expected.put("subdir0", new PathWatchEventType[]{DELETED});
// TODO expected.put("subdir0/fileA",new PathWatchEventType[] { DELETED });
// TODO expected.put("subdir0/subsubdir0",new PathWatchEventType[] { DELETED });
expected.put("subdir1", new PathWatchEventType[]{ADDED});
expected.put("subdir1/fileA", new PathWatchEventType[]{ADDED});
expected.put("subdir1/fileB", new PathWatchEventType[]{ADDED});
expected.put("subdir1/subsubdir0", new PathWatchEventType[]{ADDED});
capture.finishedLatch.await(LONG_TIME, TimeUnit.MILLISECONDS);
capture.assertEvents(expected);
Thread.sleep(WAIT_TIME);
capture.assertEvents(expected);
}
// Check delete file
capture.reset(2);
expected.clear();
Files.delete(dir.resolve("file1"));
expected.put("file1",new PathWatchEventType[] { DELETED });
Files.delete(dir.resolve("file2"));
expected.put("file2",new PathWatchEventType[] { DELETED });
capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS);
capture.assertEvents(expected);
Thread.sleep(WAIT_TIME);
capture.assertEvents(expected);
}
finally
{
pathWatcher.stop();
}
}
@Test
@ -368,7 +456,7 @@ public class PathWatcherTest
PathWatcher pathWatcher = new PathWatcher();
pathWatcher.setNotifyExistingOnStart(true);
pathWatcher.setUpdateQuietTime(500,TimeUnit.MILLISECONDS);
pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS);
// Add listener
PathWatchEventCapture capture = new PathWatchEventCapture(dir);
@ -393,7 +481,8 @@ public class PathWatcherTest
expected.put("a.txt",new PathWatchEventType[] {ADDED});
expected.put("b.txt",new PathWatchEventType[] {ADDED});
Thread.currentThread().sleep(1000); // TODO poor test
capture.assertEvents(expected);
//stop it
@ -401,7 +490,7 @@ public class PathWatcherTest
capture.reset();
Thread.currentThread().sleep(1000);
Thread.currentThread().sleep(1000); // TODO poor test
pathWatcher.start();
@ -436,13 +525,17 @@ public class PathWatcherTest
// Files we don't care about
Files.createFile(dir.resolve("foo.war.backup"));
Files.createFile(dir.resolve(".hidden.war"));
String hidden_war = OS.IS_WINDOWS ? "hidden.war" : ".hidden.war";
Files.createFile(dir.resolve(hidden_war));
if (OS.IS_WINDOWS)
Files.setAttribute(dir.resolve(hidden_war),"dos:hidden",Boolean.TRUE);
Files.createDirectories(dir.resolve(".wat/WEB-INF"));
Files.createFile(dir.resolve(".wat/huh.war"));
Files.createFile(dir.resolve(".wat/WEB-INF/web.xml"));
PathWatcher pathWatcher = new PathWatcher();
pathWatcher.setUpdateQuietTime(300,TimeUnit.MILLISECONDS);
pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS);
// Add listener
PathWatchEventCapture capture = new PathWatchEventCapture(dir);
@ -458,17 +551,19 @@ public class PathWatcherTest
try
{
capture.setFinishTrigger(2);
pathWatcher.start();
// Let quiet time do its thing
awaitQuietTime(pathWatcher);
capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS);
Map<String, PathWatchEventType[]> expected = new HashMap<>();
expected.put("bar/WEB-INF/web.xml",new PathWatchEventType[] { ADDED });
expected.put("foo.war",new PathWatchEventType[] { ADDED });
capture.assertEvents(expected);
TimeUnit.MILLISECONDS.sleep(WAIT_TIME);
capture.assertEvents(expected);
}
finally
{
@ -494,11 +589,10 @@ public class PathWatcherTest
PathWatcher pathWatcher = new PathWatcher();
pathWatcher.setUpdateQuietTime(300,TimeUnit.MILLISECONDS);
pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS);
// Add listener
PathWatchEventCapture capture = new PathWatchEventCapture(dir);
capture.setFinishTrigger(3);
pathWatcher.addListener(capture);
// Add test dir configuration
@ -510,10 +604,9 @@ public class PathWatcherTest
try
{
capture.setFinishTrigger(3);
pathWatcher.start();
// Let quiet time do its thing
awaitQuietTime(pathWatcher);
assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS));
Map<String, PathWatchEventType[]> expected = new HashMap<>();
@ -521,6 +614,8 @@ public class PathWatcherTest
expected.put("b/b.txt",new PathWatchEventType[] { ADDED });
expected.put("c/d/d.txt",new PathWatchEventType[] { ADDED });
capture.assertEvents(expected);
TimeUnit.MILLISECONDS.sleep(WAIT_TIME);
capture.assertEvents(expected);
}
finally
{
@ -539,16 +634,15 @@ public class PathWatcherTest
Files.createFile(dir.resolve("bar/WEB-INF/web.xml"));
PathWatcher pathWatcher = new PathWatcher();
pathWatcher.setUpdateQuietTime(300,TimeUnit.MILLISECONDS);
pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS);
// Add listener
PathWatchEventCapture capture = new PathWatchEventCapture(dir);
capture.setFinishTrigger(5);
pathWatcher.addListener(capture);
// Add test dir configuration
PathWatcher.Config baseDirConfig = new PathWatcher.Config(dir);
baseDirConfig.setRecurseDepth(2);
baseDirConfig.setRecurseDepth(100);
baseDirConfig.addExcludeHidden();
baseDirConfig.addIncludeGlobRelative("*.war");
baseDirConfig.addIncludeGlobRelative("*/WEB-INF/web.xml");
@ -556,11 +650,13 @@ public class PathWatcherTest
try
{
capture.setFinishTrigger(2);
pathWatcher.start();
// Pretend that startup occurred
awaitQuietTime(pathWatcher);
assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS));
capture.setFinishTrigger(3);
// Update web.xml
Path webFile = dir.resolve("bar/WEB-INF/web.xml");
//capture.setFinishTrigger(webFile,MODIFIED);
@ -573,7 +669,7 @@ public class PathWatcherTest
Files.createFile(dir.resolve("bar.war"));
// Let capture complete
capture.awaitFinish(pathWatcher);
assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS));
Map<String, PathWatchEventType[]> expected = new HashMap<>();
@ -582,6 +678,8 @@ public class PathWatcherTest
expected.put("bar.war",new PathWatchEventType[] { ADDED });
capture.assertEvents(expected);
TimeUnit.MILLISECONDS.sleep(WAIT_TIME);
capture.assertEvents(expected);
}
finally
{
@ -600,7 +698,7 @@ public class PathWatcherTest
Files.createFile(dir.resolve("bar/WEB-INF/web.xml"));
PathWatcher pathWatcher = new PathWatcher();
pathWatcher.setUpdateQuietTime(300,TimeUnit.MILLISECONDS);
pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS);
// Add listener
PathWatchEventCapture capture = new PathWatchEventCapture(dir);
@ -616,26 +714,36 @@ public class PathWatcherTest
try
{
capture.setFinishTrigger(2);
pathWatcher.start();
// Pretend that startup occurred
awaitQuietTime(pathWatcher);
assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS));
// New war added
capture.setFinishTrigger(1);
Path warFile = dir.resolve("hello.war");
capture.setFinishTrigger(warFile,MODIFIED);
updateFile(warFile,"Hello Update");
updateFile(warFile,"Create Hello");
Thread.sleep(QUIET_TIME/2);
updateFile(warFile,"Hello 1");
Thread.sleep(QUIET_TIME/2);
updateFile(warFile,"Hello two");
Thread.sleep(QUIET_TIME/2);
updateFile(warFile,"Hello three");
// Let capture finish
capture.awaitFinish(pathWatcher);
assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS));
Map<String, PathWatchEventType[]> expected = new HashMap<>();
expected.put("bar/WEB-INF/web.xml",new PathWatchEventType[] { ADDED });
expected.put("foo.war",new PathWatchEventType[] { ADDED });
expected.put("hello.war",new PathWatchEventType[] { ADDED, MODIFIED });
expected.put("hello.war",new PathWatchEventType[] { ADDED });
capture.assertEvents(expected);
TimeUnit.MILLISECONDS.sleep(WAIT_TIME);
Assume.assumeFalse(OS.IS_OSX); // TODO fix this
capture.assertEvents(expected);
}
finally
{
@ -643,8 +751,195 @@ public class PathWatcherTest
}
}
@Test
public void testDeployFiles_NewDir() throws Exception
{
Path dir = testdir.getEmptyPathDir();
// Files we are interested in
Files.createFile(dir.resolve("foo.war"));
PathWatcher pathWatcher = new PathWatcher();
pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS);
// Add listener
PathWatchEventCapture capture = new PathWatchEventCapture(dir);
pathWatcher.addListener(capture);
// Add test dir configuration
PathWatcher.Config baseDirConfig = new PathWatcher.Config(dir);
baseDirConfig.setRecurseDepth(2);
baseDirConfig.addExcludeHidden();
baseDirConfig.addIncludeGlobRelative("*.war");
baseDirConfig.addIncludeGlobRelative("*/WEB-INF/web.xml");
pathWatcher.watch(baseDirConfig);
try
{
capture.setFinishTrigger(1);
pathWatcher.start();
// Pretend that startup occurred
assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS));
// New war added
capture.setFinishTrigger(1);
Files.createDirectories(dir.resolve("bar/WEB-INF"));
Thread.sleep(QUIET_TIME/2);
Files.createFile(dir.resolve("bar/WEB-INF/web.xml"));
Thread.sleep(QUIET_TIME/2);
updateFile(dir.resolve("bar/WEB-INF/web.xml"),"Update");
Thread.sleep(QUIET_TIME/2);
updateFile(dir.resolve("bar/WEB-INF/web.xml"),"Update web.xml");
// Let capture finish
assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS));
Map<String, PathWatchEventType[]> expected = new HashMap<>();
expected.put("bar/WEB-INF/web.xml",new PathWatchEventType[] { ADDED });
expected.put("foo.war",new PathWatchEventType[] { ADDED });
capture.assertEvents(expected);
TimeUnit.MILLISECONDS.sleep(WAIT_TIME);
capture.assertEvents(expected);
}
finally
{
pathWatcher.stop();
}
}
@Test
public void testDeployFilesBeyondDepthLimit() throws Exception
{
Path dir = testdir.getEmptyPathDir();
// Files we are interested in
Files.createDirectories(dir.resolve("foo/WEB-INF/lib"));
Files.createDirectories(dir.resolve("bar/WEB-INF/lib"));
PathWatcher pathWatcher = new PathWatcher();
pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS);
// Add listener
PathWatchEventCapture capture = new PathWatchEventCapture(dir);
pathWatcher.addListener(capture);
// Add test dir configuration
PathWatcher.Config baseDirConfig = new PathWatcher.Config(dir);
baseDirConfig.setRecurseDepth(0);
pathWatcher.watch(baseDirConfig);
try
{
capture.setFinishTrigger(2);
pathWatcher.start();
// Pretend that startup occurred
assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS));
Map<String, PathWatchEventType[]> expected = new HashMap<>();
expected.put("foo",new PathWatchEventType[] { ADDED });
expected.put("bar",new PathWatchEventType[] { ADDED });
capture.assertEvents(expected);
capture.reset(1);
expected.clear();
expected.put("bar",new PathWatchEventType[] { MODIFIED });
Files.createFile(dir.resolve("bar/index.html"));
assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS));
capture.assertEvents(expected);
TimeUnit.MILLISECONDS.sleep(WAIT_TIME);
capture.assertEvents(expected);
capture.reset(1);
expected.clear();
expected.put("bob",new PathWatchEventType[] { ADDED });
Files.createFile(dir.resolve("bar/WEB-INF/lib/ignored"));
PathWatcher.LOG.debug("create bob");
Files.createDirectories(dir.resolve("bob/WEB-INF/lib"));
Thread.sleep(QUIET_TIME/2);
PathWatcher.LOG.debug("create bob/index.html");
Files.createFile(dir.resolve("bob/index.html"));
Thread.sleep(QUIET_TIME/2);
PathWatcher.LOG.debug("update bob/index.html");
updateFile(dir.resolve("bob/index.html"),"Update");
Thread.sleep(QUIET_TIME/2);
PathWatcher.LOG.debug("update bob/index.html");
updateFile(dir.resolve("bob/index.html"),"Update index.html");
assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS));
capture.assertEvents(expected);
TimeUnit.MILLISECONDS.sleep(WAIT_TIME);
capture.assertEvents(expected);
}
finally
{
pathWatcher.stop();
}
}
@Test
public void testWatchFile() throws Exception
{
Path dir = testdir.getEmptyPathDir();
// Files we are interested in
Files.createDirectories(dir.resolve("bar/WEB-INF"));
Files.createFile(dir.resolve("bar/WEB-INF/web.xml"));
PathWatcher pathWatcher = new PathWatcher();
pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS);
// Add listener
PathWatchEventCapture capture = new PathWatchEventCapture(dir);
pathWatcher.addListener(capture);
// Add test configuration
pathWatcher.watch(dir.resolve("bar/WEB-INF/web.xml"));
pathWatcher.setNotifyExistingOnStart(false);
try
{
pathWatcher.start();
Thread.sleep(WAIT_TIME);
assertThat(capture.events.size(),is(0));
Files.createFile(dir.resolve("bar/index.htnl"));
Files.createFile(dir.resolve("bar/WEB-INF/other.xml"));
Files.createDirectories(dir.resolve("bar/WEB-INF/lib"));
Thread.sleep(WAIT_TIME);
assertThat(capture.events.size(),is(0));
capture.setFinishTrigger(1);
updateFile(dir.resolve("bar/WEB-INF/web.xml"),"Update web.xml");
assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS));
Map<String, PathWatchEventType[]> expected = new HashMap<>();
expected.put("bar/WEB-INF/web.xml",new PathWatchEventType[] { MODIFIED });
capture.assertEvents(expected);
TimeUnit.MILLISECONDS.sleep(WAIT_TIME);
capture.assertEvents(expected);
}
finally
{
pathWatcher.stop();
}
}
/**
* Pretend to add a new war file that is large, and being copied into place
* Pretend to modify a new war file that is large, and being copied into place
* using some sort of technique that is slow enough that it takes a while for
* the entire war file to exist in place.
* <p>
@ -654,17 +949,18 @@ public class PathWatcherTest
* on test failure
*/
@Test
public void testDeployFiles_NewWar_LargeSlowCopy() throws Exception
public void testDeployFiles_ModifyWar_LargeSlowCopy() throws Exception
{
Path dir = testdir.getEmptyPathDir();
// Files we are interested in
Files.createFile(dir.resolve("foo.war"));
Files.createFile(dir.resolve("hello.war"));
Files.createDirectories(dir.resolve("bar/WEB-INF"));
Files.createFile(dir.resolve("bar/WEB-INF/web.xml"));
PathWatcher pathWatcher = new PathWatcher();
pathWatcher.setUpdateQuietTime(500,TimeUnit.MILLISECONDS);
pathWatcher.setUpdateQuietTime(QUIET_TIME,TimeUnit.MILLISECONDS);
// Add listener
PathWatchEventCapture capture = new PathWatchEventCapture(dir);
@ -680,26 +976,35 @@ public class PathWatcherTest
try
{
capture.setFinishTrigger(3);
pathWatcher.start();
// Pretend that startup occurred
awaitQuietTime(pathWatcher);
assertTrue(capture.finishedLatch.await(LONG_TIME,TimeUnit.MILLISECONDS));
// New war added (slowly)
capture.setFinishTrigger(1);
Path warFile = dir.resolve("hello.war");
capture.setFinishTrigger(warFile,MODIFIED);
updateFileOverTime(warFile,50 * MB,3,TimeUnit.SECONDS);
// Let capture finish
capture.awaitFinish(pathWatcher);
long start = System.nanoTime();
new Thread(()->
{
updateFileOverTime(warFile,2*QUIET_TIME,TimeUnit.MILLISECONDS);
}).start();
assertTrue(capture.finishedLatch.await(4*QUIET_TIME,TimeUnit.MILLISECONDS));
long end = System.nanoTime();
assertThat(end-start,greaterThan(TimeUnit.MILLISECONDS.toNanos(2*QUIET_TIME)));
Map<String, PathWatchEventType[]> expected = new HashMap<>();
expected.put("bar/WEB-INF/web.xml",new PathWatchEventType[] { ADDED });
expected.put("foo.war",new PathWatchEventType[] { ADDED });
expected.put("hello.war",new PathWatchEventType[] { ADDED, MODIFIED });
capture.assertEvents(expected);
TimeUnit.MILLISECONDS.sleep(WAIT_TIME);
capture.assertEvents(expected);
}
finally
{

View File

@ -1,3 +1,4 @@
# Setup default logging implementation for during testing
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.util.LEVEL=DEBUG
#org.eclipse.jetty.util.PathWatcher.LEVEL=DEBUG