package jj.resource; import static java.util.concurrent.TimeUnit.SECONDS; import static jj.application.AppLocation.*; import static jj.server.ServerLocation.Virtual; import static org.junit.Assert.*; import static org.hamcrest.Matchers.*; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.attribute.FileTime; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.inject.Inject; import jj.App; import jj.ServerRoot; import jj.TreeDeleter; import jj.document.DocumentScriptEnvironment; import jj.document.HtmlResource; import jj.event.Listener; import jj.event.Subscriber; import jj.http.server.EmbeddedHttpRequest; import jj.http.server.EmbeddedHttpServer; import jj.script.module.JSONResource; import jj.script.module.ModuleScriptEnvironment; import jj.script.module.RequiredModule; import jj.script.module.ScriptResource; import jj.testing.JibbrJabbrTestServer; import jj.testing.Latch; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; /** * Validates the various complex behaviors of the resource system * @author jason * */ @Subscriber public class ResourceSystemIntegrationTest { @Inject ResourceFinder finder; @Inject EmbeddedHttpServer server; @Inject ResourceCache resourceCache; DocumentScriptEnvironment dse; HtmlResource htmlResource; ModuleScriptEnvironment mse1; ScriptResource scriptResource1; ModuleScriptEnvironment mse2; ScriptResource scriptResource2; ModuleScriptEnvironment mse3; JSONResource jsonResource1; String createDirectoriesOne = "created/1/directory/structure"; String createDirectoriesTwo = "created/1/other/structure"; String createDirectoriesThree = "created/2/hi/there"; @After public void after() throws Exception { // System.out.println("resourceCache = " + resourceCache); // System.out.println("reloadedCount = " + reloadedCount); // System.out.println("killedCount = " + killedCount); // System.out.println("loadedCount = " + loadedCount); try { Files.walkFileTree(App.module.resolve("created"), new TreeDeleter()); } catch (NoSuchFileException nsfe) { /* empty */ } } @Rule public JibbrJabbrTestServer app = new JibbrJabbrTestServer(ServerRoot.one, App.module) .withFileWatcher() .injectInstance(this); // done as a single test because at some point, the document system will be split off // and for now, the repl is a simple way to cause things to happen via the loader @Test public void testResourceSystem() throws Throwable { // validates that directory structures are created as expected DirectoryResource root = finder.findResource(DirectoryResource.class, AppBase, ""); DirectoryResource pub = finder.findResource(DirectoryResource.class, Public, ""); DirectoryResource pubDeep = finder.findResource(DirectoryResource.class, Public, "deep"); DirectoryResource priv = finder.findResource(DirectoryResource.class, Private, ""); DirectoryResource privDeep = finder.findResource(DirectoryResource.class, Private, "deep"); DirectoryResource nesting = finder.findResource(DirectoryResource.class, Private, "deep/nesting"); assertThat(root, is(notNullValue())); assertThat(pub, is(notNullValue())); //assertTrue(root.dependents().contains(pub)); assertThat(pubDeep, is(notNullValue())); assertTrue(pub.dependents().contains(pubDeep)); assertThat(priv, is(notNullValue())); //assertTrue(root.dependents().contains(priv)); assertThat(privDeep, is(notNullValue())); assertThat(nesting, is(notNullValue())); assertTrue(priv.dependents().contains(privDeep)); assertTrue(privDeep.dependents().contains(nesting)); assertThat(server.request(new EmbeddedHttpRequest("deep/nested")).await(1, SECONDS).status().code(), is(200)); dse = finder.findResource(DocumentScriptEnvironment.class, Virtual, "deep/nested"); htmlResource = finder.findResource(HtmlResource.class, Public, "deep/nested.html"); assertTrue(pubDeep.dependents().contains(htmlResource)); mse1 = finder.findResource(ModuleScriptEnvironment.class, Virtual, "deep/module", new RequiredModule(dse, "deep/module")); scriptResource1 = finder.findResource(ScriptResource.class, Private, "deep/module.js"); assertTrue(privDeep.dependents().contains(scriptResource1)); assertTrue(((AbstractResource<?>)dse).dependents().contains(mse1)); mse2 = finder.findResource(ModuleScriptEnvironment.class, Virtual, "deep/nesting/module", new RequiredModule(dse, "deep/nesting/module")); scriptResource2 = finder.findResource(ScriptResource.class, Private, "deep/nesting/module.js"); assertTrue(nesting.dependents().contains(scriptResource2)); assertTrue(((AbstractResource<?>)dse).dependents().contains(mse2)); mse3 = finder.findResource(ModuleScriptEnvironment.class, Virtual, "deep/nesting/values", new RequiredModule(dse, "deep/nesting/values")); jsonResource1 = finder.findResource(JSONResource.class, Private, "deep/nesting/values.json"); assertTrue(nesting.dependents().contains(jsonResource1)); assertTrue(((AbstractResource<?>)dse).dependents().contains(mse3)); assertTrue(dse.alive()); assertTrue(htmlResource.alive()); assertTrue(mse1.alive()); assertTrue(scriptResource1.alive()); assertTrue(mse2.alive()); assertTrue(scriptResource2.alive()); assertTrue(mse3.alive()); assertTrue(jsonResource1.alive()); assertThat(finder.findResource(DirectoryResource.class, AppBase, createDirectoriesOne), is(nullValue())); assertThat(finder.findResource(DirectoryResource.class, AppBase, createDirectoriesTwo), is(nullValue())); assertThat(finder.findResource(DirectoryResource.class, AppBase, createDirectoriesThree), is(nullValue())); final AtomicBoolean failed = new AtomicBoolean(); new Thread() { public void run() { try { // touch a script and wait for a reload event. touch(scriptResource2); // and let's add some directories Files.createDirectories(App.module.resolve(createDirectoriesOne)); Files.createDirectories(App.module.resolve(createDirectoriesTwo)); Files.createDirectories(App.module.resolve(createDirectoriesThree)); } catch (Exception e) { e.printStackTrace(); failed.set(true); } } }.start(); // on the Mac, you will wait a while..., so if it's time to // make this test more things, try to only have this // single wait point! // wait count is 9 for the directories created above // + 5 kills from scriptResource2, mse3, mse2, mse1, and dse // + 1 reload of dse. note that scriptResource1 and jsonResource1 and // htmlResource are left alone in the cache, and that the // modules don't load again without another request waitForCount(9 + 5 + 1); assertFalse("couldn't update things correctly", failed.get()); assertFalse(scriptResource2.alive()); // should be removed upon death assertFalse(nesting.dependents().contains(scriptResource2)); assertFalse(mse3.alive()); assertFalse(mse2.alive()); assertFalse(mse1.alive()); assertFalse(dse.alive()); assertTrue(jsonResource1.alive()); assertTrue(scriptResource1.alive()); assertTrue(htmlResource.alive()); assertThat( finder.findResource(DocumentScriptEnvironment.class, Virtual, "deep/nested"), is(notNullValue()) ); assertThat(dse, is(not(sameInstance( finder.findResource(DocumentScriptEnvironment.class, Virtual, "deep/nested") )))); assertThat( finder.findResource(ModuleScriptEnvironment.class, Virtual, "deep/module", new RequiredModule(dse, "deep/module")), is(nullValue()) ); assertThat( finder.findResource(ModuleScriptEnvironment.class, Virtual, "deep/nesting/module", new RequiredModule(dse, "deep/nesting/module")), is(nullValue()) ); assertThat( finder.findResource(ModuleScriptEnvironment.class, Virtual, "deep/nesting/values", new RequiredModule(dse, "deep/nesting/values")), is(nullValue()) ); assertThat(finder.findResource(DirectoryResource.class, AppBase, createDirectoriesOne), is(notNullValue())); assertThat(finder.findResource(DirectoryResource.class, AppBase, createDirectoriesTwo), is(notNullValue())); assertThat(finder.findResource(DirectoryResource.class, AppBase, createDirectoriesThree), is(notNullValue())); } // also need a delete test! private void touch(FileResource<?> resource) throws Exception { FileTime originalFileTime = Files.getLastModifiedTime(resource.path()); FileTime newFileTime; do { newFileTime = FileTime.fromMillis(System.currentTimeMillis()); } while (newFileTime.compareTo(originalFileTime) < 1); Files.setLastModifiedTime(resource.path(), newFileTime); } // small strictly ordered waiting mechanism // also tests out the events indirectly boolean countEvents = false; Latch latch; AtomicInteger reloadedCount; AtomicInteger killedCount; AtomicInteger loadedCount; @Before public void before() { reloadedCount = new AtomicInteger(); killedCount = new AtomicInteger(); loadedCount = new AtomicInteger(); } @Listener void on(ResourceReloaded event) { reloadedCount.incrementAndGet(); if (countEvents) { latch.countDown(); } } @Listener void on(ResourceKilled event) { killedCount.incrementAndGet(); if (countEvents) { latch.countDown(); } } @Listener void on(ResourceLoaded event) { if (loadedCount != null) loadedCount.incrementAndGet(); if (countEvents) { latch.countDown(); } } private void waitForCount(int count) throws Exception { latch = new Latch(count); countEvents = true; latch.await(4, SECONDS); } }