/* * 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.testing; import static jj.testing.HttpTraceMode.*; import io.netty.util.ResourceLeakDetector; import io.netty.util.ResourceLeakDetector.Level; import io.netty.util.internal.logging.InternalLoggerFactory; import io.netty.util.internal.logging.Slf4JLoggerFactory; import java.nio.file.Path; import java.util.ArrayList; import javax.inject.Inject; import javax.inject.Singleton; import jj.JJModule; import jj.webdriver.WebDriverProvider; import jj.webdriver.WebDriverRule; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Stage; /** * <p> * Test rule to encapsulate a JibbrJabbr server. * * <p> * The server is stood up immediately before each test * method, and torn down immediately after. * * <p> * There are some fluent configuration methods * * * @author jason * */ public class JibbrJabbrTestServer implements TestRule { static { ResourceLeakDetector.setLevel(Level.PARANOID); InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()); // need to create a test logger that searches the log output for leaks // and fail the test and kill the world } private HttpTraceMode mode = Nothing; private final Path rootPath; private final Path appPath; private boolean fileWatcher = false; private boolean httpServer = false; private int httpPort = 0; private boolean runAllSpecs = false; private Object instance; ArrayList<JJModule> modules; private Injector injector; /** * Construct a test server, passing the appPath as * the app parameter. * @param appPath */ public JibbrJabbrTestServer(final Path rootPath, final Path appPath) { this.rootPath = rootPath; this.appPath = appPath; } public JibbrJabbrTestServer verifying() { assertNotStarted(); mode = Verifying; return this; } public JibbrJabbrTestServer recording() { assertNotStarted(); mode = Recording; return this; } public JibbrJabbrTestServer withFileWatcher() { assertNotStarted(); fileWatcher = true; return this; } public JibbrJabbrTestServer withHttp() { assertNotStarted(); httpServer = true; return this; } public JibbrJabbrTestServer withHttpOnPort(int port) { assert (port > 1023 && port < 65536) : "http port must be between 1024-65535 inclusive"; assertNotStarted(); httpServer = true; httpPort = port; return this; } public JibbrJabbrTestServer runAllSpecs() { assertNotStarted(); runAllSpecs = true; return this; } public JibbrJabbrTestServer injectInstance(Object instance) { assertNotStarted(); this.instance = instance; return this; } public JibbrJabbrTestServer withModule(JJModule module) { assertNotStarted(); if (modules == null) { modules = new ArrayList<>(1); // probably } modules.add(module); return this; } public WebDriverRule webDriverRule(Class<? extends WebDriverProvider> webDriverProvider) { assertNotStarted(); if (!httpServer) { // todo - pick a local open port withHttpOnPort(8080); } WebDriverRule result = new WebDriverRule().driverProvider(webDriverProvider); if (httpPort != 0) { result.baseUrl("http://0.0.0.0:" + httpPort); } return result; } public int httpPort() { return httpPort; } private void assertNotStarted() { assert injector == null : "server must be configured outside of runs!"; } /** * statement that injects a test instance * @author jason * */ @Singleton private static class TestInjectionStatement extends JibbrJabbrTestStatement { private final Injector injector; private final JibbrJabbrTestServer serverRule; @Inject TestInjectionStatement(final Injector injector, final JibbrJabbrTestServer serverRule) { this.injector = injector; this.serverRule = serverRule; } @Override public void evaluate() throws Throwable { injector.injectMembers(serverRule.instance); evaluateInner(); } } /** * statement that manages the test injector, and sets its children up * @author jason * */ private class InjectorManagerStatement extends JibbrJabbrTestStatement { InjectorManagerStatement(TestMethodStatement baseStatement) { ServerLifecycleStatement statement = injector.getInstance(ServerLifecycleStatement.class); statement.inner(baseStatement); if (httpServer) { statement.inner(injector.getInstance(HttpServerStatement.class)); } inner(statement); } @Override public void evaluate() throws Throwable { try { injector.injectMembers(JibbrJabbrTestServer.this); evaluateInner(); } finally { injector = null; } } } /** * innermost statement that executes the test method. this is just a debugging wrapper * @author jason * */ private static class TestMethodStatement extends JibbrJabbrTestStatement { private final Statement base; TestMethodStatement(Statement base) { this.base = base; } @Override public void evaluate() throws Throwable { base.evaluate(); } } @Override public Statement apply(final Statement base, final Description description) { ArrayList<String> argBuilder = new ArrayList<>(); argBuilder.add("server-root=" + rootPath); argBuilder.add("app=" + appPath); argBuilder.add("fileWatcher=" + fileWatcher); argBuilder.add("httpServer=" + httpServer); argBuilder.add("runAllSpecs=" + runAllSpecs); argBuilder.add("http-trace-mode=" + mode); if (httpPort > 1023 && httpPort < 65536) { argBuilder.add("httpPort=" + httpPort); } injector = Guice.createInjector( Stage.PRODUCTION, new TestModule(this, argBuilder.toArray(new String[argBuilder.size()]), description, httpServer) ); JibbrJabbrTestStatement statement = new InjectorManagerStatement(new TestMethodStatement(base)); if (instance != null) { statement.inner(injector.getInstance(TestInjectionStatement.class)); } statement = mode.traceStatement(statement, description.getClassName() + "." + description.getMethodName()); return statement; } /** * @return The base URL of the HTTP server */ public String baseUrl() { return "http://localhost:" + (httpPort > 1023 && httpPort < 65536 ? httpPort : 8080); } }