/* * Copyright 2016 Red Hat, Inc. and/or its affiliates. * * 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 org.uberfire.wbtest.gwttest; import java.util.ArrayList; import java.util.List; import com.google.common.base.Predicate; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import org.jboss.errai.enterprise.client.cdi.AbstractCDIEventCallback; import org.jboss.errai.enterprise.client.cdi.api.CDI; import org.jboss.errai.ioc.client.container.IOC; import org.uberfire.client.mvp.ActivityLifecycleError; import org.uberfire.client.mvp.ActivityLifecycleError.LifecyclePhase; import org.uberfire.client.mvp.ActivityManager; import org.uberfire.client.mvp.PerspectiveManager; import org.uberfire.client.mvp.PlaceManager; import org.uberfire.client.mvp.PlaceStatus; import org.uberfire.client.workbench.PanelManager; import org.uberfire.mvp.PlaceRequest; import org.uberfire.mvp.impl.DefaultPlaceRequest; import org.uberfire.wbtest.client.breakable.BreakableMenuScreen; import org.uberfire.wbtest.client.breakable.BreakablePerspective; import org.uberfire.wbtest.client.breakable.BreakableScreen; import org.uberfire.wbtest.client.main.DefaultPerspectiveActivity; import org.uberfire.wbtest.client.main.DefaultScreenActivity; import org.uberfire.workbench.model.PanelDefinition; import org.uberfire.workbench.model.PartDefinition; import static com.google.common.base.Predicates.*; import static org.uberfire.client.mvp.ActivityLifecycleError.LifecyclePhase.*; import static org.uberfire.debug.Debug.*; import static org.uberfire.wbtest.testutil.TestingPredicates.*; /** * Tests for bulletproofing and notification for activities that throw exceptions from their lifecycle methods. */ public class BrokenLifecycleTest extends AbstractUberFireGwtTest { private static final Predicate<Void> BREAKABLE_MENU_NOT_VISIBLE = new Predicate<Void>() { @Override public boolean apply(Void input) { return DOM.getElementById(shortName(BreakableMenuScreen.class)) == null; } }; private static final Predicate<Void> BREAKABLE_SCREEN_NOT_VISIBLE = new Predicate<Void>() { @Override public boolean apply(Void input) { Element element = DOM.getElementById(shortName(BreakableScreen.class)); return element == null; } }; private final LifecycleErrorLogger lifecycleErrorLog = new LifecycleErrorLogger(); private PlaceManager placeManager; private ActivityManager activityManager; private PanelManager panelManager; private PerspectiveManager perspectiveManager; @Override protected void gwtSetUp() throws Exception { super.gwtSetUp(); placeManager = IOC.getBeanManager().lookupBean(PlaceManager.class).getInstance(); activityManager = IOC.getBeanManager().lookupBean(ActivityManager.class).getInstance(); panelManager = IOC.getBeanManager().lookupBean(PanelManager.class).getInstance(); perspectiveManager = IOC.getBeanManager().lookupBean(PerspectiveManager.class).getInstance(); CDI.subscribeLocal(ActivityLifecycleError.class.getName(), lifecycleErrorLog); } /** * Tests that we remain on the current perspective when the requested one can't be started. */ public void testBrokenPerspectiveStartup() throws Exception { final PlaceRequest brokenPerspectivePlace = DefaultPlaceRequest.parse(BreakablePerspective.class.getName() + "?broken=" + STARTUP); pollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { placeManager.goTo(brokenPerspectivePlace); } }) .thenDelay(500) .thenPollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultPerspectiveActivity.class.getName())); assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultScreenActivity.class.getName())); assertEquals(PlaceStatus.CLOSE, placeManager.getStatus(brokenPerspectivePlace)); assertEquals(PlaceStatus.CLOSE, placeManager.getStatus(BreakableMenuScreen.class.getName())); assertTrue(lifecycleErrorLog.contains(BreakablePerspective.class, STARTUP)); } }); } /** * Tests that we remain on the current perspective when the requested one can't be opened. */ public void testBrokenPerspectiveOpen() throws Exception { final PlaceRequest brokenPerspectivePlace = DefaultPlaceRequest.parse(BreakablePerspective.class.getName() + "?broken=" + OPEN); pollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { placeManager.goTo(brokenPerspectivePlace); } }) .thenDelay(500) .thenPollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { assertEquals(DefaultPerspectiveActivity.class.getName(), perspectiveManager.getCurrentPerspective().getPlace().getIdentifier()); assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultPerspectiveActivity.class.getName())); assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultScreenActivity.class.getName())); assertNull(placeManager.getActivity(brokenPerspectivePlace)); assertEquals(PlaceStatus.CLOSE, placeManager.getStatus(brokenPerspectivePlace)); assertEquals(PlaceStatus.CLOSE, placeManager.getStatus(BreakableMenuScreen.class.getName())); assertTrue(lifecycleErrorLog.contains(BreakablePerspective.class, OPEN)); } }); } /** * Tests that switching away from a perspective with a broken onClose works as if the onClose was functional. * (And that an error is fired). */ public void testBrokenPerspectiveClose() throws Exception { final PlaceRequest brokenPerspectivePlace = DefaultPlaceRequest.parse(BreakablePerspective.class.getName() + "?broken=" + CLOSE); pollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { placeManager.goTo(brokenPerspectivePlace); } }) .thenPollWhile(BREAKABLE_MENU_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { placeManager.goTo(DefaultPerspectiveActivity.class.getName()); } }) .thenPollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultPerspectiveActivity.class.getName())); assertEquals(PlaceStatus.CLOSE, placeManager.getStatus(brokenPerspectivePlace)); assertEquals(PlaceStatus.CLOSE, placeManager.getStatus(BreakableMenuScreen.class.getName())); assertTrue(lifecycleErrorLog.contains(BreakablePerspective.class, CLOSE)); } }); } /** * Tests that switching away from a perspective with a broken onShutdown works as if the onShutdown was functional. * (And that an error is fired). */ public void testBrokenPerspectiveShutdown() throws Exception { final PlaceRequest brokenPerspectivePlace = DefaultPlaceRequest.parse(BreakablePerspective.class.getName() + "?broken=" + SHUTDOWN); pollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { placeManager.goTo(brokenPerspectivePlace); } }) .thenPollWhile(BREAKABLE_MENU_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { placeManager.goTo(DefaultPerspectiveActivity.class.getName()); } }) .thenPollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultPerspectiveActivity.class.getName())); assertEquals(PlaceStatus.CLOSE, placeManager.getStatus(brokenPerspectivePlace)); assertEquals(PlaceStatus.CLOSE, placeManager.getStatus(BreakableMenuScreen.class.getName())); assertTrue(lifecycleErrorLog.contains(BreakablePerspective.class, SHUTDOWN)); } }); } /** * Tests that launching a screen with broken startup doesn't corrupt the *Manager state. */ public void testBrokenScreenStartup() throws Exception { final PlaceRequest brokenScreenPlace = DefaultPlaceRequest.parse(BreakableScreen.class.getName() + "?broken=" + STARTUP); pollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { placeManager.goTo(brokenScreenPlace); } }) .thenDelay(500) .thenPollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultPerspectiveActivity.class.getName())); assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultScreenActivity.class.getName())); assertEquals(PlaceStatus.CLOSE, placeManager.getStatus(brokenScreenPlace)); assertPanelDoesNotContain(panelManager.getRoot(), brokenScreenPlace); assertTrue(lifecycleErrorLog.contains(BreakableScreen.class, STARTUP)); } }); } /** * Tests that launching a screen with broken open doesn't corrupt the *Manager state. */ public void testBrokenScreenOpen() throws Exception { final PlaceRequest brokenScreenPlace = DefaultPlaceRequest.parse(BreakableScreen.class.getName() + "?broken=" + OPEN); pollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { placeManager.goTo(brokenScreenPlace); } }) .thenDelay(500) .thenPollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultPerspectiveActivity.class.getName())); assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultScreenActivity.class.getName())); assertEquals(PlaceStatus.CLOSE, placeManager.getStatus(brokenScreenPlace)); assertPanelDoesNotContain(panelManager.getRoot(), brokenScreenPlace); assertTrue(lifecycleErrorLog.contains(BreakableScreen.class, OPEN)); } }); } /** * Tests that closing a screen with broken close doesn't corrupt the *Manager state. */ public void testBrokenScreenClose() throws Exception { final PlaceRequest brokenScreenPlace = DefaultPlaceRequest.parse(BreakableScreen.class.getName() + "?broken=" + CLOSE); pollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { placeManager.goTo(brokenScreenPlace); } }) .thenPollWhile(BREAKABLE_SCREEN_NOT_VISIBLE) .thenDelay(500) .thenDo(new Runnable() { @Override public void run() { placeManager.closePlace(brokenScreenPlace); } }) .thenPollWhile(not(BREAKABLE_SCREEN_NOT_VISIBLE)) .thenDo(new Runnable() { @Override public void run() { assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultPerspectiveActivity.class.getName())); assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultScreenActivity.class.getName())); assertEquals(PlaceStatus.CLOSE, placeManager.getStatus(brokenScreenPlace)); assertPanelDoesNotContain(panelManager.getRoot(), brokenScreenPlace); assertTrue(lifecycleErrorLog.contains(BreakableScreen.class, CLOSE)); } }); } /** * Tests that closing a screen with broken shutdown doesn't corrupt the *Manager state. */ public void testBrokenScreenShutdown() throws Exception { final PlaceRequest brokenScreenPlace = DefaultPlaceRequest.parse(BreakableScreen.class.getName() + "?broken=" + SHUTDOWN); pollWhile(DEFAULT_SCREEN_NOT_VISIBLE) .thenDo(new Runnable() { @Override public void run() { placeManager.goTo(brokenScreenPlace); } }) .thenPollWhile(BREAKABLE_SCREEN_NOT_VISIBLE) .thenDelay(500) .thenDo(new Runnable() { @Override public void run() { placeManager.closePlace(brokenScreenPlace); } }) .thenPollWhile(not(BREAKABLE_SCREEN_NOT_VISIBLE)) .thenDo(new Runnable() { @Override public void run() { assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultPerspectiveActivity.class.getName())); assertEquals(PlaceStatus.OPEN, placeManager.getStatus(DefaultScreenActivity.class.getName())); assertEquals(PlaceStatus.CLOSE, placeManager.getStatus(brokenScreenPlace)); assertPanelDoesNotContain(panelManager.getRoot(), brokenScreenPlace); assertTrue(lifecycleErrorLog.contains(BreakableScreen.class, SHUTDOWN)); } }); } public void assertPanelDoesNotContain(PanelDefinition panelDef, PlaceRequest place) { for (PartDefinition part : panelDef.getParts()) { if (part.getPlace().equals(place)) { fail("Found a part for " + place + " in a panel it should not have been in"); } } } public static class LifecycleErrorLogger extends AbstractCDIEventCallback<ActivityLifecycleError> { List<ActivityLifecycleError> errors = new ArrayList<ActivityLifecycleError>(); @Override protected void fireEvent(ActivityLifecycleError event) { errors.add(event); } public boolean contains(Class<?> activityType, LifecyclePhase failedCallback) { for (ActivityLifecycleError error : errors) { if (error.getFailedActivity().getClass() == activityType && error.getFailedCall() == failedCallback) { return true; } } return false; } } }