/******************************************************************************* * Copyright (c) 2015 Google, Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Stefan Xenos (Google) - initial API and implementation ******************************************************************************/ package org.eclipse.core.tests.databinding; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import org.eclipse.core.databinding.observable.sideeffect.ISideEffect; import org.eclipse.core.databinding.observable.value.ComputedValue; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.jface.tests.databinding.AbstractJUnit4RealmTestCase; import org.junit.Before; import org.junit.Test; /** * Test cases for the {@link ISideEffect}. * * @since 3.2 */ public class SideEffectTest extends AbstractJUnit4RealmTestCase { // TODO: Add test cases for {@link SideEffect#create(Runnable)}, // {@link SideEffect#create(java.util.function.Supplier, // java.util.function.Consumer)} // {@link SideEffect#pause()}, and {@link SideEffect#resume()} // - Validate that runIfDirty does nothing when paused private ISideEffect sideEffect; private int sideEffectInvocations; private WritableValue<String> defaultDependency; private WritableValue<String> alternateDependency; private WritableValue<Boolean> useDefaultDependency; @Override @Before public void setUp() throws Exception { super.setUp(); defaultDependency = new WritableValue<>("", null); alternateDependency = new WritableValue<>("", null); useDefaultDependency = new WritableValue<>(true, null); sideEffect = ISideEffect.createPaused(() -> { if (useDefaultDependency.getValue()) { defaultDependency.getValue(); } else { alternateDependency.getValue(); } sideEffectInvocations++; }); } @Test public void testSideEffectDoesntRunUntilResumed() throws Exception { runAsync(); assertEquals(0, sideEffectInvocations); } @Test public void testSideEffectRunsWhenResumed() throws Exception { sideEffect.resume(); runAsync(); assertEquals(1, sideEffectInvocations); } @Test(expected = IllegalStateException.class) public void testResumingSideEffectMultipleTimesThrowsIllegalStateException() throws Exception { sideEffect.resume(); sideEffect.resume(); runAsync(); assertEquals(1, sideEffectInvocations); } @Test public void testSideEffectSelectsCorrectDependency() throws Exception { // Run the side-effect once sideEffect.resume(); runAsync(); assertEquals(1, sideEffectInvocations); // Confirm that the SideEffect is reacting to defaultDependency defaultDependency.setValue("foo"); runAsync(); assertEquals(2, sideEffectInvocations); // Confirm that the SideEffect is not reacting to alternateDependency alternateDependency.setValue("foo"); runAsync(); assertEquals(2, sideEffectInvocations); // Now change the branch that the side effect ran through and ensure // that it selected the correct new dependency (and removed the old one) useDefaultDependency.setValue(false); runAsync(); assertEquals(3, sideEffectInvocations); // Confirm that the SideEffect is not reacting to defaultDependency defaultDependency.setValue("bar"); runAsync(); assertEquals(3, sideEffectInvocations); // Confirm that the SideEffect is reacting to alternateDependency alternateDependency.setValue("bar"); runAsync(); assertEquals(4, sideEffectInvocations); } @Test public void testChangingMultipleDependenciesOnlyRunsTheSideEffectOnce() throws Exception { sideEffect.resume(); runAsync(); assertEquals(1, sideEffectInvocations); defaultDependency.setValue("Foo"); alternateDependency.setValue("Foo"); useDefaultDependency.setValue(false); runAsync(); assertEquals(2, sideEffectInvocations); } @Test public void testChangingDependencyRerunsSideEffect() throws Exception { // Run the side-effect once sideEffect.resume(); runAsync(); assertEquals(1, sideEffectInvocations); // Now change the dependency defaultDependency.setValue("Foo"); runAsync(); // Ensure that the side effect ran again as a result assertEquals(2, sideEffectInvocations); } @Test public void testChangingUnrelatedNodeDoesntRunSideEffect() throws Exception { // Run the side-effect once sideEffect.resume(); runAsync(); assertEquals(1, sideEffectInvocations); // Now change the currently-unused dependency alternateDependency.setValue("Bar"); runAsync(); // Ensure that the side effect did not run again assertEquals(1, sideEffectInvocations); } @Test public void testDeactivatedSideEffectWontRunWhenTriggeredByDependency() throws Exception { // Run the side-effect once sideEffect.resume(); runAsync(); assertEquals(1, sideEffectInvocations); // Now deactivate the side-effect and trigger one of its dependencies defaultDependency.setValue("Foo"); sideEffect.dispose(); runAsync(); // Ensure that the side effect did not run again assertEquals(1, sideEffectInvocations); } @Test public void testDeactivatedSideEffectWontRunWhenRunIfDirtyInvoked() throws Exception { // Run the side-effect once sideEffect.resume(); runAsync(); assertEquals(1, sideEffectInvocations); sideEffect.pause(); defaultDependency.setValue("MakeItDirty"); sideEffect.runIfDirty(); runAsync(); // Ensure that the side effect did not run again assertEquals(1, sideEffectInvocations); } @Test public void testRunIfDirtyDoesNothingIfSideEffectNotDirty() throws Exception { // Run the side-effect once sideEffect.resume(); runAsync(); assertEquals(1, sideEffectInvocations); // Now deactivate the side-effect and trigger one of its dependencies sideEffect.runIfDirty(); // Ensure that the side effect did not run again assertEquals(1, sideEffectInvocations); } @Test public void testRunIfDirty() throws Exception { sideEffect.resume(); runAsync(); assertEquals(1, sideEffectInvocations); defaultDependency.setValue("Foo"); sideEffect.runIfDirty(); assertEquals(2, sideEffectInvocations); } @Test public void testNestedDependencyChangeAndRunIfDirtyCompletes() throws Exception { AtomicBoolean hasRun = new AtomicBoolean(); WritableValue<Object> invalidator = new WritableValue<Object>(new Object(), null); ISideEffect innerSideEffect = ISideEffect.create(() -> { invalidator.getValue(); }); ISideEffect.createPaused(() -> { // Make sure that there are no infinite loops. assertFalse(hasRun.get()); hasRun.set(true); invalidator.setValue(new Object()); innerSideEffect.runIfDirty(); }).resume(); runAsync(); assertTrue(hasRun.get()); } @Test public void testNestedInvalidateAndRunIfDirtyCompletes() throws Exception { AtomicBoolean hasRun = new AtomicBoolean(); final WritableValue<Object> makesThingsDirty = new WritableValue<>(null, null); ISideEffect innerSideEffect = ISideEffect.createPaused(() -> { makesThingsDirty.getValue(); }); innerSideEffect.resume(); ISideEffect.createPaused(() -> { // Make sure that there are no infinite loops. assertFalse(hasRun.get()); hasRun.set(true); makesThingsDirty.setValue(new Object()); innerSideEffect.runIfDirty(); }).resume(); runAsync(); assertTrue(hasRun.get()); } @Test public void testConsumeOnceDoesntPassNullToConsumer() throws Exception { AtomicBoolean consumerHasRun = new AtomicBoolean(); WritableValue<Object> makesThingsDirty = new WritableValue<>(null, null); ComputedValue<Object> value = new ComputedValue<Object>() { @Override protected Object calculate() { makesThingsDirty.getValue(); return null; } }; ISideEffect consumeOnce = ISideEffect.consumeOnceAsync(value::getValue, (Object) -> { consumerHasRun.set(true); }); makesThingsDirty.setValue(new Object()); runAsync(); makesThingsDirty.setValue(new Object()); runAsync(); assertFalse(consumerHasRun.get()); consumeOnce.dispose(); } @Test public void testConsumeOnceDoesntRunTwice() throws Exception { AtomicInteger numberOfRuns = new AtomicInteger(); WritableValue<Object> makesThingsDirty = new WritableValue<>(null, null); WritableValue<Object> returnValue = new WritableValue<>(null, null); ComputedValue<Object> value = new ComputedValue<Object>() { @Override protected Object calculate() { makesThingsDirty.getValue(); return returnValue.getValue(); } }; ISideEffect consumeOnce = ISideEffect.consumeOnceAsync(value::getValue, (Object) -> { numberOfRuns.set(numberOfRuns.get() + 1); }); makesThingsDirty.setValue(new Object()); runAsync(); assertEquals(0, numberOfRuns.get()); returnValue.setValue("Foo"); runAsync(); assertEquals(1, numberOfRuns.get()); returnValue.setValue("Bar"); runAsync(); assertEquals(1, numberOfRuns.get()); consumeOnce.dispose(); } @Test public void testConsumeOnceDoesntRunAtAllIfDisposed() throws Exception { AtomicInteger numberOfRuns = new AtomicInteger(); WritableValue<Object> returnValue = new WritableValue<>("foo", null); ISideEffect consumeOnce = ISideEffect.consumeOnceAsync(returnValue::getValue, (Object) -> { numberOfRuns.set(numberOfRuns.get() + 1); }); consumeOnce.dispose(); runAsync(); assertEquals(0, numberOfRuns.get()); } @Test public void testConsumeOnceRunsIfInitialValueNonNull() throws Exception { AtomicInteger numberOfRuns = new AtomicInteger(); WritableValue<Object> returnValue = new WritableValue<>("foo", null); ISideEffect consumeOnce = ISideEffect.consumeOnceAsync(returnValue::getValue, (Object) -> { numberOfRuns.set(numberOfRuns.get() + 1); }); runAsync(); assertEquals(1, numberOfRuns.get()); consumeOnce.dispose(); } @Test public void testNestedSideEffectCreation() throws Exception { AtomicBoolean hasRun = new AtomicBoolean(); // Make sure that creating a SideEffect within another side effect works // propely. ISideEffect.createPaused(() -> { ISideEffect.createPaused(() -> { assertFalse(hasRun.get()); hasRun.set(true); }).resume(); }).resume(); runAsync(); assertTrue(hasRun.get()); } @Test public void testSideEffectFiresDisposeEvent() throws Exception { AtomicBoolean hasRun = new AtomicBoolean(); // Make sure that a dispose event is sent correctly. ISideEffect disposeTest = ISideEffect.createPaused(() -> { }); disposeTest.resume(); disposeTest.addDisposeListener(sideEffect -> { assertTrue(disposeTest == sideEffect); hasRun.set(true); }); disposeTest.dispose(); runAsync(); assertTrue(hasRun.get()); } @Test public void testCanRemoveDisposeListener() throws Exception { AtomicBoolean hasRun = new AtomicBoolean(); // Make sure that a dispose event is sent correctly. ISideEffect disposeTest = ISideEffect.createPaused(() -> { }); disposeTest.resume(); Consumer<ISideEffect> disposeListener = sideEffect -> { hasRun.set(true); }; disposeTest.addDisposeListener(disposeListener); disposeTest.removeDisposeListener(disposeListener); disposeTest.dispose(); runAsync(); assertFalse(hasRun.get()); } // Doesn't currently work, but this would be a desirable property for // SideEffect to have // public void testInvalidateSelf() throws Exception { // AtomicInteger runCount = new AtomicInteger(); // WritableValue<Object> invalidator = new WritableValue<>(null, // null); // // Make sure that if a side effect invalidates it self, it will run at // // least once more but eventually stop. // ISideEffect[] sideEffect = new ISideEffect[1]; // sideEffect[0] = ISideEffect.createPaused(() -> { // assertTrue(runCount.get() < 2); // invalidator.getValue(); // int count = runCount.incrementAndGet(); // if (count == 1) { // invalidator.setValue(new Object()); // } // }); // sideEffect[0].resume(); // runAsync(); // assertEquals(2, runCount.get()); // } }