diff --git a/framework/src/play/test/FunctionalTest.java b/framework/src/play/test/FunctionalTest.java index f4c144b4b4..0f4a0fde0f 100644 --- a/framework/src/play/test/FunctionalTest.java +++ b/framework/src/play/test/FunctionalTest.java @@ -31,12 +31,16 @@ import play.Invoker; import play.Invoker.InvocationContext; import play.classloading.enhancers.ControllersEnhancer.ControllerInstrumentation; +import play.exceptions.JavaExecutionException; +import play.exceptions.UnexpectedException; import play.libs.F.Action; import play.mvc.ActionInvoker; import play.mvc.Controller; import play.mvc.Http; import play.mvc.Http.Request; import play.mvc.Http.Response; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import play.mvc.Router.ActionDefinition; import play.mvc.Scope.RenderArgs; @@ -65,7 +69,7 @@ public static Response GET(Object url) { /** * sends a GET request to the application under tests. - * + * * @param url * relative url such as "/products/1234" * @param followRedirect @@ -94,7 +98,7 @@ public static Response GET(Object url, boolean followRedirect) { /** * Sends a GET request to the application under tests. - * + * * @param request * The given request * @param url @@ -144,7 +148,7 @@ public static Response POST(Object url, String contenttype, InputStream body) { /** * Sends a POST request to the application under tests. - * + * * @param request * The given request * @param url @@ -178,7 +182,7 @@ public static Response POST(Request request, Object url, String contenttype, Inp /** * Sends a POST request to the application under tests as a multipart form. Designed for file upload testing. - * + * * @param url * relative url such as "/products/1234" * @param parameters @@ -243,7 +247,7 @@ public static Response PUT(Object url, String contenttype, String body) { /** * Sends a PUT request to the application under tests. - * + * * @param request * The given request * @param url @@ -281,7 +285,7 @@ public static Response DELETE(String url) { /** * Sends a DELETE request to the application under tests. - * + * * @param request * The given request * @param url @@ -310,7 +314,7 @@ public static Response DELETE(Request request, Object url) { public static void makeRequest(final Request request, final Response response) { final CountDownLatch actionCompleted = new CountDownLatch(1); - TestEngine.functionalTestsExecutor.submit(new Invoker.Invocation() { + final Future invocationResult = TestEngine.functionalTestsExecutor.submit(new Invoker.Invocation() { @Override public void execute() throws Exception { @@ -353,9 +357,27 @@ public InvocationContext getInvocationContext() { }); try { + // We can not simply wait on the future result because of how continuations + // are implemented. Only when the latch is counted down the action is really + // completed. Therefore, wait on the latch. if (!actionCompleted.await(30, TimeUnit.SECONDS)) { throw new TimeoutException("Request did not complete in time"); } + } catch (Exception ex) { + throw new RuntimeException(ex); + } + try { + // We still call this to raise any exception that might have + // occurred during execution of the invocation. + invocationResult.get(); + } + catch (ExecutionException e) { + throw unwrapOriginalException(e); + } + catch (Exception e) { + throw new RuntimeException(e); + } + try { if (savedCookies == null) { savedCookies = new HashMap<>(); } @@ -377,6 +399,21 @@ public InvocationContext getInvocationContext() { } } + private static RuntimeException unwrapOriginalException(final ExecutionException e) { + // Check if the original exceptions fits the usual patterns. If yes, throw the very + // original runtime exception + final Throwable executionCause = e.getCause(); + if (executionCause != null + && (executionCause instanceof JavaExecutionException || executionCause instanceof UnexpectedException)) { + final Throwable originalCause = executionCause.getCause(); + if (originalCause != null && originalCause instanceof RuntimeException) { + throw (RuntimeException) originalCause; + } + } + // As a last fallback, just wrap everything up + return new RuntimeException(e); + } + public static Response makeRequest(Request request) { Response response = newResponse(); makeRequest(request, response); @@ -436,7 +473,7 @@ public static Request newRequest() { // Assertions /** * Asserts a 2OO Success response - * + * * @param response * server response */ @@ -446,7 +483,7 @@ public static void assertIsOk(Response response) { /** * Asserts a 404 (not found) response - * + * * @param response * server response */ @@ -456,7 +493,7 @@ public static void assertIsNotFound(Response response) { /** * Asserts response status code - * + * * @param status * expected HTTP response code * @param response @@ -468,7 +505,7 @@ public static void assertStatus(int status, Response response) { /** * Exact equality assertion on response body - * + * * @param content * expected body content * @param response @@ -480,7 +517,7 @@ public static void assertContentEquals(String content, Response response) { /** * Asserts response body matched a pattern or contains some text. - * + * * @param pattern * a regular expression pattern or a regular text, ( which must be escaped using Pattern.quote) * @param response @@ -495,7 +532,7 @@ public static void assertContentMatch(String pattern, Response response) { /** * Verify response charset encoding, as returned by the server in the Content-Type header. Be aware that if no * charset is returned, assertion will fail. - * + * * @param charset * expected charset encoding such as "utf-8" or "iso8859-1". * @param response @@ -509,7 +546,7 @@ public static void assertCharset(String charset, Response response) { /** * Verify the response content-type - * + * * @param contentType * expected content-type without any charset extension, such as "text/html" * @param response @@ -524,7 +561,7 @@ public static void assertContentType(String contentType, Response response) { /** * Exact equality assertion on a response header value - * + * * @param headerName * header to verify. case-insensitive * @param value @@ -539,7 +576,7 @@ public static void assertHeaderEquals(String headerName, String value, Response /** * obtains the response body as a string - * + * * @param response * server response * @return the response body as an utf-8 string diff --git a/samples-and-tests/just-test-cases/app/controllers/StatusCodes.java b/samples-and-tests/just-test-cases/app/controllers/StatusCodes.java new file mode 100644 index 0000000000..2be98462a2 --- /dev/null +++ b/samples-and-tests/just-test-cases/app/controllers/StatusCodes.java @@ -0,0 +1,36 @@ +package controllers; + +import play.jobs.Job; +import play.mvc.Controller; +import play.mvc.Http.Response; + +public class StatusCodes extends Controller { + + public static void justOkay() { + renderText("Okay"); + } + + public static void rendersNotFound() { + notFound(); + } + + public static void rendersUnauthorized() { + unauthorized(); + } + + public static void usesContinuation() { + final String text = await(new Job() { + @Override + public String doJobWithResult() throws Exception { + return "Job completed successfully"; + } + }.now()); + Response.current().status = Integer.valueOf(201); + renderText(text); + } + + public static void throwsException() throws Exception { + throw new UnsupportedOperationException("Whoops"); + } + +} diff --git a/samples-and-tests/just-test-cases/app/controllers/Transactional.java b/samples-and-tests/just-test-cases/app/controllers/Transactional.java index 724bee0463..f24da190ae 100644 --- a/samples-and-tests/just-test-cases/app/controllers/Transactional.java +++ b/samples-and-tests/just-test-cases/app/controllers/Transactional.java @@ -19,9 +19,8 @@ public static void readOnlyTest() { tag2.name = "TransactionalTest"; post.tags.add(tag1); post.tags.add(tag2); - post.save(); - // since this is read only the count will not go up with successive - // calls as the Post we just stored will be rolled back + post.save(); // since this is read only the request will fail with javax.persistence.TransactionRequiredException + renderText("Wrote 1 post: total is now " + Post.count()); } diff --git a/samples-and-tests/just-test-cases/app/controllers/WithContinuations.java b/samples-and-tests/just-test-cases/app/controllers/WithContinuations.java index 643fc5caf5..0e1f3eaaed 100644 --- a/samples-and-tests/just-test-cases/app/controllers/WithContinuations.java +++ b/samples-and-tests/just-test-cases/app/controllers/WithContinuations.java @@ -163,14 +163,14 @@ public static void rollbackWithContinuationsThatWorks() { public static void streamedResult() { response.contentType = "text/html"; - response.writeChunk("

This page should load progressively in about 3 second

"); + response.writeChunk("

This page should load progressively in about a few seconds

"); long s = System.currentTimeMillis(); for(int i=0; i<100; i++) { await(10); response.writeChunk("

Hello " + i + "

"); } long t = System.currentTimeMillis() - s; - response.writeChunk("Time: " + t + ", isOk->" + (t > 1000 && t < 10000)); + response.writeChunk("Time: " + t + ", isOk->" + (t > 1000 && t < 25000)); } public static void loopWithCallback() { @@ -206,7 +206,7 @@ public void invoke() { await(10, this); } else { long t = System.currentTimeMillis() - s.get(); - response.writeChunk("Time: " + t + ", isOk->" + (t > 1000 && t < 10000)); + response.writeChunk("Time: " + t + ", isOk->" + (t > 1000 && t < 25000)); } } }; diff --git a/samples-and-tests/just-test-cases/conf/routes b/samples-and-tests/just-test-cases/conf/routes index f253ba559d..fe401ed95d 100644 --- a/samples-and-tests/just-test-cases/conf/routes +++ b/samples-and-tests/just-test-cases/conf/routes @@ -5,8 +5,8 @@ GET / Application.index PUT /sayHello Application.hello GET /aGetForm Application.aGetForm -GET /aGetForm/ Application.aGetForm2 -GET /optionalSlash/? Application.optional +GET /aGetForm/ Application.aGetForm2 +GET /optionalSlash/? Application.optional GET /index Application.index GET /re/{<[a-z]+>re} Application.ok @@ -83,5 +83,11 @@ GET /databinding/changeLanguage/{lang}/? DataBinding.changeLa GET /useAwaitViaOtherClass WithContinuations.ControllerWithoutContinuations.useAwaitViaOtherClass +GET /status/ok/ StatusCodes.justOkay +GET /status/not-found/ StatusCodes.rendersNotFound +GET /status/unauthorized/ StatusCodes.rendersUnauthorized +POST /status/job/ StatusCodes.usesContinuation +GET /status/failure/ StatusCodes.throwsException + # module * / module:secure diff --git a/samples-and-tests/just-test-cases/test/BinaryTest.java b/samples-and-tests/just-test-cases/test/BinaryTest.java index beaf21c626..d4ec910663 100755 --- a/samples-and-tests/just-test-cases/test/BinaryTest.java +++ b/samples-and-tests/just-test-cases/test/BinaryTest.java @@ -28,7 +28,7 @@ public void setUp() { Response deletedResponse = GET(deleteURL); assertStatus(200, deletedResponse); } - + @Test public void testUploadSomething() { URL imageURL = reverse(); { @@ -235,11 +235,13 @@ public void testGetEmptyBinary() { assertTrue(Binary.emptyInputStreamClosed); } - @Test + @Test(expected = Exception.class) public void testGetErrorBinary() { - Response response = GET("/binary/getErrorBinary"); - // This does not work. See Lighthouse ticket #1637. - // assertStatus(500, response); - assertTrue(Binary.errorInputStreamClosed); + try { + GET("/binary/getErrorBinary"); + } + finally { + assertTrue(Binary.errorInputStreamClosed); + } } } diff --git a/samples-and-tests/just-test-cases/test/FunctionalTestTest.java b/samples-and-tests/just-test-cases/test/FunctionalTestTest.java index 64e87c85ab..08d80be97f 100644 --- a/samples-and-tests/just-test-cases/test/FunctionalTestTest.java +++ b/samples-and-tests/just-test-cases/test/FunctionalTestTest.java @@ -3,18 +3,19 @@ import play.test.*; import play.libs.WS; import play.mvc.Http.*; +import play.mvc.results.*; import models.*; import java.util.HashMap; public class FunctionalTestTest extends FunctionalTest { - + @org.junit.Before public void setUp() throws Exception { Fixtures.deleteDatabase(); Fixtures.loadModels("users.yml"); } - + @Test public void testAndCall() { assertEquals(2, User.count()); @@ -37,14 +38,14 @@ public void twoCalls() { response = GET("/jpacontroller/show"); assertIsOk(response); } - + @Test public void usingTransaction() { Response response = GET("/users/list"); assertIsOk(response); assertContentEquals("2", response); } - + @Test public void usingTransaction2() { new User("Bob").create(); @@ -54,7 +55,7 @@ public void usingTransaction2() { User bob = User.find("byName", "Bob").first(); assertNotNull(bob); } - + @Test public void usingTransaction3() { Response response = POST("/users/newUser?name=Kiki"); @@ -85,33 +86,33 @@ public void makeSureCookieSaved(){ assertIsOk(response); assertEquals("Is it keeping saved?", response.cookies.get("PLAY_TEST").value); } - + public static class AnotherInnerTest extends UnitTest { - + @Test public void hello() { assertEquals(2, 1+1); } - + } - + @Test public void usingRedirection() { Response response = GET("/users/redirectToIndex"); assertStatus( 302, response); String location = response.headers.get("Location").value(); - + response = GET( location ); assertIsOk(response); - + response = POST("/users/redirectToIndex"); assertStatus( 302, response); location = response.headers.get("Location").value(); - + response = POST( location ); assertIsOk(response); } - + @Test public void canGetRenderArgs() { Response response = GET("/users/edit"); @@ -120,13 +121,70 @@ public void canGetRenderArgs() { User u = (User) renderArgs("u"); assertEquals("Guillaume", u.name); } - - @Test + + @Test(expected = RenderStatic.class) public void testGettingStaticFile() { - Response response = GET("http://localhost:9003/public/session.test?req=1"); + Response response = GET("/public/session.test?req=1"); assertIsOk(response); } + /** + * A simple call that should always work. + */ + @Test + public void testOk() { + final Response response = GET("/status/ok/"); + assertStatus(200, response); + assertContentEquals("Okay", response); + } + + /** + * When a route is called that is not even defined, an exception is expected. + */ + @Test(expected = NotFound.class) + public void testNoRoute() { + GET("/status/route-not-defined/"); + } + + /** + * When a defined route is called but the controller decides to render a 404, + * the test code is expected to pass and we can assert on the status. + */ + @Test + public void testNotFound() { + final Response response = GET("/status/not-found/"); + assertStatus(404, response); + } + + /** + * When a controller throws a normal exception, an exception is expected in + * the test method as well. + */ + @Test(expected = UnsupportedOperationException.class) + public void testFailure() { + GET("/status/failure/"); + } + + /** + * When a controller renders a non-standard result code (which is, actually, implemented + * through exception), the call is expected to pass and we can assert on the status. + */ + @Test + public void testUnauthorized() { + final Response response = GET("/status/unauthorized/"); + assertStatus(401, response); + } + + /** + * Even when a controller makes use of continuations, e.g. by calling and waiting for a + * job, it is expected that we can assert on the status code. + */ + @Test + public void testContinuationCustomStatus() { + final Response response = POST("/status/job/"); + assertStatus(201, response); + } + /** * This is a regression test for [#2140], which is a bug in FunctionalTest that prevented it from * testing a controller action that uses {@link Response#writeChunk(Object)}. @@ -139,5 +197,15 @@ public void testWriteChunks() { assertContentType("text/plain", response); assertContentEquals("abcæøåæøå", response); } -} + /** + * Even when a controller makes use of continuations, e.g. by calling and waiting for a + * job, it is expected that we can assert on the content. + */ + @Test + public void testContinuationContent() { + final Response response = POST("/status/job/"); + assertContentEquals("Job completed successfully", response); + } + +} diff --git a/samples-and-tests/just-test-cases/test/TransactionalJPATest.java b/samples-and-tests/just-test-cases/test/TransactionalJPATest.java index a248829236..452ad760c4 100644 --- a/samples-and-tests/just-test-cases/test/TransactionalJPATest.java +++ b/samples-and-tests/just-test-cases/test/TransactionalJPATest.java @@ -2,16 +2,13 @@ import play.mvc.Http.Response; import play.test.FunctionalTest; +import javax.persistence.TransactionRequiredException; public class TransactionalJPATest extends FunctionalTest { - @Test + @Test(expected = TransactionRequiredException.class) public void testImport() { Response response = GET("/Transactional/readOnlyTest"); - assertIsOk(response); - response = GET("/Transactional/echoHowManyPosts"); - assertIsOk(response); - assertEquals("There are 0 posts", getContent(response)); } @Test