/*
* 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 );
}
}
};
}
}