1 /*
   2  * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package test.javafx.concurrent;
  27 
  28 import com.sun.org.apache.xerces.internal.util.DOMUtil;
  29 import javafx.beans.value.ChangeListener;
  30 import javafx.beans.value.ObservableValue;
  31 import test.javafx.concurrent.mocks.MythicalEvent;
  32 import test.javafx.concurrent.mocks.SimpleTask;
  33 import javafx.event.Event;
  34 import javafx.event.EventHandler;
  35 import java.util.concurrent.ConcurrentLinkedQueue;
  36 import java.util.concurrent.CountDownLatch;
  37 import java.util.concurrent.Executor;
  38 import java.util.concurrent.atomic.AtomicBoolean;
  39 import java.util.concurrent.atomic.AtomicInteger;
  40 import java.util.concurrent.atomic.AtomicReference;
  41 import javafx.concurrent.Service;
  42 import javafx.concurrent.ServiceShim;
  43 import javafx.concurrent.Task;
  44 import javafx.concurrent.TaskShim;
  45 import javafx.concurrent.Worker;
  46 import javafx.concurrent.WorkerStateEvent;
  47 import javafx.event.EventType;
  48 import org.junit.After;
  49 import org.junit.Test;
  50 import static org.junit.Assert.assertEquals;
  51 import static org.junit.Assert.assertFalse;
  52 import static org.junit.Assert.assertNotNull;
  53 import static org.junit.Assert.assertNull;
  54 import static org.junit.Assert.assertSame;
  55 import static org.junit.Assert.assertTrue;
  56 
  57 /**
  58  * Tests various rules regarding the lifecycle of a Service.
  59  */
  60 public class ServiceLifecycleTest extends ServiceTestBase {
  61     /**
  62      * The ManualExecutor is used so that there is some time period between
  63      * when something is scheduled and when it actually runs, such that the
  64      * test code has to manually tell it that it can now run.
  65      */
  66     protected ManualExecutor executor;
  67 
  68     /**
  69      * The task to run, which has methods on it to allow me to manually
  70      * put it into a passing, failed, or whatnot state.
  71      */
  72     protected ManualTask task;
  73 
  74     @Override protected TestServiceFactory setupServiceFactory() {
  75         return new TestServiceFactory() {
  76             @Override public AbstractTask createTestTask() {
  77                 return task = new ManualTask();
  78             }
  79         };
  80     }
  81 
  82     @Override protected Executor createExecutor() {
  83         return executor = new ManualExecutor(super.createExecutor());
  84     }
  85 
  86     @After public void tearDown() {
  87         if (task != null) task.finish.set(true);
  88     }
  89 
  90     /**
  91      * This class will schedule the task, and then you can execute
  92      * it manually by calling executeScheduled. In this way I can
  93      * test when a Service is scheduled but not yet started.
  94      */
  95     protected final class ManualExecutor implements Executor {
  96         private Runnable scheduled;
  97         private Executor wrapped;
  98         
  99         ManualExecutor(Executor wrapped) {
 100             this.wrapped = wrapped;
 101         }
 102 
 103         @Override public void execute(Runnable command) {
 104             this.scheduled = command;
 105         }
 106 
 107         public void executeScheduled() {
 108             wrapped.execute(scheduled);
 109             // I need to wait until the next "Sentinel" runnable
 110             // on the queue, which the Task will post when it begins
 111             // execution.
 112             handleEvents();
 113         }
 114     }
 115 
 116     protected final class ManualTask extends AbstractTask {
 117         private AtomicBoolean finish = new AtomicBoolean(false);
 118         private AtomicReference<Exception> exception = new AtomicReference<>();
 119         private boolean failToCancel = false;
 120 
 121         @Override protected String call() throws Exception {
 122             runLater(new Sentinel());
 123             while (!finish.get()) {
 124                 Exception e = exception.get();
 125                 if (e != null) throw e;
 126             }
 127             return "Done";
 128         }
 129 
 130         public void progress(long done, long max) {
 131             updateProgress(done, max);
 132         }
 133 
 134         public void message(String msg) {
 135             updateMessage(msg);
 136         }
 137 
 138         public void title(String t) {
 139             updateTitle(t);
 140         }
 141 
 142         public void fail(Exception e) {
 143             exception.set(e);
 144             handleEvents();
 145         }
 146 
 147         public void complete() {
 148             finish.set(true);
 149             handleEvents();
 150         }
 151 
 152         @Override
 153         public boolean cancel(boolean mayInterruptIfRunning) {
 154             boolean result = failToCancel ? false : super.cancel(mayInterruptIfRunning);
 155             finish.set(true);
 156             return result;
 157         }
 158     }
 159 
 160     /************************************************************************
 161      * Tests while in the ready state                                       *
 162      ***********************************************************************/
 163 
 164     @Test public void callingStartInReadyStateSchedulesJob() {
 165         assertNull(executor.scheduled);
 166         service.start();
 167         assertNotNull(executor.scheduled);
 168     }
 169 
 170     @Test public void callingStartInReadyMovesToScheduledState() {
 171         service.start();
 172         assertSame(Worker.State.SCHEDULED, service.getState());
 173         assertSame(Worker.State.SCHEDULED, service.stateProperty().get());
 174     }
 175 
 176     @Test public void callingRestartInReadyStateSchedulesJob() {
 177         assertNull(executor.scheduled);
 178         service.restart();
 179         assertNotNull(executor.scheduled);
 180     }
 181 
 182     @Test public void callingRestartInReadyMovesToScheduledState() {
 183         service.restart();
 184         assertSame(Worker.State.SCHEDULED, service.getState());
 185         assertSame(Worker.State.SCHEDULED, service.stateProperty().get());
 186     }
 187 
 188     @Test public void callingCancelInReadyStateMovesToCancelledState() {
 189         service.cancel();
 190         assertSame(Worker.State.CANCELLED, service.getState());
 191         assertSame(Worker.State.CANCELLED, service.stateProperty().get());
 192     }
 193 
 194     @Test public void callingResetInReadyStateHasNoEffect() {
 195         service.reset();
 196         assertSame(Worker.State.READY, service.getState());
 197         assertSame(Worker.State.READY, service.stateProperty().get());
 198     }
 199 
 200     /************************************************************************
 201      * Tests while in the scheduled state                                   *
 202      ***********************************************************************/
 203 
 204     @Test(expected = IllegalStateException.class)
 205     public void callingStartInScheduledStateIsISE() {
 206         service.start();
 207         service.start();
 208     }
 209 
 210     @Test public void callingCancelInScheduledStateResultsInCancelledState() {
 211         service.start();
 212         service.cancel();
 213         assertSame(Worker.State.CANCELLED, service.getState());
 214         assertSame(Worker.State.CANCELLED, service.stateProperty().get());
 215     }
 216 
 217     @Test public void callingRestartInScheduledStateShouldCancelAndReschedule() {
 218         service.start();
 219         service.restart();
 220         assertSame(Worker.State.SCHEDULED, service.getState());
 221         assertSame(Worker.State.SCHEDULED, service.stateProperty().get());
 222     }
 223 
 224     /**
 225      * This test differs from callingRestartInScheduledStateShouldCancelAndReschedule
 226      * in that under some circumstances, the cancel operation on a task may yield
 227      * a task which is not marked as CANCELLED, such as when it is already run
 228      * or cancelled). In such a case, the bindings have not fired yet and the
 229      * state of the service is off. At least, that is what is happening with
 230      * RT-20880. The fix allows this test to pass.
 231      */
 232     @Test public void callingRestartInScheduledStateShouldCancelAndReschedule_RT_20880() {
 233         service.start();
 234         task.failToCancel = true;
 235         service.restart();
 236         assertSame(Worker.State.SCHEDULED, service.getState());
 237         assertSame(Worker.State.SCHEDULED, service.stateProperty().get());
 238     }
 239 
 240     @Test(expected = IllegalStateException.class)
 241     public void callingResetInScheduledStateThrowsISE() {
 242         service.start();
 243         service.reset();
 244     }
 245 
 246     @Test public void stateChangesToRunningWhenExecutorExecutes() {
 247         service.start();
 248         executor.executeScheduled();
 249         assertSame(Worker.State.RUNNING, service.getState());
 250         assertSame(Worker.State.RUNNING, service.stateProperty().get());
 251     }
 252 
 253     @Test public void exceptionShouldBeNullInScheduledState() {
 254         service.start();
 255         assertNull(service.getException());
 256         assertNull(service.exceptionProperty().get());
 257     }
 258 
 259     @Test public void valueShouldBeNullInScheduledState() {
 260         service.start();
 261         assertNull(service.getValue());
 262         assertNull(service.valueProperty().get());
 263     }
 264 
 265     @Test public void runningShouldBeTrueInScheduledState() {
 266         service.start();
 267         assertTrue(service.isRunning());
 268         assertTrue(service.runningProperty().get());
 269     }
 270 
 271     @Test public void runningPropertyNotificationInScheduledState() {
 272         final AtomicBoolean passed = new AtomicBoolean(false);
 273         service.runningProperty().addListener((o, oldValue, newValue) -> passed.set(newValue));
 274         service.start();
 275         assertTrue(passed.get());
 276     }
 277 
 278     @Test public void workDoneShouldBeNegativeOneInitiallyInScheduledState() {
 279         service.start();
 280         assertEquals(-1, service.getWorkDone(), 0);
 281         assertEquals(-1, service.workDoneProperty().get(), 0);
 282     }
 283 
 284     @Test public void totalWorkShouldBeNegativeOneAtStartOfScheduledState() {
 285         service.start();
 286         assertEquals(-1, service.getTotalWork(), 0);
 287         assertEquals(-1, service.totalWorkProperty().get(), 0);
 288     }
 289 
 290     @Test public void progressShouldBeNegativeOneAtStartOfScheduledState() {
 291         service.start();
 292         assertEquals(-1, service.getProgress(), 0);
 293         assertEquals(-1, task.progressProperty().get(), 0);
 294     }
 295 
 296     @Test public void messageShouldBeEmptyStringWhenEnteringScheduledState() {
 297         service.start();
 298         assertEquals("", service.getMessage());
 299         assertEquals("", task.messageProperty().get());
 300     }
 301 
 302     @Test public void titleShouldBeEmptyStringAtStartOfScheduledState() {
 303         service.start();
 304         assertEquals("", service.getTitle());
 305         assertEquals("", task.titleProperty().get());
 306     }
 307 
 308     /************************************************************************
 309      * Tests while in the running state                                     *
 310      ***********************************************************************/
 311 
 312     @Test(expected = IllegalStateException.class)
 313     public void callingStartInRunningStateIsISE() {
 314         service.start();
 315         executor.executeScheduled();
 316         service.start();
 317     }
 318 
 319     @Test(expected = IllegalStateException.class)
 320     public void callingResetInRunningStateIsISE() {
 321         service.start();
 322         executor.executeScheduled();
 323         service.reset();
 324     }
 325 
 326     @Test public void callingRestartInRunningStateCancelsAndReschedules() {
 327         service.start();
 328         executor.executeScheduled();
 329         service.restart();
 330         assertSame(Worker.State.SCHEDULED, service.getState());
 331         assertSame(Worker.State.SCHEDULED, service.stateProperty().get());
 332     }
 333 
 334     @Test public void callingCancelInRunningStateResultsInCancelledState() {
 335         service.start();
 336         executor.executeScheduled();
 337         service.cancel();
 338         assertSame(Worker.State.CANCELLED, service.getState());
 339         assertSame(Worker.State.CANCELLED, service.stateProperty().get());
 340     }
 341 
 342     @Test public void exceptionShouldBeNullInRunningState() {
 343         service.start();
 344         executor.executeScheduled();
 345         assertNull(service.getException());
 346         assertNull(service.exceptionProperty().get());
 347     }
 348 
 349     @Test public void valueShouldBeNullInRunningState() {
 350         service.start();
 351         executor.executeScheduled();
 352         assertNull(service.getValue());
 353         assertNull(service.valueProperty().get());
 354     }
 355 
 356     @Test public void runningShouldBeTrueInRunningState() {
 357         service.start();
 358         executor.executeScheduled();
 359         assertTrue(service.isRunning());
 360         assertTrue(service.runningProperty().get());
 361     }
 362 
 363     @Test public void runningPropertyNotificationInRunningState() {
 364         final AtomicBoolean passed = new AtomicBoolean(false);
 365         service.runningProperty().addListener((o, oldValue, newValue) -> passed.set(newValue));
 366         service.start();
 367         executor.executeScheduled();
 368         assertTrue(passed.get());
 369     }
 370 
 371     @Test public void workDoneShouldBeNegativeOneInitiallyInRunningState() {
 372         service.start();
 373         executor.executeScheduled();
 374         assertEquals(-1, service.getWorkDone(), 0);
 375         assertEquals(-1, service.workDoneProperty().get(), 0);
 376     }
 377 
 378     @Test public void workDoneShouldAdvanceTo10() {
 379         service.start();
 380         executor.executeScheduled();
 381         task.progress(10, 20);
 382         assertEquals(10, service.getWorkDone(), 0);
 383         assertEquals(10, service.workDoneProperty().get(), 0);
 384     }
 385 
 386     @Test public void workDonePropertyNotification() {
 387         final AtomicBoolean passed = new AtomicBoolean(false);
 388         service.workDoneProperty().addListener((o, oldValue, newValue) -> passed.set(newValue.doubleValue() == 10));
 389         service.start();
 390         executor.executeScheduled();
 391         task.progress(10, 20);
 392         assertTrue(passed.get());
 393     }
 394 
 395     @Test public void totalWorkShouldBeNegativeOneAtStartOfRunning() {
 396         service.start();
 397         executor.executeScheduled();
 398         assertEquals(-1, service.getTotalWork(), 0);
 399         assertEquals(-1, service.totalWorkProperty().get(), 0);
 400     }
 401 
 402     @Test public void totalWorkShouldBeTwenty() {
 403         service.start();
 404         executor.executeScheduled();
 405         task.progress(10, 20);
 406         assertEquals(20, service.getTotalWork(), 0);
 407         assertEquals(20, service.totalWorkProperty().get(), 0);
 408     }
 409 
 410     @Test public void totalWorkPropertyNotification() {
 411         final AtomicBoolean passed = new AtomicBoolean(false);
 412         service.totalWorkProperty().addListener((o, oldValue, newValue) -> passed.set(newValue.doubleValue() == 20));
 413         service.start();
 414         executor.executeScheduled();
 415         task.progress(10, 20);
 416         assertTrue(passed.get());
 417     }
 418 
 419     @Test public void progressShouldBeNegativeOneAtStartOfRunningState() {
 420         service.start();
 421         executor.executeScheduled();
 422         assertEquals(-1, service.getProgress(), 0);
 423         assertEquals(-1, task.progressProperty().get(), 0);
 424     }
 425 
 426     @Test public void afterRunningProgressShouldBe_FiftyPercent() {
 427         service.start();
 428         executor.executeScheduled();
 429         task.progress(10, 20);
 430         assertEquals(.5, service.getProgress(), 0);
 431         assertEquals(.5, task.progressProperty().get(), 0);
 432     }
 433 
 434     @Test public void progressPropertyNotification() {
 435         final AtomicBoolean passed = new AtomicBoolean(false);
 436         service.start();
 437         task.progressProperty().addListener((o, oldValue, newValue) -> passed.set(newValue.doubleValue() == .5));
 438         executor.executeScheduled();
 439         task.progress(10, 20);
 440         assertTrue(passed.get());
 441     }
 442 
 443     @Test public void messageShouldBeEmptyStringWhenEnteringRunningState() {
 444         service.start();
 445         executor.executeScheduled();
 446         assertEquals("", service.getMessage());
 447         assertEquals("", task.messageProperty().get());
 448     }
 449 
 450     @Test public void messageShouldBeLastSetValue() {
 451         service.start();
 452         executor.executeScheduled();
 453         task.message("Running");
 454         assertEquals("Running", service.getMessage());
 455         assertEquals("Running", task.messageProperty().get());
 456     }
 457 
 458     @Test public void messagePropertyNotification() {
 459         final AtomicBoolean passed = new AtomicBoolean(false);
 460         service.start();
 461         task.messageProperty().addListener((o, oldValue, newValue) -> passed.set("Running".equals(service.getMessage())));
 462         executor.executeScheduled();
 463         task.message("Running");
 464         assertTrue(passed.get());
 465     }
 466 
 467     @Test public void titleShouldBeEmptyStringAtStartOfRunningState() {
 468         service.start();
 469         executor.executeScheduled();
 470         assertEquals("", service.getTitle());
 471         assertEquals("", task.titleProperty().get());
 472     }
 473 
 474     @Test public void titleShouldBeLastSetValue() {
 475         service.start();
 476         executor.executeScheduled();
 477         task.title("Title");
 478         assertEquals("Title", service.getTitle());
 479         assertEquals("Title", task.titleProperty().get());
 480     }
 481 
 482     @Test public void titlePropertyNotification() {
 483         final AtomicBoolean passed = new AtomicBoolean(false);
 484         service.start();
 485         task.titleProperty().addListener((o, oldValue, newValue) -> passed.set("Title".equals(service.getTitle())));
 486         executor.executeScheduled();
 487         task.title("Title");
 488         assertTrue(passed.get());
 489     }
 490 
 491     /************************************************************************
 492      * Throw an exception in the running state                              *
 493      ***********************************************************************/
 494 
 495     @Test(expected = IllegalStateException.class)
 496     public void callingStartInFailedStateIsISE() {
 497         service.start();
 498         executor.executeScheduled();
 499         task.fail(new Exception("anything"));
 500         service.start();
 501     }
 502 
 503     @Test public void callingResetInFailedStateResetsStateToREADY() {
 504         service.start();
 505         executor.executeScheduled();
 506         task.fail(new Exception("anything"));
 507         service.reset();
 508 
 509         assertSame(Worker.State.READY, service.getState());
 510         assertSame(Worker.State.READY, service.stateProperty().get());
 511     }
 512 
 513     @Test public void callingResetInFailedStateResetsValueToNull() {
 514         service.start();
 515         executor.executeScheduled();
 516         task.fail(new Exception("anything"));
 517         service.reset();
 518 
 519         assertNull(service.getValue());
 520         assertNull(service.valueProperty().get());
 521     }
 522 
 523     @Test public void callingResetInFailedStateResetsExceptionToNull() {
 524         service.start();
 525         executor.executeScheduled();
 526         task.fail(new Exception("anything"));
 527         service.reset();
 528 
 529         assertNull(service.getException());
 530         assertNull(service.exceptionProperty().get());
 531     }
 532 
 533     @Test public void callingResetInFailedStateResetsWorkDoneToNegativeOne() {
 534         service.start();
 535         executor.executeScheduled();
 536         task.progress(10, 20);
 537         task.fail(new Exception("anything"));
 538         service.reset();
 539 
 540         assertEquals(-1, service.getWorkDone(), 0);
 541         assertEquals(-1, service.workDoneProperty().get(), 0);
 542     }
 543 
 544     @Test public void callingResetInFailedStateResetsTotalWorkToNegativeOne() {
 545         service.start();
 546         executor.executeScheduled();
 547         task.progress(10, 20);
 548         task.fail(new Exception("anything"));
 549         service.reset();
 550 
 551         assertEquals(-1, service.getTotalWork(), 0);
 552         assertEquals(-1, service.totalWorkProperty().get(), 0);
 553     }
 554 
 555     @Test public void callingResetInFailedStateResetsProgressToNegativeOne() {
 556         service.start();
 557         executor.executeScheduled();
 558         task.progress(10, 20);
 559         task.fail(new Exception("anything"));
 560         service.reset();
 561 
 562         assertEquals(-1, service.getProgress(), 0);
 563         assertEquals(-1, service.progressProperty().get(), 0);
 564     }
 565 
 566     @Test public void callingResetInFailedStateResetsRunningToFalse() {
 567         service.start();
 568         executor.executeScheduled();
 569         task.fail(new Exception("anything"));
 570         service.reset();
 571 
 572         assertFalse(service.isRunning());
 573         assertFalse(service.runningProperty().get());
 574     }
 575 
 576     @Test public void callingResetInFailedStateResetsMessageToEmptyString() {
 577         service.start();
 578         executor.executeScheduled();
 579         task.message("Message");
 580         task.fail(new Exception("anything"));
 581         service.reset();
 582 
 583         assertEquals("", service.getMessage());
 584         assertEquals("", service.messageProperty().get());
 585     }
 586 
 587     @Test public void callingResetInFailedStateResetsTitleToEmptyString() {
 588         service.start();
 589         executor.executeScheduled();
 590         task.title("Title");
 591         task.fail(new Exception("anything"));
 592         service.reset();
 593 
 594         assertEquals("", service.getTitle());
 595         assertEquals("", service.titleProperty().get());
 596     }
 597 
 598     @Test public void callingRestartInFailedStateReschedules() {
 599         service.start();
 600         executor.executeScheduled();
 601         task.fail(new Exception("anything"));
 602         service.restart();
 603         assertSame(Worker.State.SCHEDULED, service.getState());
 604         assertSame(Worker.State.SCHEDULED, service.stateProperty().get());
 605     }
 606 
 607     @Test public void callingCancelInFailedStateResultsInNoChange() {
 608         service.start();
 609         executor.executeScheduled();
 610         task.fail(new Exception("anything"));
 611         service.cancel();
 612         assertSame(Worker.State.FAILED, service.getState());
 613         assertSame(Worker.State.FAILED, service.stateProperty().get());
 614     }
 615 
 616     /************************************************************************
 617      * Proper Completion of a task                                          *
 618      ***********************************************************************/
 619 
 620     @Test(expected = IllegalStateException.class)
 621     public void callingStartInSucceededStateIsISE() {
 622         service.start();
 623         executor.executeScheduled();
 624         task.progress(20, 20);
 625         task.complete();
 626         service.start();
 627     }
 628 
 629     @Test public void callingResetInSucceededStateResetsStateToREADY() {
 630         service.start();
 631         executor.executeScheduled();
 632         task.progress(20, 20);
 633         task.complete();
 634         service.reset();
 635 
 636         assertSame(Worker.State.READY, service.getState());
 637         assertSame(Worker.State.READY, service.stateProperty().get());
 638     }
 639 
 640     @Test public void callingResetInSucceededStateResetsValueToNull() {
 641         service.start();
 642         executor.executeScheduled();
 643         task.progress(20, 20);
 644         task.complete();
 645         service.reset();
 646 
 647         assertNull(service.getValue());
 648         assertNull(service.valueProperty().get());
 649     }
 650 
 651     @Test public void callingResetInSucceededStateResetsExceptionToNull() {
 652         service.start();
 653         executor.executeScheduled();
 654         task.progress(20, 20);
 655         task.complete();
 656         service.reset();
 657 
 658         assertNull(service.getException());
 659         assertNull(service.exceptionProperty().get());
 660     }
 661 
 662     @Test public void callingResetInSucceededStateResetsWorkDoneToNegativeOne() {
 663         service.start();
 664         executor.executeScheduled();
 665         task.progress(20, 20);
 666         task.complete();
 667         service.reset();
 668 
 669         assertEquals(-1, service.getWorkDone(), 0);
 670         assertEquals(-1, service.workDoneProperty().get(), 0);
 671     }
 672 
 673     @Test public void callingResetInSucceededStateResetsTotalWorkToNegativeOne() {
 674         service.start();
 675         executor.executeScheduled();
 676         task.progress(20, 20);
 677         task.complete();
 678         service.reset();
 679 
 680         assertEquals(-1, service.getTotalWork(), 0);
 681         assertEquals(-1, service.totalWorkProperty().get(), 0);
 682     }
 683 
 684     @Test public void callingResetInSucceededStateResetsProgressToNegativeOne() {
 685         service.start();
 686         executor.executeScheduled();
 687         task.progress(20, 20);
 688         task.complete();
 689         service.reset();
 690 
 691         assertEquals(-1, service.getProgress(), 0);
 692         assertEquals(-1, service.progressProperty().get(), 0);
 693     }
 694 
 695     @Test public void callingResetInSucceededStateResetsRunningToFalse() {
 696         service.start();
 697         executor.executeScheduled();
 698         task.progress(20, 20);
 699         task.complete();
 700         service.reset();
 701 
 702         assertFalse(service.isRunning());
 703         assertFalse(service.runningProperty().get());
 704     }
 705 
 706     @Test public void callingResetInSucceededStateResetsMessageToEmptyString() {
 707         service.start();
 708         executor.executeScheduled();
 709         task.message("Message");
 710         task.progress(20, 20);
 711         task.complete();
 712         service.reset();
 713 
 714         assertEquals("", service.getMessage());
 715         assertEquals("", service.messageProperty().get());
 716     }
 717 
 718     @Test public void callingResetInSucceededStateResetsTitleToEmptyString() {
 719         service.start();
 720         executor.executeScheduled();
 721         task.title("Title");
 722         task.progress(20, 20);
 723         task.complete();
 724         service.reset();
 725 
 726         assertEquals("", service.getTitle());
 727         assertEquals("", service.titleProperty().get());
 728     }
 729 
 730     @Test public void callingRestartInSucceededStateReschedules() {
 731         service.start();
 732         executor.executeScheduled();
 733         task.progress(20, 20);
 734         task.complete();
 735         service.restart();
 736         assertSame(Worker.State.SCHEDULED, service.getState());
 737         assertSame(Worker.State.SCHEDULED, service.stateProperty().get());
 738     }
 739 
 740     @Test public void callingCancelInSucceededStateResultsInNoChange() {
 741         service.start();
 742         executor.executeScheduled();
 743         task.progress(20, 20);
 744         task.complete();
 745         service.cancel();
 746         assertSame(Worker.State.SUCCEEDED, service.getState());
 747         assertSame(Worker.State.SUCCEEDED, service.stateProperty().get());
 748     }
 749 
 750     /***************************************************************************
 751      *                                                                         *
 752      * Tests for onReady                                                       *
 753      *                                                                         *
 754      **************************************************************************/
 755 
 756     @Test public void onReadyPropertyNameShouldMatchMethodName() {
 757         assertEquals("onReady", service.onReadyProperty().getName());
 758     }
 759 
 760     @Test public void onReadyBeanShouldMatchService() {
 761         assertSame(service, service.onReadyProperty().getBean());
 762     }
 763 
 764     @Test public void onReadyIsInitializedToNull() {
 765         assertNull(service.getOnReady());
 766         assertNull(service.onReadyProperty().get());
 767     }
 768 
 769     @Test public void onReadyFilterCalledBefore_onReady() {
 770         final AtomicBoolean filterCalled = new AtomicBoolean(false);
 771         final AtomicBoolean filterCalledFirst = new AtomicBoolean(false);
 772         service.start();
 773         executor.executeScheduled();
 774         task.complete();
 775         service.addEventFilter(WorkerStateEvent.WORKER_STATE_READY, workerStateEvent -> filterCalled.set(true));
 776         service.setOnReady(workerStateEvent -> filterCalledFirst.set(filterCalled.get()));
 777 
 778         // Transition to Ready state
 779         service.reset();
 780         // Events should have happened
 781         assertTrue(filterCalledFirst.get());
 782     }
 783 
 784     @Test public void onReadyHandlerCalled() {
 785         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
 786         service.start();
 787         executor.executeScheduled();
 788         task.complete();
 789         service.addEventHandler(WorkerStateEvent.WORKER_STATE_READY, workerStateEvent -> handlerCalled.set(true));
 790 
 791         // Transition to Ready state
 792         service.reset();
 793         // Events should have happened
 794         assertTrue(handlerCalled.get());
 795     }
 796 
 797     @Test public void removed_onReadyHandlerNotCalled() {
 798         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
 799         final AtomicBoolean sanity = new AtomicBoolean(false);
 800         service.start();
 801         executor.executeScheduled();
 802         task.complete();
 803         EventHandler<WorkerStateEvent> handler = workerStateEvent -> handlerCalled.set(true);
 804         service.addEventHandler(WorkerStateEvent.WORKER_STATE_READY, handler);
 805         service.removeEventHandler(WorkerStateEvent.WORKER_STATE_READY, handler);
 806         service.addEventHandler(WorkerStateEvent.WORKER_STATE_READY, workerStateEvent -> sanity.set(true));
 807 
 808         service.reset();
 809         assertTrue(sanity.get());
 810         assertFalse(handlerCalled.get());
 811     }
 812 
 813     @Test public void removed_onReadyFilterNotCalled() {
 814         final AtomicBoolean filterCalled = new AtomicBoolean(false);
 815         final AtomicBoolean sanity = new AtomicBoolean(false);
 816         service.start();
 817         executor.executeScheduled();
 818         task.complete();
 819         EventHandler<WorkerStateEvent> filter = workerStateEvent -> filterCalled.set(true);
 820         service.addEventFilter(WorkerStateEvent.WORKER_STATE_READY, filter);
 821         service.removeEventFilter(WorkerStateEvent.WORKER_STATE_READY, filter);
 822         service.addEventFilter(WorkerStateEvent.WORKER_STATE_READY, workerStateEvent -> sanity.set(true));
 823 
 824         service.reset();
 825         assertTrue(sanity.get());
 826         assertFalse(filterCalled.get());
 827     }
 828 
 829     @Test public void cancelCalledFromOnReady() {
 830         final AtomicInteger cancelNotificationCount = new AtomicInteger();
 831         service.start();
 832         executor.executeScheduled();
 833         task.complete();
 834         service.addEventFilter(WorkerStateEvent.WORKER_STATE_READY, workerStateEvent -> {
 835             service.cancel();
 836         });
 837         service.addEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, event -> {
 838             cancelNotificationCount.incrementAndGet();
 839         });
 840 
 841         service.reset();
 842         assertEquals(Worker.State.CANCELLED, service.getState());
 843         assertEquals(1, cancelNotificationCount.get());
 844     }
 845 
 846     /***************************************************************************
 847      *                                                                         *
 848      * Tests for onScheduled                                                   *
 849      *                                                                         *
 850      **************************************************************************/
 851 
 852     @Test public void onScheduledPropertyNameShouldMatchMethodName() {
 853         assertEquals("onScheduled", service.onScheduledProperty().getName());
 854     }
 855 
 856     @Test public void onScheduledBeanShouldMatchService() {
 857         assertSame(service, service.onScheduledProperty().getBean());
 858     }
 859 
 860     @Test public void onScheduledIsInitializedToNull() {
 861         assertNull(service.getOnScheduled());
 862         assertNull(service.onScheduledProperty().get());
 863     }
 864 
 865     @Test public void onScheduledFilterCalledBefore_onScheduled() {
 866         final AtomicBoolean filterCalled = new AtomicBoolean(false);
 867         final AtomicBoolean filterCalledFirst = new AtomicBoolean(false);
 868         service.addEventFilter(WorkerStateEvent.WORKER_STATE_SCHEDULED, workerStateEvent -> filterCalled.set(true));
 869         service.setOnScheduled(workerStateEvent -> filterCalledFirst.set(filterCalled.get()));
 870 
 871         // Transition to Scheduled state
 872         service.start();
 873         executor.executeScheduled();
 874         // Events should have happened
 875         assertTrue(filterCalledFirst.get());
 876     }
 877 
 878     @Test public void scheduledCalledAfterHandler() {
 879         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
 880         service.setOnScheduled(workerStateEvent -> handlerCalled.set(true));
 881 
 882         // Transition to Scheduled state
 883         service.start();
 884         executor.executeScheduled();
 885         // Events should have happened
 886         assertTrue(handlerCalled.get() && factory.getCurrentTask().scheduledSemaphore.getQueueLength() == 0);
 887     }
 888 
 889     @Test public void scheduledCalledAfterHandlerEvenIfConsumed() {
 890         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
 891         service.setOnScheduled(workerStateEvent -> {
 892             handlerCalled.set(true);
 893             workerStateEvent.consume();
 894         });
 895 
 896         // Transition to Scheduled state
 897         service.start();
 898         executor.executeScheduled();
 899         // Events should have happened
 900         assertTrue(handlerCalled.get() && factory.getCurrentTask().scheduledSemaphore.getQueueLength() == 0);
 901     }
 902 
 903     @Test public void onScheduledHandlerCalled() {
 904         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
 905         service.addEventHandler(WorkerStateEvent.WORKER_STATE_SCHEDULED, workerStateEvent -> handlerCalled.set(true));
 906 
 907         service.start();
 908         executor.executeScheduled();
 909         // Events should have happened
 910         assertTrue(handlerCalled.get());
 911     }
 912 
 913     @Test public void removed_onScheduledHandlerNotCalled() {
 914         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
 915         final AtomicBoolean sanity = new AtomicBoolean(false);
 916         EventHandler<WorkerStateEvent> handler = workerStateEvent -> handlerCalled.set(true);
 917         service.addEventHandler(WorkerStateEvent.WORKER_STATE_SCHEDULED, handler);
 918         service.removeEventHandler(WorkerStateEvent.WORKER_STATE_SCHEDULED, handler);
 919         service.addEventHandler(WorkerStateEvent.WORKER_STATE_SCHEDULED, workerStateEvent -> sanity.set(true));
 920 
 921         service.start();
 922         executor.executeScheduled();
 923         assertTrue(sanity.get());
 924         assertFalse(handlerCalled.get());
 925     }
 926 
 927     @Test public void removed_onScheduledFilterNotCalled() {
 928         final AtomicBoolean filterCalled = new AtomicBoolean(false);
 929         final AtomicBoolean sanity = new AtomicBoolean(false);
 930         EventHandler<WorkerStateEvent> filter = workerStateEvent -> filterCalled.set(true);
 931         service.addEventFilter(WorkerStateEvent.WORKER_STATE_SCHEDULED, filter);
 932         service.removeEventFilter(WorkerStateEvent.WORKER_STATE_SCHEDULED, filter);
 933         service.addEventFilter(WorkerStateEvent.WORKER_STATE_SCHEDULED, workerStateEvent -> sanity.set(true));
 934 
 935         service.start();
 936         executor.executeScheduled();
 937         assertTrue(sanity.get());
 938         assertFalse(filterCalled.get());
 939     }
 940 
 941     @Test public void cancelCalledFromOnScheduled() {
 942         final AtomicInteger cancelNotificationCount = new AtomicInteger();
 943         service.addEventFilter(WorkerStateEvent.WORKER_STATE_SCHEDULED, workerStateEvent -> {
 944             service.cancel();
 945         });
 946         service.addEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, event -> {
 947             cancelNotificationCount.incrementAndGet();
 948         });
 949 
 950         service.start();
 951         executor.executeScheduled();
 952         assertEquals(Worker.State.CANCELLED, service.getState());
 953         assertEquals(1, cancelNotificationCount.get());
 954     }
 955 
 956     /***************************************************************************
 957      *                                                                         *
 958      * Tests for onRunning                                                     *
 959      *                                                                         *
 960      **************************************************************************/
 961 
 962     @Test public void onRunningPropertyNameShouldMatchMethodName() {
 963         assertEquals("onRunning", service.onRunningProperty().getName());
 964     }
 965 
 966     @Test public void onRunningBeanShouldMatchService() {
 967         assertSame(service, service.onRunningProperty().getBean());
 968     }
 969 
 970     @Test public void onRunningIsInitializedToNull() {
 971         assertNull(service.getOnRunning());
 972         assertNull(service.onRunningProperty().get());
 973     }
 974 
 975     @Test public void onRunningFilterCalledBefore_onRunning() {
 976         final AtomicBoolean filterCalled = new AtomicBoolean(false);
 977         final AtomicBoolean filterCalledFirst = new AtomicBoolean(false);
 978         service.addEventFilter(WorkerStateEvent.WORKER_STATE_RUNNING, workerStateEvent -> filterCalled.set(true));
 979         service.setOnRunning(workerStateEvent -> filterCalledFirst.set(filterCalled.get()));
 980 
 981         // Transition to Running state
 982         service.start();
 983         executor.executeScheduled();
 984         // Events should have happened
 985         assertTrue(filterCalledFirst.get());
 986     }
 987 
 988     @Test public void runningCalledAfterHandler() {
 989         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
 990         service.setOnRunning(workerStateEvent -> handlerCalled.set(true));
 991 
 992         // Transition to Running state
 993         service.start();
 994         executor.executeScheduled();
 995         // Events should have happened
 996         assertTrue(handlerCalled.get() && factory.getCurrentTask().runningSemaphore.getQueueLength() == 0);
 997     }
 998 
 999     @Test public void runningCalledAfterHandlerEvenIfConsumed() {
1000         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1001         service.setOnRunning(workerStateEvent -> {
1002             handlerCalled.set(true);
1003             workerStateEvent.consume();
1004         });
1005 
1006         // Transition to Running state
1007         service.start();
1008         executor.executeScheduled();
1009         // Events should have happened
1010         assertTrue(handlerCalled.get() && factory.getCurrentTask().runningSemaphore.getQueueLength() == 0);
1011     }
1012 
1013     @Test public void onRunningHandlerCalled() {
1014         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1015         service.addEventHandler(WorkerStateEvent.WORKER_STATE_RUNNING, workerStateEvent -> handlerCalled.set(true));
1016 
1017         service.start();
1018         executor.executeScheduled();
1019         // Events should have happened
1020         assertTrue(handlerCalled.get());
1021     }
1022 
1023     @Test public void removed_onRunningHandlerNotCalled() {
1024         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1025         final AtomicBoolean sanity = new AtomicBoolean(false);
1026         EventHandler<WorkerStateEvent> handler = workerStateEvent -> handlerCalled.set(true);
1027         service.addEventHandler(WorkerStateEvent.WORKER_STATE_RUNNING, handler);
1028         service.removeEventHandler(WorkerStateEvent.WORKER_STATE_RUNNING, handler);
1029         service.addEventHandler(WorkerStateEvent.WORKER_STATE_RUNNING, workerStateEvent -> sanity.set(true));
1030 
1031         service.start();
1032         executor.executeScheduled();
1033         assertTrue(sanity.get());
1034         assertFalse(handlerCalled.get());
1035     }
1036 
1037     @Test public void removed_onRunningFilterNotCalled() {
1038         final AtomicBoolean filterCalled = new AtomicBoolean(false);
1039         final AtomicBoolean sanity = new AtomicBoolean(false);
1040         EventHandler<WorkerStateEvent> filter = workerStateEvent -> filterCalled.set(true);
1041         service.addEventFilter(WorkerStateEvent.WORKER_STATE_RUNNING, filter);
1042         service.removeEventFilter(WorkerStateEvent.WORKER_STATE_RUNNING, filter);
1043         service.addEventFilter(WorkerStateEvent.WORKER_STATE_RUNNING, workerStateEvent -> sanity.set(true));
1044 
1045         service.start();
1046         executor.executeScheduled();
1047         assertTrue(sanity.get());
1048         assertFalse(filterCalled.get());
1049     }
1050 
1051     @Test public void cancelCalledFromOnRunning() {
1052         final AtomicInteger cancelNotificationCount = new AtomicInteger();
1053         service.addEventFilter(WorkerStateEvent.WORKER_STATE_RUNNING, workerStateEvent -> {
1054             service.cancel();
1055         });
1056         service.addEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, event -> {
1057             cancelNotificationCount.incrementAndGet();
1058         });
1059 
1060         service.start();
1061         executor.executeScheduled();
1062         assertEquals(Worker.State.CANCELLED, service.getState());
1063         assertEquals(1, cancelNotificationCount.get());
1064     }
1065 
1066     /***************************************************************************
1067      *                                                                         *
1068      * Tests for onSucceeded                                                   *
1069      *                                                                         *
1070      **************************************************************************/
1071 
1072     @Test public void onSucceededPropertyNameShouldMatchMethodName() {
1073         assertEquals("onSucceeded", service.onSucceededProperty().getName());
1074     }
1075 
1076     @Test public void onSucceededBeanShouldMatchService() {
1077         assertSame(service, service.onSucceededProperty().getBean());
1078     }
1079 
1080     @Test public void onSucceededIsInitializedToNull() {
1081         assertNull(service.getOnSucceeded());
1082         assertNull(service.onSucceededProperty().get());
1083     }
1084 
1085     @Test public void onSucceededFilterCalledBefore_onSucceeded() {
1086         final AtomicBoolean filterCalled = new AtomicBoolean(false);
1087         final AtomicBoolean filterCalledFirst = new AtomicBoolean(false);
1088         service.addEventFilter(WorkerStateEvent.WORKER_STATE_SUCCEEDED, workerStateEvent -> filterCalled.set(true));
1089         service.setOnSucceeded(workerStateEvent -> filterCalledFirst.set(filterCalled.get()));
1090 
1091         // Transition to Succeeded state
1092         service.start();
1093         executor.executeScheduled();
1094         task.complete();
1095         // Events should have happened
1096         assertTrue(filterCalledFirst.get());
1097     }
1098 
1099     @Test public void succeededCalledAfterHandler() {
1100         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1101         service.setOnSucceeded(workerStateEvent -> handlerCalled.set(true));
1102 
1103         // Transition to Succeeded state
1104         service.start();
1105         executor.executeScheduled();
1106         task.complete();
1107         // Events should have happened
1108         assertTrue(handlerCalled.get() && factory.getCurrentTask().succeededSemaphore.getQueueLength() == 0);
1109     }
1110 
1111     @Test public void succeededCalledAfterHandlerEvenIfConsumed() {
1112         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1113         service.setOnSucceeded(workerStateEvent -> {
1114             handlerCalled.set(true);
1115             workerStateEvent.consume();
1116         });
1117 
1118         // Transition to Succeeded state
1119         service.start();
1120         executor.executeScheduled();
1121         task.complete();
1122         // Events should have happened
1123         assertTrue(handlerCalled.get() && factory.getCurrentTask().succeededSemaphore.getQueueLength() == 0);
1124     }
1125 
1126     @Test public void onSucceededHandlerCalled() {
1127         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1128         service.addEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, workerStateEvent -> handlerCalled.set(true));
1129 
1130         service.start();
1131         executor.executeScheduled();
1132         task.complete();
1133         // Events should have happened
1134         assertTrue(handlerCalled.get());
1135     }
1136 
1137     @Test public void removed_onSucceededHandlerNotCalled() {
1138         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1139         final AtomicBoolean sanity = new AtomicBoolean(false);
1140         EventHandler<WorkerStateEvent> handler = workerStateEvent -> handlerCalled.set(true);
1141         service.addEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, handler);
1142         service.removeEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, handler);
1143         service.addEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, workerStateEvent -> sanity.set(true));
1144 
1145         service.start();
1146         executor.executeScheduled();
1147         task.complete();
1148         assertTrue(sanity.get());
1149         assertFalse(handlerCalled.get());
1150     }
1151 
1152     @Test public void removed_onSucceededFilterNotCalled() {
1153         final AtomicBoolean filterCalled = new AtomicBoolean(false);
1154         final AtomicBoolean sanity = new AtomicBoolean(false);
1155         EventHandler<WorkerStateEvent> filter = workerStateEvent -> filterCalled.set(true);
1156         service.addEventFilter(WorkerStateEvent.WORKER_STATE_SUCCEEDED, filter);
1157         service.removeEventFilter(WorkerStateEvent.WORKER_STATE_SUCCEEDED, filter);
1158         service.addEventFilter(WorkerStateEvent.WORKER_STATE_SUCCEEDED, workerStateEvent -> sanity.set(true));
1159 
1160         service.start();
1161         executor.executeScheduled();
1162         task.complete();
1163     }
1164 
1165     @Test public void cancelCalledFromOnSucceeded() {
1166         final AtomicInteger cancelNotificationCount = new AtomicInteger();
1167         service.addEventFilter(WorkerStateEvent.WORKER_STATE_SUCCEEDED, workerStateEvent -> {
1168             service.cancel();
1169         });
1170         service.addEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, event -> {
1171             cancelNotificationCount.incrementAndGet();
1172         });
1173 
1174         service.start();
1175         executor.executeScheduled();
1176         task.complete();
1177         assertEquals(Worker.State.SUCCEEDED, service.getState());
1178         assertEquals(0, cancelNotificationCount.get());
1179     }
1180 
1181     /***************************************************************************
1182      *                                                                         *
1183      * Tests for onCancelled                                                   *
1184      *                                                                         *
1185      **************************************************************************/
1186 
1187     @Test public void onCancelledPropertyNameShouldMatchMethodName() {
1188         assertEquals("onCancelled", service.onCancelledProperty().getName());
1189     }
1190 
1191     @Test public void onCancelledBeanShouldMatchService() {
1192         assertSame(service, service.onCancelledProperty().getBean());
1193     }
1194 
1195     @Test public void onCancelledIsInitializedToNull() {
1196         assertNull(service.getOnCancelled());
1197         assertNull(service.onCancelledProperty().get());
1198     }
1199 
1200     @Test public void onCancelledFilterCalledBefore_onCancelled() {
1201         final AtomicBoolean filterCalled = new AtomicBoolean(false);
1202         final AtomicBoolean filterCalledFirst = new AtomicBoolean(false);
1203         service.addEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, workerStateEvent -> filterCalled.set(true));
1204         service.setOnCancelled(workerStateEvent -> filterCalledFirst.set(filterCalled.get()));
1205 
1206         // Transition to Cancelled state
1207         service.start();
1208         executor.executeScheduled();
1209         task.cancel();
1210         // Events should have happened
1211         assertTrue(filterCalledFirst.get());
1212     }
1213 
1214     @Test public void cancelledCalledAfterHandler() {
1215         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1216         service.setOnCancelled(workerStateEvent -> handlerCalled.set(true));
1217 
1218         // Transition to Cancelled state
1219         service.start();
1220         executor.executeScheduled();
1221         task.cancel();
1222         // Events should have happened
1223         assertTrue(handlerCalled.get() && factory.getCurrentTask().cancelledSemaphore.getQueueLength() == 0);
1224     }
1225 
1226     @Test public void cancelledCalledAfterHandlerEvenIfConsumed() {
1227         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1228         service.setOnCancelled(workerStateEvent -> {
1229             handlerCalled.set(true);
1230             workerStateEvent.consume();
1231         });
1232 
1233         // Transition to Cancelled state
1234         service.start();
1235         executor.executeScheduled();
1236         task.cancel();
1237         // Events should have happened
1238         assertTrue(handlerCalled.get() && factory.getCurrentTask().cancelledSemaphore.getQueueLength() == 0);
1239     }
1240 
1241     @Test public void onCancelledHandlerCalled() {
1242         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1243         service.addEventHandler(WorkerStateEvent.WORKER_STATE_CANCELLED, workerStateEvent -> handlerCalled.set(true));
1244 
1245         service.start();
1246         executor.executeScheduled();
1247         task.cancel();
1248         // Events should have happened
1249         assertTrue(handlerCalled.get());
1250     }
1251 
1252     @Test public void removed_onCancelledHandlerNotCalled() {
1253         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1254         final AtomicBoolean sanity = new AtomicBoolean(false);
1255         EventHandler<WorkerStateEvent> handler = workerStateEvent -> handlerCalled.set(true);
1256         service.addEventHandler(WorkerStateEvent.WORKER_STATE_CANCELLED, handler);
1257         service.removeEventHandler(WorkerStateEvent.WORKER_STATE_CANCELLED, handler);
1258         service.addEventHandler(WorkerStateEvent.WORKER_STATE_CANCELLED, workerStateEvent -> sanity.set(true));
1259 
1260         service.start();
1261         executor.executeScheduled();
1262         task.cancel();
1263         assertTrue(sanity.get());
1264         assertFalse(handlerCalled.get());
1265     }
1266 
1267     @Test public void removed_onCancelledFilterNotCalled() {
1268         final AtomicBoolean filterCalled = new AtomicBoolean(false);
1269         final AtomicBoolean sanity = new AtomicBoolean(false);
1270         EventHandler<WorkerStateEvent> filter = workerStateEvent -> filterCalled.set(true);
1271         service.addEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, filter);
1272         service.removeEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, filter);
1273         service.addEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, workerStateEvent -> sanity.set(true));
1274 
1275         service.start();
1276         executor.executeScheduled();
1277         task.cancel();
1278         assertTrue(sanity.get());
1279         assertFalse(filterCalled.get());
1280     }
1281 
1282     @Test public void cancelCalledFromOnCancelled() {
1283         final AtomicInteger cancelNotificationCount = new AtomicInteger();
1284         service.addEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, workerStateEvent -> {
1285             service.cancel();
1286         });
1287         service.addEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, event -> {
1288             cancelNotificationCount.incrementAndGet();
1289         });
1290 
1291         service.start();
1292         executor.executeScheduled();
1293         task.cancel();
1294         assertEquals(Worker.State.CANCELLED, service.getState());
1295         assertEquals(1, cancelNotificationCount.get());
1296     }
1297 
1298     @Test public void cancelCalledFromOnFailed() {
1299         final AtomicInteger cancelNotificationCount = new AtomicInteger();
1300         service.addEventFilter(WorkerStateEvent.WORKER_STATE_FAILED, workerStateEvent -> {
1301             service.cancel();
1302         });
1303         service.addEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, event -> {
1304             cancelNotificationCount.incrementAndGet();
1305         });
1306 
1307         service.start();
1308         executor.executeScheduled();
1309         task.fail(new Exception("Quit"));
1310         assertEquals(Worker.State.FAILED, service.getState());
1311         assertEquals(0, cancelNotificationCount.get());
1312     }
1313 
1314     /***************************************************************************
1315      *                                                                         *
1316      * Tests for onFailed                                                      *
1317      *                                                                         *
1318      **************************************************************************/
1319 
1320     @Test public void onFailedPropertyNameShouldMatchMethodName() {
1321         assertEquals("onFailed", service.onFailedProperty().getName());
1322     }
1323 
1324     @Test public void onFailedBeanShouldMatchService() {
1325         assertSame(service, service.onFailedProperty().getBean());
1326     }
1327 
1328     @Test public void onFailedIsInitializedToNull() {
1329         assertNull(service.getOnFailed());
1330         assertNull(service.onFailedProperty().get());
1331     }
1332 
1333     @Test public void onFailedFilterCalledBefore_onFailed() {
1334         final AtomicBoolean filterCalled = new AtomicBoolean(false);
1335         final AtomicBoolean filterCalledFirst = new AtomicBoolean(false);
1336         service.addEventFilter(WorkerStateEvent.WORKER_STATE_FAILED, workerStateEvent -> filterCalled.set(true));
1337         service.setOnFailed(workerStateEvent -> filterCalledFirst.set(filterCalled.get()));
1338 
1339         // Transition to Succeeded state
1340         service.start();
1341         executor.executeScheduled();
1342         task.fail(new Exception("The End"));
1343         // Events should have happened
1344         assertTrue(filterCalledFirst.get());
1345     }
1346 
1347     @Test public void failedCalledAfterHandler() {
1348         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1349         service.setOnFailed(workerStateEvent -> handlerCalled.set(true));
1350 
1351         // Transition to Succeeded state
1352         service.start();
1353         executor.executeScheduled();
1354         task.fail(new Exception("Quit Now"));
1355         // Events should have happened
1356         assertTrue(handlerCalled.get() && factory.getCurrentTask().failedSemaphore.getQueueLength() == 0);
1357     }
1358 
1359     @Test public void failedCalledAfterHandlerEvenIfConsumed() {
1360         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1361         service.setOnFailed(workerStateEvent -> handlerCalled.set(true));
1362 
1363         // Transition to Succeeded state
1364         service.start();
1365         executor.executeScheduled();
1366         task.fail(new Exception("Quit Now"));
1367         // Events should have happened
1368         assertTrue(handlerCalled.get() && factory.getCurrentTask().failedSemaphore.getQueueLength() == 0);
1369     }
1370 
1371     @Test public void onFailedHandlerCalled() {
1372         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1373         service.addEventHandler(WorkerStateEvent.WORKER_STATE_FAILED, workerStateEvent -> handlerCalled.set(true));
1374 
1375         service.start();
1376         executor.executeScheduled();
1377         task.fail(new Exception("Forget about it"));
1378         // Events should have happened
1379         assertTrue(handlerCalled.get());
1380     }
1381 
1382     @Test public void removed_onFailedHandlerNotCalled() {
1383         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
1384         final AtomicBoolean sanity = new AtomicBoolean(false);
1385         EventHandler<WorkerStateEvent> handler = workerStateEvent -> handlerCalled.set(true);
1386         service.addEventHandler(WorkerStateEvent.WORKER_STATE_FAILED, handler);
1387         service.removeEventHandler(WorkerStateEvent.WORKER_STATE_FAILED, handler);
1388         service.addEventHandler(WorkerStateEvent.WORKER_STATE_FAILED, workerStateEvent -> sanity.set(true));
1389 
1390         service.start();
1391         executor.executeScheduled();
1392         task.fail(new Exception("Quit"));
1393         assertTrue(sanity.get());
1394         assertFalse(handlerCalled.get());
1395     }
1396 
1397     @Test public void removed_onFailedFilterNotCalled() {
1398         final AtomicBoolean filterCalled = new AtomicBoolean(false);
1399         final AtomicBoolean sanity = new AtomicBoolean(false);
1400         EventHandler<WorkerStateEvent> filter = workerStateEvent -> filterCalled.set(true);
1401         service.addEventFilter(WorkerStateEvent.WORKER_STATE_FAILED, filter);
1402         service.removeEventFilter(WorkerStateEvent.WORKER_STATE_FAILED, filter);
1403         service.addEventFilter(WorkerStateEvent.WORKER_STATE_FAILED, workerStateEvent -> sanity.set(true));
1404 
1405         service.start();
1406         executor.executeScheduled();
1407         task.fail(new Exception("Quit"));
1408         assertTrue(sanity.get());
1409         assertFalse(filterCalled.get());
1410     }
1411 
1412     /***************************************************************************
1413      *                                                                         *
1414      * Tests that invoking methods from the wrong thread leads to errors, and  *
1415      * that regardless of which thread starts the Service, all notification    *
1416      * (events, etc) happen on the FX thread only.                             *
1417      *                                                                         *
1418      **************************************************************************/
1419 
1420     @Test public void canCreateServiceOnRandomThread() {
1421         RandomThread random = new RandomThread(() -> {
1422             DoNothingService s = null;
1423             try {
1424                 s = new DoNothingService();
1425             } finally {
1426                 if (s != null) s.shutdown();
1427             }
1428         });
1429         random.test();
1430     }
1431 
1432     @Test public void canGetReferencesToPropertiesOnRandomThread() {
1433         RandomThread random = new RandomThread(() -> {
1434             DoNothingService s = null;
1435             try {
1436                 s = new DoNothingService();
1437                 s.exceptionProperty();
1438                 s.executorProperty();
1439                 s.messageProperty();
1440                 s.progressProperty();
1441                 s.onCancelledProperty();
1442                 s.onFailedProperty();
1443                 s.onReadyProperty();
1444                 s.onRunningProperty();
1445                 s.onScheduledProperty();
1446                 s.onSucceededProperty();
1447                 s.runningProperty();
1448                 s.stateProperty();
1449                 s.titleProperty();
1450                 s.totalWorkProperty();
1451                 s.valueProperty();
1452                 s.workDoneProperty();
1453             } finally {
1454                 if (s != null) s.shutdown();
1455             }
1456         });
1457         random.test();
1458     }
1459 
1460     @Test public void canInvokeGettersOnRandomThread() {
1461         RandomThread random = new RandomThread(() -> {
1462             DoNothingService s = null;
1463             try {
1464                 s = new DoNothingService();
1465                 s.getException();
1466                 s.getExecutor();
1467                 s.getMessage();
1468                 s.getProgress();
1469                 s.getOnCancelled();
1470                 s.getOnFailed();
1471                 s.getOnReady();
1472                 s.getOnRunning();
1473                 s.getOnScheduled();
1474                 s.getOnSucceeded();
1475                 s.isRunning();
1476                 s.getState();
1477                 s.getTitle();
1478                 s.getTotalWork();
1479                 s.getValue();
1480                 s.getWorkDone();
1481             } finally {
1482                 if (s != null) s.shutdown();
1483             }
1484         });
1485         random.test();
1486     }
1487 
1488     @Test public void canInvokeSettersOnRandomThread() {
1489         RandomThread random = new RandomThread(() -> {
1490             DoNothingService s = null;
1491             try {
1492                 s = new DoNothingService();
1493                 ServiceShim.setEventHandler(s, WorkerStateEvent.ANY, event -> {
1494                 });
1495                 s.setOnCancelled(event -> {
1496                 });
1497                 s.setOnFailed(event -> {
1498                 });
1499                 s.setOnReady(event -> {
1500                 });
1501                 s.setOnRunning(event -> {
1502                 });
1503                 s.setOnScheduled(event -> {
1504                 });
1505                 s.setOnSucceeded(event -> {
1506                 });
1507             } finally {
1508                 if (s != null) s.shutdown();
1509             }
1510         });
1511         random.test();
1512     }
1513 
1514     @Test public void canInvokeStartOnRandomThread() {
1515         RandomThread random = new RandomThread(() -> {
1516             DoNothingService s = null;
1517             try {
1518                 s = new DoNothingService();
1519                 s.start();
1520             } finally {
1521                 if (s != null) s.shutdown();
1522             }
1523         });
1524         random.test();
1525     }
1526 
1527     @Test (expected = IllegalStateException.class)
1528     public void cannotInvokeRestartOnRandomThreadAfterStart() throws Throwable {
1529         assertThrowsException(s -> s.restart());
1530     }
1531 
1532     @Test (expected = IllegalStateException.class)
1533     public void cannotInvokeCancelOnRandomThreadAfterStart() throws Throwable {
1534         assertThrowsException(s -> {
1535             s.cancel();
1536         });
1537     }
1538 
1539     @Test (expected = IllegalStateException.class)
1540     public void cannotInvokeSettersOnRandomThreadAfterStart_1() throws Throwable {
1541         assertThrowsException(s -> 
1542                 ServiceShim.setEventHandler(s, WorkerStateEvent.ANY, event -> {
1543         }));
1544     }
1545 
1546     @Test (expected = IllegalStateException.class)
1547     public void cannotInvokeSettersOnRandomThreadAfterStart_2() throws Throwable {
1548         assertThrowsException(s -> s.setOnCancelled(event -> {
1549         }));
1550     }
1551 
1552     @Test (expected = IllegalStateException.class)
1553     public void cannotInvokeSettersOnRandomThreadAfterStart_3() throws Throwable {
1554         assertThrowsException(s -> s.setOnFailed(event -> { }));
1555     }
1556 
1557     @Test (expected = IllegalStateException.class)
1558     public void cannotInvokeSettersOnRandomThreadAfterStart_4() throws Throwable {
1559         assertThrowsException(s -> s.setOnReady(event -> { }));
1560     }
1561 
1562     @Test (expected = IllegalStateException.class)
1563     public void cannotInvokeSettersOnRandomThreadAfterStart_5() throws Throwable {
1564         assertThrowsException(s -> s.setOnRunning(event -> { }));
1565     }
1566 
1567     @Test (expected = IllegalStateException.class)
1568     public void cannotInvokeSettersOnRandomThreadAfterStart_6() throws Throwable {
1569         assertThrowsException(s -> s.setOnScheduled(event -> {
1570         }));
1571     }
1572 
1573     @Test (expected = IllegalStateException.class)
1574     public void cannotInvokeSettersOnRandomThreadAfterStart_7() throws Throwable {
1575         assertThrowsException(s -> s.setOnSucceeded(event -> { }));
1576     }
1577 
1578     @Test (expected = IllegalStateException.class)
1579     public void cannotInvokeGettersOnRandomThreadAfterStart_1() throws Throwable {
1580         assertThrowsException(s -> {
1581             s.getException();
1582         });
1583     }
1584 
1585     @Test (expected = IllegalStateException.class)
1586     public void cannotInvokeGettersOnRandomThreadAfterStart_2() throws Throwable {
1587         assertThrowsException(s -> {
1588             s.getExecutor();
1589         });
1590     }
1591 
1592     @Test (expected = IllegalStateException.class)
1593     public void cannotInvokeGettersOnRandomThreadAfterStart_3() throws Throwable {
1594         assertThrowsException(s -> {
1595             s.getMessage();
1596         });
1597     }
1598 
1599     @Test (expected = IllegalStateException.class)
1600     public void cannotInvokeGettersOnRandomThreadAfterStart_4() throws Throwable {
1601         assertThrowsException(s -> {
1602             s.getProgress();
1603         });
1604     }
1605 
1606     @Test (expected = IllegalStateException.class)
1607     public void cannotInvokeGettersOnRandomThreadAfterStart_5() throws Throwable {
1608         assertThrowsException(s -> {
1609             s.getOnCancelled();
1610         });
1611     }
1612 
1613     @Test (expected = IllegalStateException.class)
1614     public void cannotInvokeGettersOnRandomThreadAfterStart_6() throws Throwable {
1615         assertThrowsException(s -> {
1616             s.getOnFailed();
1617         });
1618     }
1619 
1620     @Test (expected = IllegalStateException.class)
1621     public void cannotInvokeGettersOnRandomThreadAfterStart_7() throws Throwable {
1622         assertThrowsException(s -> {
1623             s.getOnReady();
1624         });
1625     }
1626 
1627     @Test (expected = IllegalStateException.class)
1628     public void cannotInvokeGettersOnRandomThreadAfterStart_8() throws Throwable {
1629         assertThrowsException(s -> {
1630             s.getOnRunning();
1631         });
1632     }
1633 
1634     @Test (expected = IllegalStateException.class)
1635     public void cannotInvokeGettersOnRandomThreadAfterStart_9() throws Throwable {
1636         assertThrowsException(s -> {
1637             s.getOnScheduled();
1638         });
1639     }
1640 
1641     @Test (expected = IllegalStateException.class)
1642     public void cannotInvokeGettersOnRandomThreadAfterStart_10() throws Throwable {
1643         assertThrowsException(s -> {
1644             s.getOnSucceeded();
1645         });
1646     }
1647 
1648     @Test (expected = IllegalStateException.class)
1649     public void cannotInvokeGettersOnRandomThreadAfterStart_11() throws Throwable {
1650         assertThrowsException(s -> {
1651             s.isRunning();
1652         });
1653     }
1654 
1655     @Test (expected = IllegalStateException.class)
1656     public void cannotInvokeGettersOnRandomThreadAfterStart_12() throws Throwable {
1657         assertThrowsException(s -> {
1658             s.getState();
1659         });
1660     }
1661 
1662     @Test (expected = IllegalStateException.class)
1663     public void cannotInvokeGettersOnRandomThreadAfterStart_13() throws Throwable {
1664         assertThrowsException(s -> {
1665             s.getTitle();
1666         });
1667     }
1668 
1669     @Test (expected = IllegalStateException.class)
1670     public void cannotInvokeGettersOnRandomThreadAfterStart_14() throws Throwable {
1671         assertThrowsException(s -> {
1672             s.getTotalWork();
1673         });
1674     }
1675 
1676     @Test (expected = IllegalStateException.class)
1677     public void cannotInvokeGettersOnRandomThreadAfterStart_15() throws Throwable {
1678         assertThrowsException(s -> {
1679             s.getValue();
1680         });
1681     }
1682 
1683     @Test (expected = IllegalStateException.class)
1684     public void cannotInvokeGettersOnRandomThreadAfterStart_16() throws Throwable {
1685         assertThrowsException(s -> {
1686             s.getValue();
1687         });
1688     }
1689 
1690     @Test (expected = IllegalStateException.class)
1691     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_1() throws Throwable {
1692         assertThrowsException(s -> {
1693             s.exceptionProperty();
1694         });
1695     }
1696 
1697     @Test (expected = IllegalStateException.class)
1698     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_2() throws Throwable {
1699         assertThrowsException(s -> {
1700             s.executorProperty();
1701         });
1702     }
1703 
1704     @Test (expected = IllegalStateException.class)
1705     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_3() throws Throwable {
1706         assertThrowsException(s -> {
1707             s.messageProperty();
1708         });
1709     }
1710 
1711     @Test (expected = IllegalStateException.class)
1712     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_4() throws Throwable {
1713         assertThrowsException(s -> {
1714             s.progressProperty();
1715         });
1716     }
1717 
1718     @Test (expected = IllegalStateException.class)
1719     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_5() throws Throwable {
1720         assertThrowsException(s -> {
1721             s.onCancelledProperty();
1722         });
1723     }
1724 
1725     @Test (expected = IllegalStateException.class)
1726     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_6() throws Throwable {
1727         assertThrowsException(s -> {
1728             s.onFailedProperty();
1729         });
1730     }
1731 
1732     @Test (expected = IllegalStateException.class)
1733     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_7() throws Throwable {
1734         assertThrowsException(s -> {
1735             s.onReadyProperty();
1736         });
1737     }
1738 
1739     @Test (expected = IllegalStateException.class)
1740     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_8() throws Throwable {
1741         assertThrowsException(s -> {
1742             s.onRunningProperty();
1743         });
1744     }
1745 
1746     @Test (expected = IllegalStateException.class)
1747     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_9() throws Throwable {
1748         assertThrowsException(s -> {
1749             s.onScheduledProperty();
1750         });
1751     }
1752 
1753     @Test (expected = IllegalStateException.class)
1754     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_10() throws Throwable {
1755         assertThrowsException(s -> {
1756             s.onSucceededProperty();
1757         });
1758     }
1759 
1760     @Test (expected = IllegalStateException.class)
1761     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_11() throws Throwable {
1762         assertThrowsException(s -> {
1763             s.runningProperty();
1764         });
1765     }
1766 
1767     @Test (expected = IllegalStateException.class)
1768     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_12() throws Throwable {
1769         assertThrowsException(s -> {
1770             s.stateProperty();
1771         });
1772     }
1773 
1774     @Test (expected = IllegalStateException.class)
1775     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_13() throws Throwable {
1776         assertThrowsException(s -> {
1777             s.titleProperty();
1778         });
1779     }
1780 
1781     @Test (expected = IllegalStateException.class)
1782     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_14() throws Throwable {
1783         assertThrowsException(s -> {
1784             s.totalWorkProperty();
1785         });
1786     }
1787 
1788     @Test (expected = IllegalStateException.class)
1789     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_15() throws Throwable {
1790         assertThrowsException(s -> {
1791             s.valueProperty();
1792         });
1793     }
1794 
1795     @Test (expected = IllegalStateException.class)
1796     public void cannotInvokePropertyGettersOnRandomThreadAfterStart_16() throws Throwable {
1797         assertThrowsException(s -> {
1798             s.workDoneProperty();
1799         });
1800     }
1801 
1802     private void assertThrowsException(final ServiceTestExecution c) throws Throwable {
1803         RandomThread random = new RandomThread(() -> {
1804             DoNothingService s = null;
1805             try {
1806                 s = new DoNothingService();
1807                 s.start();
1808                 c.test(s);
1809             } finally {
1810                 if (s != null) s.shutdown();
1811             }
1812         });
1813 
1814         try {
1815             random.test();
1816         } catch (AssertionError er) {
1817             throw er.getCause();
1818         }
1819     }
1820 
1821     private interface ServiceTestExecution {
1822         public void test(DoNothingService s);
1823     }
1824 
1825     /**
1826      * Specialized thread used for checking access to various methods from a "random thread" other
1827      * than the FX thread. This class has built into it all the supported needed for handling
1828      * exceptions and so forth, such that assertion errors are raised if an exception occurs
1829      * on the thread, and also handles blocking until the thread body concludes.
1830      */
1831     private static final class RandomThread extends Thread {
1832         private final CountDownLatch testCompleted = new CountDownLatch(1);
1833         private Throwable error;
1834 
1835         public RandomThread(Runnable target) {
1836             super(target);
1837         }
1838 
1839         @Override public void run() {
1840             try {
1841                 super.run();
1842             } catch (Throwable th) {
1843                 error = th;
1844             } finally {
1845                 testCompleted.countDown();
1846             }
1847         }
1848 
1849         public void test() throws AssertionError {
1850             start();
1851             try {
1852                 testCompleted.await();
1853             } catch (InterruptedException e) {
1854                 throw new AssertionError("Test did not complete normally");
1855             }
1856             if (error != null) {
1857                 throw new AssertionError(error);
1858             }
1859         }
1860     }
1861 
1862     /**
1863      * A service which does absolutely nothing and isn't hardwired to believe that
1864      * the test thread is the FX thread (unlike the other services in these tests)
1865      */
1866     private static final class DoNothingService extends ServiceShim {
1867         private Thread pretendFXThread;
1868         private ConcurrentLinkedQueue<Runnable> eventQueue = new ConcurrentLinkedQueue<>();
1869         private volatile boolean shutdown = false;
1870 
1871         public DoNothingService() {
1872             setExecutor(command -> {
1873                 Thread backgroundThread = new Thread(command);
1874                 backgroundThread.start();
1875             });
1876         }
1877 
1878         void shutdown() {
1879             shutdown = true;
1880         }
1881 
1882         @Override protected Task createTask() {
1883             return new TaskShim() {
1884                 @Override protected Object call() throws Exception {
1885                     return null;
1886                 }
1887 
1888                 @Override public boolean isFxApplicationThread() {
1889                     return Thread.currentThread() == pretendFXThread;
1890                 }
1891 
1892                 @Override
1893                 public void runLater(Runnable r) {
1894                     DoNothingService.this.runLater(r);
1895                 }
1896             };
1897         }
1898 
1899         @Override public void runLater(Runnable r) {
1900             eventQueue.add(r);
1901             if (pretendFXThread == null) {
1902                 pretendFXThread = new Thread() {
1903                     @Override public void run() {
1904                         while (!shutdown) {
1905                             Runnable event = eventQueue.poll();
1906                             if (event != null) {
1907                                 event.run();
1908                             }
1909                         }
1910                     }
1911                 };
1912                 pretendFXThread.start();
1913             }
1914         }
1915 
1916         @Override public boolean isFxApplicationThread() {
1917             return Thread.currentThread() == pretendFXThread;
1918         }
1919     }
1920 
1921     /***************************************************************************
1922      *                                                                         *
1923      * A mythical subclass should be able to set an event handler and          *
1924      * have events fired on the Service work.                                  *
1925      *                                                                         *
1926      **************************************************************************/
1927 
1928     @Test public void eventFiredOnSubclassWorks() {
1929         final AtomicBoolean result = new AtomicBoolean(false);
1930         TestServiceFactory factory = new TestServiceFactory() {
1931             @Override public AbstractTask createTestTask() {
1932                 return new SimpleTask();
1933             }
1934 
1935             @Override public Service<String> createService() {
1936                 MythicalService svc = new MythicalService();
1937                 svc.setHandler(mythicalEvent -> result.set(true));
1938                 ServiceShim.fireEvent(svc, new MythicalEvent()); 
1939                 return svc;
1940             }
1941         };
1942         Service<String> svc = factory.createService();
1943         svc.start();
1944         assertTrue(result.get());
1945     }
1946     
1947     private static final class MythicalService extends ServiceShim<String> {
1948         public void setHandler(EventHandler<MythicalEvent> h) {
1949             ServiceShim.setEventHandler(this, MythicalEvent.ANY, h);
1950         }
1951 
1952         @Override protected Task<String> createTask() {
1953             return new SimpleTask();
1954         }
1955 
1956         @Override public void checkThread() { }
1957 
1958         @Override public void runLater(Runnable r) {
1959             r.run();
1960         }
1961     }
1962 }