Skip to content

Commit

Permalink
Merge pull request #340 from sialcasa/310_ServiceStateMachine_on_sync…
Browse files Browse the repository at this point in the history
…hronous_delegate_command

310 service state machine on synchronous delegate command
  • Loading branch information
manuel-mauky committed Dec 17, 2015
2 parents 28a7832 + 71e106f commit f3263e7
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
public interface Command {

/**
* This method will be called when the command is invoked.
* This method will be called when the command is invoked. This has to get called from FX Thread
*/
void execute();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@

import java.util.function.Supplier;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import eu.lestard.doc.Beta;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.value.ObservableBooleanValue;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import eu.lestard.doc.Beta;

/**
* A {@link Command} implementation of a {@link Service<Void>} that encapsulates an {@link Action} ({@link Task<Void>})
Expand All @@ -42,9 +45,10 @@ public class DelegateCommand extends Service<Void> implements Command {
protected final ReadOnlyBooleanWrapper executable = new ReadOnlyBooleanWrapper(true);
protected ReadOnlyBooleanWrapper notExecutable;
protected ReadOnlyBooleanWrapper notRunning;
private Exception occuredException;



Logger LOG = LoggerFactory.getLogger(DelegateCommand.class);

/**
* Creates a command without a condition about the executability.
Expand Down Expand Up @@ -114,6 +118,8 @@ public DelegateCommand(final Supplier<Action> actionSupplier, ObservableBooleanV
*/
@Override
public void execute() {
occuredException = null;

if (!isExecutable()) {
throw new RuntimeException("The execute()-method of the command was called while it wasn't executable.");
} else {
Expand All @@ -123,18 +129,35 @@ public void execute() {
start();
}
} else {
try {
actionSupplier.get().action();
} catch (Exception e) {
throw new RuntimeException(e);
}
// When the Command is not executed in background, we have to imitate a Service execution, so the
// Service Statemachine provides the
// correct Service State to the command.
callActionAndSynthesizeServiceRun();
}
}
}

private void callActionAndSynthesizeServiceRun() {
try {
// We call the User Action. If an exception occures we save it, therefore we can use it in the Test
// (createSynthesizedTask) to throw it during the Service invokation.
actionSupplier.get().action();
} catch (Exception e) {
LOG.error("Exception in Command Execution", occuredException);
this.occuredException = e;
}
// Start the Service to trigger the Service state machine. createTask->createSynthesizedTask will be called and
// will throw the Exception which was catched some lines before
reset();
start();
}

@Override
protected Task<Void> createTask() {
return actionSupplier.get();
if (inBackground) {
return actionSupplier.get();
}
return createSynthesizedTask();
}

@Override
Expand Down Expand Up @@ -175,4 +198,16 @@ public final ReadOnlyBooleanProperty notRunningProperty() {
public final boolean isNotRunning() {
return notRunningProperty().get();
}

private Task<Void> createSynthesizedTask() {
return new Task<Void>() {
@Override
protected Void call() throws Exception {
if (occuredException != null) {
throw occuredException;
}
return null;
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,33 @@
import static org.junit.Assert.assertTrue;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import com.cedarsoft.test.utils.CatchAllExceptionsRule;
import de.saxsys.mvvmfx.testingutils.jfxrunner.JfxRunner;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker.State;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import com.cedarsoft.test.utils.CatchAllExceptionsRule;

import de.saxsys.mvvmfx.testingutils.jfxrunner.JfxRunner;



@RunWith(JfxRunner.class)
public class DelegateCommandTest {

// Rule to get exceptions from the JavaFX Thread into the JUnit thread
@Rule
public CatchAllExceptionsRule catchAllExceptionsRule = new CatchAllExceptionsRule();




@Test
public void executable() {
BooleanProperty condition = new SimpleBooleanProperty(true);
Expand All @@ -56,7 +58,7 @@ protected void action() {
}

@Test
public void firePositive() {
public void firePositive() throws InterruptedException, ExecutionException, TimeoutException {
BooleanProperty condition = new SimpleBooleanProperty(true);
BooleanProperty called = new SimpleBooleanProperty();

Expand All @@ -70,6 +72,16 @@ protected void action() {
assertFalse(called.get());
delegateCommand.execute();
assertTrue(called.get());

CompletableFuture<State> stateFromFxThread = new CompletableFuture<>();
Platform.runLater(() -> {
delegateCommand.stateProperty().addListener((b, o, n) -> {
if (n == State.SUCCEEDED) {
stateFromFxThread.complete(n);
}
});
});
assertThat(stateFromFxThread.get(3, TimeUnit.SECONDS)).isEqualTo(State.SUCCEEDED);
}

@Test(expected = RuntimeException.class)
Expand All @@ -85,6 +97,45 @@ protected void action() {
delegateCommand.execute();
}

@Test
public void firePositiveWithExc() throws InterruptedException, ExecutionException, TimeoutException {
BooleanProperty throwExc = new SimpleBooleanProperty(true);

DelegateCommand delegateCommand = new DelegateCommand(() -> new Action() {
@Override
protected void action() {
if (throwExc.get())
throw new RuntimeException("Someerror");
}
}, new SimpleBooleanProperty(true));



CompletableFuture<State> stateFromFxThread1 = new CompletableFuture<>();
Platform.runLater(() -> {
delegateCommand.stateProperty().addListener((b, o, n) -> {
if (n == State.FAILED) {
stateFromFxThread1.complete(n);
}
});
});
Platform.runLater(() -> delegateCommand.execute());
assertThat(stateFromFxThread1.get(3, TimeUnit.SECONDS)).isEqualTo(State.FAILED);

throwExc.set(false);
CompletableFuture<State> stateFromFxThread2 = new CompletableFuture<>();
Platform.runLater(() -> {
delegateCommand.stateProperty().addListener((b, o, n) -> {
if (n == State.SUCCEEDED) {
stateFromFxThread2.complete(n);
}
});
});
Platform.runLater(() -> delegateCommand.execute());
assertThat(stateFromFxThread2.get(3, TimeUnit.SECONDS)).isEqualTo(State.SUCCEEDED);

}


@Test
public void longRunningAsync() throws Exception {
Expand All @@ -104,26 +155,22 @@ protected void action() throws Exception {
assertFalse(delegateCommand.runningProperty().get());
assertTrue(delegateCommand.notRunningProperty().get());

delegateCommand.runningProperty().addListener(new ChangeListener<Boolean>() {

@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
assertTrue(delegateCommand.runningProperty().get());
assertFalse(delegateCommand.notRunningProperty().get());
assertFalse(delegateCommand.executableProperty().get());
assertTrue(delegateCommand.notExecutableProperty().get());
commandStarted.complete(null);
}
if (!newValue && oldValue) {
assertFalse(delegateCommand.runningProperty().get());
assertTrue(delegateCommand.notRunningProperty().get());
assertTrue(delegateCommand.executableProperty().get());
assertFalse(delegateCommand.notExecutableProperty().get());
commandCompleted.complete(null);
}

delegateCommand.runningProperty().addListener((ChangeListener<Boolean>) (observable, oldValue, newValue) -> {
if (newValue) {
assertTrue(delegateCommand.runningProperty().get());
assertFalse(delegateCommand.notRunningProperty().get());
assertFalse(delegateCommand.executableProperty().get());
assertTrue(delegateCommand.notExecutableProperty().get());
commandStarted.complete(null);
}
if (!newValue && oldValue) {
assertFalse(delegateCommand.runningProperty().get());
assertTrue(delegateCommand.notRunningProperty().get());
assertTrue(delegateCommand.executableProperty().get());
assertFalse(delegateCommand.notExecutableProperty().get());
commandCompleted.complete(null);
}

});

delegateCommand.execute();
Expand All @@ -134,13 +181,13 @@ public void changed(ObservableValue<? extends Boolean> observable, Boolean oldVa

@Test
public void progressProperty() throws Exception {

CompletableFuture<Void> stepOne = new CompletableFuture<>();
CompletableFuture<Void> stepTwo = new CompletableFuture<>();
CompletableFuture<Void> stepThree = new CompletableFuture<>();
CompletableFuture<Void> stepFour = new CompletableFuture<>();

DelegateCommand command = new DelegateCommand(()-> new Action() {
DelegateCommand command = new DelegateCommand(() -> new Action() {
@Override
protected void action() throws Exception {
updateProgress(0, 3);
Expand All @@ -162,7 +209,7 @@ protected void action() throws Exception {
stepOne.get(1, TimeUnit.SECONDS);
Platform.runLater(() ->
assertThat(command.getProgress()).isEqualTo(0.0));

stepTwo.get(1, TimeUnit.SECONDS);
Platform.runLater(() ->
assertThat(command.getProgress()).isEqualTo(0.3, offset(0.1)));
Expand All @@ -175,7 +222,7 @@ protected void action() throws Exception {
Platform.runLater(() ->
assertThat(command.getProgress()).isEqualTo(1, offset(0.1)));

// sleep to prevent the Junit thread from exiting
// sleep to prevent the Junit thread from exiting
// before eventual assertion errors from the JavaFX Thread are detected
sleep(500);
}
Expand Down

0 comments on commit f3263e7

Please sign in to comment.