/*
* Copyright 2012 Jason Miller
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jj.resource;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
import static jj.server.ServerLocation.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import jj.Base;
import jj.ServerStarting;
import jj.ServerStopping;
import jj.event.MockPublisher;
import jj.execution.JJTask;
import jj.execution.MockTaskRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
// this class is relatively concrete because it
// integrates a bit
@RunWith(MockitoJUnitRunner.class)
public class ResourceWatchServiceLoopTest {
@Mock ResourceWatchSwitch resourceWatchSwitch;
ResourceCache resourceCache;
@Mock ResourceLoader resourceLoader;
@Mock FileWatcher watcher;
MockPublisher publisher;
MockTaskRunner taskRunner;
Map<Path, FileWatcher.Action> changes = new HashMap<>();
ResourceWatchServiceLoop loop;
MyResource resource1;
MyResource resource2;
MyResource resource3;
MyResource resource4;
MyResource resource5;
@SuppressWarnings({"unchecked", "rawtypes"})
private ResourceCreators makeResourceCreators() {
Map map = new HashMap<>();
map.put(MyResource.class, new MyResourceCreator(publisher = new MockPublisher()));
return new ResourceCreators(map);
}
private MyResource makeResource(String name) {
MyResource result = spy(new MyResource(name, publisher));
resourceCache.putIfAbsent(result);
return result;
}
@Before
public void before() throws Exception {
resourceCache = new ResourceCache(makeResourceCreators());
taskRunner = new MockTaskRunner();
loop = new ResourceWatchServiceLoop(
resourceWatchSwitch,
resourceCache,
resourceLoader,
watcher,
publisher,
taskRunner
);
resource1 = makeResource("resource1");
resource2 = makeResource("resource2");
resource3 = makeResource("resource3");
resource4 = makeResource("resource4");
resource5 = makeResource("resource5");
}
@Test
public void testStart() {
assertThat(startWatchLoop(), is(loop));
}
public interface MyTestResource extends FileSystemResource, Resource<Void> {}
@Mock MyTestResource resource;
@SuppressWarnings("unchecked")
@Test
public void testWatches() {
given((ResourceIdentifier<MyTestResource, Void>)resource.identifier()).willReturn(
new MockResourceIdentifierMaker().make(MyTestResource.class, Virtual, "whatever")
);
given(resourceWatchSwitch.runFileWatcher()).willReturn(true);
Path path = Paths.get("whatever/makes/you/happy");
given(resource.path()).willReturn(path);
loop.on(new ResourceLoaded(resource));
verify(watcher, never()).watch(path.getParent());
given(watcher.start()).willReturn(true);
loop.on((ServerStarting) null);
loop.on(new ResourceLoaded(resource));
verify(watcher).watch(path.getParent());
given(resource.isDirectory()).willReturn(true);
loop.on(new ResourceLoaded(resource));
verify(watcher).watch(path);
}
@Test
public void testDirectoryCreation() throws Exception {
startWatchLoop();
changes.put(Base.path, FileWatcher.Action.Create);
loopOverChangesAndDie();
DirectoryCreation dc = (DirectoryCreation)publisher.events.get(0);
assertThat(dc.path, is(Base.path));
}
@Test
public void testFileCreation() throws Exception {
startWatchLoop();
Path path = Base.path.resolve("blank.gif");
changes.put(path, FileWatcher.Action.Create);
loopOverChangesAndDie();
FileCreation fc = (FileCreation)publisher.events.get(0);
assertThat(fc.path, is(path));
}
@Test
public void testDependencyTreeAllDeletes() throws Exception {
startWatchLoop();
// we should only delete because only resource 5 is set to be reloaded
// and it is not in the tree as resource one depends on it
given(resource5.removeOnReload()).willReturn(false);
// set up some dependencies, note a change, and verify!
resource1.addDependent(resource2);
resource2.addDependent(resource3);
resource2.addDependent(resource4);
resource3.addDependent(resource4);
resource5.addDependent(resource1);
changes.put(resource1.path(), FileWatcher.Action.Delete);
loopOverChangesAndDie();
assertThat(resourceCache.get(resource1.identifier()), is(nullValue()));
assertThat(resourceCache.get(resource2.identifier()), is(nullValue()));
assertThat(resourceCache.get(resource3.identifier()), is(nullValue()));
assertThat(resourceCache.get(resource4.identifier()), is(nullValue()));
assertThat(MyResource.class.cast(resourceCache.get(resource5.identifier())), is(resource5));
assertThat(taskRunner.tasks, is(empty()));
verifyZeroInteractions(resourceLoader);
verify(resource1).kill();
verify(resource2).kill();
verify(resource3).kill();
verify(resource4).kill();
verify(resource5, never()).kill();
assertThat(publisher.events.size(), is(9));
check((ResourceKilled)publisher.events.get(5), resource1);
check((ResourceKilled)publisher.events.get(6), resource2);
check((ResourceKilled)publisher.events.get(7), resource3);
check((ResourceKilled)publisher.events.get(8), resource4);
}
private void check(ResourceEvent event, MyResource resource) {
// because we're spying, the resource is a runtime subclass
assertTrue(event.type().isAssignableFrom(resource.getClass()));
}
@Test
public void testDependencyTreeWithReloads() throws Exception {
startWatchLoop();
// we should only delete because only resource 5 is set to be reloaded
// and it is not in the tree as resource one depends on it
given(resource5.removeOnReload()).willReturn(false);
// set up some dependencies, note a change, and verify!
resource1.addDependent(resource2);
resource2.addDependent(resource3);
resource2.addDependent(resource4);
resource3.addDependent(resource4);
resource4.addDependent(resource5);
changes.put(resource1.path(), FileWatcher.Action.Modify);
loopOverChangesAndDie();
assertThat(resourceCache.get(resource1.identifier()), is(nullValue()));
assertThat(resourceCache.get(resource2.identifier()), is(nullValue()));
assertThat(resourceCache.get(resource3.identifier()), is(nullValue()));
assertThat(resourceCache.get(resource4.identifier()), is(nullValue()));
assertThat(resourceCache.get(resource5.identifier()), is(nullValue()));
verify(resource1).kill();
verify(resource2).kill();
verify(resource3).kill();
verify(resource4).kill();
verify(resource5).kill();
assertThat(publisher.events.size(), is(10));
check((ResourceKilled) publisher.events.get(5), resource1);
check((ResourceKilled) publisher.events.get(6), resource2);
check((ResourceKilled) publisher.events.get(7), resource3);
check((ResourceKilled) publisher.events.get(8), resource4);
check((ResourceKilled) publisher.events.get(9), resource5);
verify(resourceLoader).loadResource(resource5.identifier());
verifyNoMoreInteractions(resourceLoader);
}
private JJTask<?> startWatchLoop() {
given(resourceWatchSwitch.runFileWatcher()).willReturn(true);
given(watcher.start()).willReturn(true);
loop.on((ServerStarting) null);
return taskRunner.tasks.remove(0);
}
private void loopOverChangesAndDie() throws Exception {
InterruptedException ie = new InterruptedException();
given(watcher.awaitChangedPaths()).willReturn(changes).willThrow(ie);
try {
loop.on((ServerStarting)null);
loop.run();
} catch (InterruptedException caught) {
assertTrue(ie == caught);
} finally {
loop.on((ServerStopping)null);
}
}
}