| /* |
| * Copyright 2011 Google Inc. |
| * |
| * 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 com.google.web.bindery.requestfactory.gwt.client; |
| |
| import com.google.web.bindery.event.shared.UmbrellaException; |
| import com.google.web.bindery.requestfactory.shared.Receiver; |
| import com.google.web.bindery.requestfactory.shared.RequestContext; |
| import com.google.web.bindery.requestfactory.shared.ServerFailure; |
| import com.google.web.bindery.requestfactory.shared.SimpleFooProxy; |
| import com.google.web.bindery.requestfactory.shared.SimpleFooRequest; |
| |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| import javax.validation.ConstraintViolation; |
| |
| /** |
| * Tests that an exception thrown by a {@link Receiver} does not prevent other |
| * {@link Receiver}s from being called. |
| */ |
| public class RequestFactoryExceptionPropagationTest extends |
| RequestFactoryTestBase { |
| /* |
| * DO NOT USE finishTest(). Instead, call finishTestAndReset(); |
| */ |
| |
| private class CountingReceiver extends Receiver<Object> { |
| int constraintViolationCallCount; |
| int failureCallCount; |
| int successCallCount; |
| int violationCallCount; |
| |
| public void assertCounts(int expectedFailureCallCount, |
| int expectedSuccessCallCount, int expectedViolationCallCount) { |
| assertEquals(expectedFailureCallCount, failureCallCount); |
| assertEquals(expectedSuccessCallCount, successCallCount); |
| assertEquals(expectedViolationCallCount, constraintViolationCallCount); |
| assertEquals(expectedViolationCallCount, violationCallCount); |
| } |
| |
| @Override |
| public void onConstraintViolation(Set<ConstraintViolation<?>> errors) { |
| constraintViolationCallCount++; |
| // Forward to onViolation |
| super.onConstraintViolation(errors); |
| } |
| |
| @Override |
| public void onFailure(ServerFailure error) { |
| failureCallCount++; |
| } |
| |
| @Override |
| public void onSuccess(Object response) { |
| successCallCount++; |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Override |
| public void onViolation(Set<com.google.web.bindery.requestfactory.shared.Violation> errors) { |
| violationCallCount++; |
| } |
| } |
| |
| private class ThrowingReceiver<T> extends Receiver<T> { |
| private final RuntimeException e; |
| |
| public ThrowingReceiver(RuntimeException e) { |
| this.e = e; |
| } |
| |
| @Override |
| public void onFailure(ServerFailure error) { |
| throw e; |
| } |
| |
| @Override |
| public void onSuccess(T response) { |
| throw e; |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Override |
| public void onViolation(Set<com.google.web.bindery.requestfactory.shared.Violation> errors) { |
| throw e; |
| } |
| } |
| |
| /** An abstraction to verify an uncaught exception */ |
| public interface ExceptionVerifier { |
| void verify(Throwable ex); |
| } |
| |
| private static final int DELAY_TEST_FINISH = 10 * 1000; |
| |
| @Override |
| public String getModuleName() { |
| return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite"; |
| } |
| |
| private ExceptionVerifier exceptionVerifier; |
| |
| @Override |
| protected void gwtTearDown() throws Exception { |
| super.gwtTearDown(); |
| exceptionVerifier = null; |
| } |
| |
| @Override |
| protected void reportUncaughtException(Throwable ex) { |
| try { |
| exceptionVerifier.verify(ex); |
| } catch (Throwable e) { |
| super.reportUncaughtException(ex); |
| } |
| } |
| |
| /** |
| * Test mixed invocation failure and success. One receiver will throw from |
| * onSuccess and another from onFailure. Other receivers onSuccess and |
| * onFailure will corectly be called. |
| */ |
| public void testMixedSuccessAndFailureThrow() { |
| delayTestFinish(DELAY_TEST_FINISH); |
| |
| final RuntimeException exception1 = new RuntimeException("first exception"); |
| final RuntimeException exception2 = new RuntimeException("second exception"); |
| final CountingReceiver count = new CountingReceiver(); |
| |
| SimpleFooRequest context = req.simpleFooRequest(); |
| // 42 is the crash causing magic number for a runtime exception |
| context.pleaseCrash(42).to(count); |
| context.returnNullString().to(new ThrowingReceiver<String>(exception1)); |
| context.returnNullString().to(count); |
| |
| fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2), new ExceptionVerifier() { |
| @Override |
| public void verify(Throwable e) { |
| assertUmbrellaException(e, exception1, exception2); |
| count.assertCounts(1, 1, 0); |
| finishTestAndReset(); |
| } |
| }); |
| } |
| |
| /** |
| * Test invocation failure. Other invocations' onSuccess should correctly be |
| * called. |
| */ |
| public void testOnFailureThrow() { |
| delayTestFinish(DELAY_TEST_FINISH); |
| |
| final RuntimeException exception1 = new RuntimeException("first exception"); |
| final RuntimeException exception2 = new RuntimeException("second exception"); |
| final CountingReceiver count = new CountingReceiver(); |
| |
| SimpleFooRequest context = req.simpleFooRequest(); |
| context.returnNullString().to(count); |
| // 42 is the crash causing magic number for a runtime exception |
| context.pleaseCrash(42).to(new ThrowingReceiver<Void>(exception1)); |
| context.returnNullString().to(count); |
| |
| fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2), new ExceptionVerifier() { |
| @Override |
| public void verify(Throwable e) { |
| assertUmbrellaException(e, exception1, exception2); |
| count.assertCounts(0, 2, 0); |
| finishTestAndReset(); |
| } |
| }); |
| } |
| |
| /** |
| * Test global failure. All receivers will have their onFailure called, and |
| * some of them will throw. |
| */ |
| public void testOnGlobalFailureThrow() { |
| delayTestFinish(DELAY_TEST_FINISH); |
| |
| final RuntimeException exception1 = new RuntimeException("first exception"); |
| final RuntimeException exception2 = new RuntimeException("second exception"); |
| final CountingReceiver count = new CountingReceiver(); |
| |
| SimpleFooRequest context = req.simpleFooRequest(); |
| SimpleFooProxy newFoo = context.create(SimpleFooProxy.class); |
| |
| context.returnNullString().to(count); |
| context.persist().using(newFoo).to(new ThrowingReceiver<Void>(exception1)); |
| context.returnNullString().to(count); |
| |
| final SimpleFooProxy mutableFoo = context.edit(newFoo); |
| // 42 is the crash causing magic number for a runtime exception |
| mutableFoo.setPleaseCrash(42); |
| |
| fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2), new ExceptionVerifier() { |
| @Override |
| public void verify(Throwable e) { |
| assertUmbrellaException(e, exception1, exception2); |
| count.assertCounts(2, 0, 0); |
| finishTestAndReset(); |
| } |
| }); |
| } |
| |
| /** |
| * All receivers will have their onSuccess called, and some of them will |
| * throw. |
| */ |
| public void testOnSuccessThrow() { |
| delayTestFinish(DELAY_TEST_FINISH); |
| |
| final RuntimeException exception1 = new RuntimeException("first exception"); |
| final RuntimeException exception2 = new RuntimeException("second exception"); |
| final CountingReceiver count = new CountingReceiver(); |
| |
| SimpleFooRequest context = req.simpleFooRequest(); |
| context.returnNullString().to(count); |
| context.returnNullString().to(new ThrowingReceiver<String>(exception1)); |
| context.returnNullString().to(count); |
| |
| fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2), new ExceptionVerifier() { |
| @Override |
| public void verify(Throwable e) { |
| assertUmbrellaException(e, exception1, exception2); |
| count.assertCounts(0, 2, 0); |
| finishTestAndReset(); |
| } |
| }); |
| } |
| |
| /** |
| * Test violations. All receivers will have their onViolation called, and some |
| * of them will throw. |
| */ |
| public void testOnViolationThrow() { |
| delayTestFinish(DELAY_TEST_FINISH); |
| |
| final RuntimeException exception1 = new RuntimeException("first exception"); |
| final RuntimeException exception2 = new RuntimeException("second exception"); |
| final CountingReceiver count = new CountingReceiver(); |
| |
| SimpleFooRequest context = req.simpleFooRequest(); |
| SimpleFooProxy newFoo = context.create(SimpleFooProxy.class); |
| newFoo.setUserName("a"); // too short |
| |
| context.returnNullString().to(count); |
| context.persist().using(newFoo).to(new ThrowingReceiver<Void>(exception1)); |
| context.returnNullString().to(count); |
| |
| fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2), new ExceptionVerifier() { |
| @Override |
| public void verify(Throwable e) { |
| assertUmbrellaException(e, exception1, exception2); |
| count.assertCounts(0, 0, 2); |
| finishTestAndReset(); |
| } |
| }); |
| } |
| |
| protected void fireContextAndCatch(RequestContext context, |
| Receiver<Void> receiver, ExceptionVerifier exceptionVerifier) { |
| this.exceptionVerifier = exceptionVerifier; |
| if (receiver == null) { |
| context.fire(); |
| } else { |
| context.fire(receiver); |
| } |
| } |
| |
| private static void assertUmbrellaException(Throwable e, Throwable... expectedCauses) { |
| assertTrue(e instanceof UmbrellaException); |
| Set<Throwable> causes = new HashSet<Throwable>(Arrays.asList(expectedCauses)); |
| assertEquals(causes, ((UmbrellaException) e).getCauses()); |
| } |
| } |