blob: 164112f881f77ff1aa16c3a2a9236e1dbd4a5fce [file] [log] [blame]
/*******************************************************************************
* 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.AbstractDefaultRealmTestCase;
import org.junit.Before;
import org.junit.Test;
/**
* Test cases for the {@link ISideEffect}.
*
* @since 3.2
*/
public class SideEffectTest extends AbstractDefaultRealmTestCase {
// 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());
// }
}