/* * Copyright (c) 2014 the original author or authors * * 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 io.werval.gradle; import io.werval.runtime.util.Holder; import io.werval.util.InputStreams; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.util.concurrent.Callable; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.DefaultHttpClient; import org.gradle.testkit.functional.ExecutionResult; import org.gradle.testkit.functional.GradleRunner; import org.gradle.testkit.functional.GradleRunnerFactory; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import static com.jayway.awaitility.Awaitility.await; import static io.werval.api.BuildVersion.VERSION; import static io.werval.util.InputStreams.BUF_SIZE_4K; import static java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.SECONDS; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.core.StringContains.containsString; import static org.junit.Assert.assertThat; /** * Assert that the {@literal secret}, {@literal start} and {@literal devshell} tasks execute successfuly. * * Generates a project, run it in dev mode using the plugin, change source code and assert code is reloaded. * <p> * As this test spawn several Gradle Daemons it ends by killing them all to leave the running system in a proper state. */ public class ApplicationPluginIntegTest extends AbstractPluginIntegTest { private static final Charset UTF_8 = Charset.forName( "UTF-8" ); private static final String BUILD; private static final String ROUTES; private static final String CONFIG; private static final String CONFIG_DEV; private static final String CONTROLLER; private static final String CONTROLLER_CHANGED; private static final String CONTROLLER_EXCEPTIONAL; private static final String CONTROLLER_BUILD_ERROR; static { String localRepo = new File( "../repository" ).getAbsolutePath(); BUILD = "\n" + "buildscript {\n" + " repositories {\n" + " maven { url 'file://" + localRepo + "' }\n" + " jcenter()\n" + " }\n" + " dependencies { classpath 'io.werval:io.werval.gradle:" + VERSION + "' }\n" + "}\n" + "repositories { maven { url 'file://" + localRepo + "' } }\n" + "apply plugin: \"io.werval.application\"\n" + "dependencies {\n" + " runtime 'ch.qos.logback:logback-classic:1.1.2'\n" + "}\n" + "sourceSets {\n" + " custom\n" + " main {\n" + " output.dir( 'build/SUPPLEMENTARY_OUTPUT' )\n" + " }\n" + "}\n" + "classes.dependsOn customClasses\n" + "devshell {\n" + " openBrowser = false\n" + " sourceSets += project.sourceSets.custom\n" + " configResource = 'development.conf'\n" + "}\n" + "start {\n" + " sourceSets += project.sourceSets.custom\n" + " configResource = 'application-custom.conf'\n" + "}\n" + "\n"; CONFIG = "\n" + "app.secret = e6bcdba3bc6840aa08013ef20505a0c27f800dbbcced6fbb71e8cf197fe83866\n" + "tag = custom\n"; CONFIG_DEV = "include \"application-custom.conf\"\ntag = development\n"; ROUTES = "GET / controllers.Application.index"; CONTROLLER = "\n" + "package controllers;\n" + "import io.werval.api.outcomes.Outcome;\n" + "import static io.werval.api.context.CurrentContext.application;\n" + "import static io.werval.api.context.CurrentContext.outcomes;\n" + "public class Application\n" + "{\n" + " public Outcome index()\n" + " {\n" + " return outcomes().ok( \"I ran! \" + application().config().string( \"tag\" ) ).build();\n" + " }\n" + "}\n"; CONTROLLER_CHANGED = "\n" + "package controllers;\n" + "import io.werval.api.outcomes.Outcome;\n" + "import static io.werval.api.context.CurrentContext.outcomes;\n" + "public class Application\n" + "{\n" + " public Outcome index()\n" + " {\n" + " return outcomes().ok( \"I ran changed!\" ).build();\n" + " }\n" + "}\n"; CONTROLLER_EXCEPTIONAL = "\n" + "package controllers;\n" + "import io.werval.api.outcomes.Outcome;\n" + "import static io.werval.api.context.CurrentContext.outcomes;\n" + "public class Application\n" + "{\n" + " public Outcome index()\n" + " {\n" + " throw new RuntimeException( \"I throwed!\" );\n" + " }\n" + "}\n"; CONTROLLER_BUILD_ERROR = "\n" + "package controllers;\n" + "import io.werval.api.outcomes.Outcome;\n" + "import static io.werval.api.context.CurrentContext.outcomes;\n" + "public class Application\n" + "{\n" + " public Outcome index()\n" + " {\n" + " I FAILED TO COMPILE!\n" + " }\n" + "}\n"; new File( "build/tmp/it" ).mkdirs(); } private final File devshellLock = new File( ".devshell.lock" ); @Rule public TemporaryFolder tmp = new TemporaryFolder( new File( "build/tmp/it" ) ) { @Override public void delete() { super.delete(); } }; @Before public void setupProjectLayout() throws IOException { Files.write( new File( tmp.getRoot(), "build.gradle" ).toPath(), BUILD.getBytes( UTF_8 ) ); File dev = new File( tmp.getRoot(), "src/dev/resources" ); Files.createDirectories( dev.toPath() ); Files.write( new File( dev, "development.conf" ).toPath(), CONFIG_DEV.getBytes( UTF_8 ) ); File custom = new File( tmp.getRoot(), "src/custom/resources" ); Files.createDirectories( custom.toPath() ); Files.write( new File( custom, "application-custom.conf" ).toPath(), CONFIG.getBytes( UTF_8 ) ); File resources = new File( tmp.getRoot(), "src/main/resources" ); Files.createDirectories( resources.toPath() ); Files.write( new File( resources, "routes.conf" ).toPath(), ROUTES.getBytes( UTF_8 ) ); File controllers = new File( tmp.getRoot(), "src/main/java/controllers" ); Files.createDirectories( controllers.toPath() ); Files.write( new File( controllers, "Application.java" ).toPath(), CONTROLLER.getBytes( UTF_8 ) ); } @After public void cleanupDevShellLock() throws Exception { if( devshellLock.exists() ) { Files.delete( devshellLock.toPath() ); } } @Test public void secretTaskIntegrationTest() throws IOException { GradleRunner runner = GradleRunnerFactory.create(); runner.setDirectory( tmp.getRoot() ); runner.setArguments( asList( "secret" ) ); ExecutionResult result = runner.run(); assertThat( result.getStandardOutput(), containsString( "Generate new Werval Application Secret" ) ); } @Test public void devshellTaskIntegrationTest() throws InterruptedException, IOException { final Holder<Exception> errorHolder = new Holder<>(); Thread devshellThread = new Thread( newRunnable( errorHolder, "devshell" ), "gradle-werval-devshell-thread" ); try { devshellThread.start(); await().atMost( 60, SECONDS ).until( new Callable<Boolean>() { @Override public Boolean call() throws Exception { return devshellLock.exists(); } } ); if( errorHolder.isSet() ) { throw new RuntimeException( "Error during werval:devshell invocation: " + errorHolder.get().getMessage(), errorHolder.get() ); } final HttpClient client = new DefaultHttpClient(); final HttpGet get = new HttpGet( "http://localhost:23023/" ); final ResponseHandler<String> successHandler = new BasicResponseHandler(); await().atMost( 60, SECONDS ).pollInterval( 5, SECONDS ).until( new Callable<String>() { @Override public String call() throws Exception { try { return client.execute( get, successHandler ); } catch( Exception ex ) { return null; } } }, allOf( containsString( "I ran!" ), containsString( "development" ) ) ); // Source code change Files.write( new File( tmp.getRoot(), "src/main/java/controllers/Application.java" ).toPath(), CONTROLLER_CHANGED.getBytes( UTF_8 ) ); Thread.sleep( 2000 ); // Wait for source code change to be detected assertThat( client.execute( get, successHandler ), containsString( "I ran changed!" ) ); // Exception Files.write( new File( tmp.getRoot(), "src/main/java/controllers/Application.java" ).toPath(), CONTROLLER_EXCEPTIONAL.getBytes( UTF_8 ) ); Thread.sleep( 2000 ); // Wait for source code change to be detected HttpResponse response = client.execute( get ); int code = response.getStatusLine().getStatusCode(); String body = InputStreams.readAllAsString( response.getEntity().getContent(), BUF_SIZE_4K, UTF_8 ); assertThat( code, is( 500 ) ); assertThat( body, containsString( "I throwed!" ) ); // Build error Files.write( new File( tmp.getRoot(), "src/main/java/controllers/Application.java" ).toPath(), CONTROLLER_BUILD_ERROR.getBytes( UTF_8 ) ); Thread.sleep( 2000 ); // Wait for source code change to be detected response = client.execute( get ); code = response.getStatusLine().getStatusCode(); body = InputStreams.readAllAsString( response.getEntity().getContent(), BUF_SIZE_4K, UTF_8 ); assertThat( code, is( 500 ) ); assertThat( body, containsString( "I FAILED TO COMPILE!" ) ); // Back to normal Files.write( new File( tmp.getRoot(), "src/main/java/controllers/Application.java" ).toPath(), CONTROLLER.getBytes( UTF_8 ) ); Thread.sleep( 2000 ); // Wait for source code change to be detected assertThat( client.execute( get, successHandler ), containsString( "I ran!" ) ); // Werval Documentation assertThat( client.execute( new HttpGet( "http://localhost:23023/@doc" ), successHandler ), containsString( "Werval Documentation" ) ); client.getConnectionManager().shutdown(); } finally { devshellThread.interrupt(); } } @Test public void startTaskIntegrationTest() throws InterruptedException, IOException { final Holder<Exception> errorHolder = new Holder<>(); Thread runThread = new Thread( newRunnable( errorHolder, "start" ), "gradle-werval-start-thread" ); try { runThread.start(); final HttpClient client = new DefaultHttpClient(); final HttpGet get = new HttpGet( "http://localhost:23023/" ); final ResponseHandler<String> handler = new BasicResponseHandler(); await().atMost( 60, SECONDS ).pollInterval( 5, SECONDS ).until( new Callable<String>() { @Override public String call() throws Exception { try { return client.execute( get, handler ); } catch( Exception ex ) { return null; } } }, allOf( containsString( "I ran!" ), containsString( "custom" ) ) ); client.getConnectionManager().shutdown(); if( errorHolder.isSet() ) { throw new RuntimeException( "Error during werval:start invocation: " + errorHolder.get().getMessage(), errorHolder.get() ); } } finally { runThread.interrupt(); } } private Runnable newRunnable( final Holder<Exception> errorHolder, final String task ) { return new Runnable() { @Override public void run() { try { GradleRunner runner = GradleRunnerFactory.create(); runner.setDirectory( tmp.getRoot() ); runner.setArguments( asList( "--debug", "--stacktrace", task ) ); runner.run(); } catch( Exception ex ) { errorHolder.set( ex ); } } }; } }