1 /*
   2  * Copyright (c) 2013, 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 javafx.concurrent;
  27 
  28 import javafx.concurrent.mocks.EpicFailTask;
  29 import javafx.concurrent.mocks.SimpleTask;
  30 import javafx.event.EventHandler;
  31 import javafx.util.Callback;
  32 import javafx.util.Duration;
  33 import java.util.TimerTask;
  34 import java.util.concurrent.atomic.AtomicBoolean;
  35 import java.util.concurrent.atomic.AtomicInteger;
  36 import org.junit.Test;
  37 import static org.junit.Assert.assertEquals;
  38 import static org.junit.Assert.assertFalse;
  39 import static org.junit.Assert.assertNull;
  40 import static org.junit.Assert.assertTrue;
  41 
  42 /**
  43  * Tests for the ScheduledService.
  44  */
  45 public class ScheduledServiceTest extends ServiceTestBase {
  46     private static final Callback<Void, AbstractTask> EPIC_FAIL_FACTORY = param -> new EpicFailTask();
  47 
  48     /**
  49      * The service that we're going to test. Because a ScheduledService
  50      * in its default configuration will run forever and because for the
  51      * sake of testing we've essentially caused ScheduledServiceTest to
  52      * run as though it is single threaded, we have to make sure that each
  53      * individual iteration is paused and doesn't occur without an explicit
  54      * call. So in the test code you can call start(), and then read the wall
  55      * clock time, and then call iterate() to cause the scheduled service to
  56      * start the next iteration all without affecting the "wall clock" time
  57      * inappropriately with the test code. In this way we can test with very
  58      * fine tolerances in a consistent manner.
  59      */
  60     private ScheduledServiceMock s;
  61 
  62     /**
  63      * If specified by the test BEFORE the service is started, then this
  64      * task will be used by the service. Defaults to SimpleTask if null.
  65      */
  66     private Callback<Void,AbstractTask> taskFactory = null;
  67 
  68     /**
  69      * A fake "wall clock" time, to keep track of how much
  70      * time was spent executing a task, and how much time was
  71      * spent in the delay. We fake out the delay by overriding the
  72      * "schedule" method in ScheduledServiceMock, and we fake out
  73      * the task execution time by using a custom task which, when
  74      * executed, will add to the wall clock time.
  75      */
  76     private long wallClock;
  77 
  78     @Override protected TestServiceFactory setupServiceFactory() {
  79         return new TestServiceFactory() {
  80             @Override protected AbstractTask createTestTask() {
  81                 return taskFactory == null ? new SimpleTask() : taskFactory.call(null);
  82             }
  83 
  84             @Override protected Service<String> createService() {
  85                 return new ScheduledServiceMock(this);
  86             }
  87         };
  88     }
  89 
  90     @Override public void setup() {
  91         super.setup();
  92         s = (ScheduledServiceMock) service;
  93         wallClock = 0;
  94     }
  95 
  96     /**************************************************************************************************
  97      * Big pile of tests for making sure setting the cumulative period works in a predictable manner  *
  98      * regardless of what kind of output comes from the back-off algorithm, also taking into          *
  99      * account the maximum cumulative period value.                                                   *
 100      *************************************************************************************************/
 101 
 102     @Test public void setCumulativePeriod_MaxIsInfinity_TwoSeconds() {
 103         s.setCumulativePeriod(Duration.seconds(2));
 104         assertEquals(Duration.seconds(2), s.getCumulativePeriod());
 105     }
 106 
 107     @Test public void setCumulativePeriod_MaxIsInfinity_Negative() {
 108         s.setCumulativePeriod(Duration.seconds(-2));
 109         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 110     }
 111 
 112     @Test public void setCumulativePeriod_MaxIsInfinity_NegativeInfinity() {
 113         s.setCumulativePeriod(Duration.seconds(Double.NEGATIVE_INFINITY));
 114         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 115     }
 116 
 117     @Test public void setCumulativePeriod_MaxIsInfinity_NaN() {
 118         s.setCumulativePeriod(Duration.seconds(Double.NaN));
 119         assertEquals(Duration.UNKNOWN, s.getCumulativePeriod());
 120     }
 121 
 122     @Test public void setCumulativePeriod_MaxIsInfinity_PositiveInfinity() {
 123         s.setCumulativePeriod(Duration.seconds(Double.POSITIVE_INFINITY));
 124         assertEquals(Duration.INDEFINITE, s.getCumulativePeriod());
 125     }
 126 
 127     @Test public void setCumulativePeriod_MaxIsInfinity_MAX_VALUE() {
 128         s.setCumulativePeriod(Duration.millis(Double.MAX_VALUE));
 129         assertEquals(Duration.millis(Double.MAX_VALUE), s.getCumulativePeriod());
 130     }
 131 
 132     @Test public void setCumulativePeriod_MaxIsNaN_TwoSeconds() {
 133         s.setMaximumCumulativePeriod(Duration.UNKNOWN);
 134         s.setCumulativePeriod(Duration.seconds(2));
 135         assertEquals(Duration.seconds(2), s.getCumulativePeriod());
 136     }
 137 
 138     @Test public void setCumulativePeriod_MaxIsNaN_Negative() {
 139         s.setMaximumCumulativePeriod(Duration.UNKNOWN);
 140         s.setCumulativePeriod(Duration.seconds(-2));
 141         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 142     }
 143 
 144     @Test public void setCumulativePeriod_MaxIsNaN_NegativeInfinity() {
 145         s.setMaximumCumulativePeriod(Duration.UNKNOWN);
 146         s.setCumulativePeriod(Duration.seconds(Double.NEGATIVE_INFINITY));
 147         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 148     }
 149 
 150     @Test public void setCumulativePeriod_MaxIsNaN_NaN() {
 151         s.setMaximumCumulativePeriod(Duration.UNKNOWN);
 152         s.setCumulativePeriod(Duration.seconds(Double.NaN));
 153         assertEquals(Duration.UNKNOWN, s.getCumulativePeriod());
 154     }
 155 
 156     @Test public void setCumulativePeriod_MaxIsNaN_PositiveInfinity() {
 157         s.setMaximumCumulativePeriod(Duration.UNKNOWN);
 158         s.setCumulativePeriod(Duration.seconds(Double.POSITIVE_INFINITY));
 159         assertEquals(Duration.INDEFINITE, s.getCumulativePeriod());
 160     }
 161 
 162     @Test public void setCumulativePeriod_MaxIsNaN_MAX_VALUE() {
 163         s.setMaximumCumulativePeriod(Duration.UNKNOWN);
 164         s.setCumulativePeriod(Duration.millis(Double.MAX_VALUE));
 165         assertEquals(Duration.millis(Double.MAX_VALUE), s.getCumulativePeriod());
 166     }
 167 
 168     @Test public void setCumulativePeriod_MaxIsNull_TwoSeconds() {
 169         s.setMaximumCumulativePeriod(null);
 170         s.setCumulativePeriod(Duration.seconds(2));
 171         assertEquals(Duration.seconds(2), s.getCumulativePeriod());
 172     }
 173 
 174     @Test public void setCumulativePeriod_MaxIsNull_Negative() {
 175         s.setMaximumCumulativePeriod(null);
 176         s.setCumulativePeriod(Duration.seconds(-2));
 177         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 178     }
 179 
 180     @Test public void setCumulativePeriod_MaxIsNull_NegativeInfinity() {
 181         s.setMaximumCumulativePeriod(null);
 182         s.setCumulativePeriod(Duration.seconds(Double.NEGATIVE_INFINITY));
 183         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 184     }
 185 
 186     @Test public void setCumulativePeriod_MaxIsNull_NaN() {
 187         s.setMaximumCumulativePeriod(null);
 188         s.setCumulativePeriod(Duration.seconds(Double.NaN));
 189         assertEquals(Duration.UNKNOWN, s.getCumulativePeriod());
 190     }
 191 
 192     @Test public void setCumulativePeriod_MaxIsNull_PositiveInfinity() {
 193         s.setMaximumCumulativePeriod(null);
 194         s.setCumulativePeriod(Duration.seconds(Double.POSITIVE_INFINITY));
 195         assertEquals(Duration.INDEFINITE, s.getCumulativePeriod());
 196     }
 197 
 198     @Test public void setCumulativePeriod_MaxIsNull_MAX_VALUE() {
 199         s.setMaximumCumulativePeriod(null);
 200         s.setCumulativePeriod(Duration.millis(Double.MAX_VALUE));
 201         assertEquals(Duration.millis(Double.MAX_VALUE), s.getCumulativePeriod());
 202     }
 203 
 204     @Test public void setCumulativePeriod_MaxIs10_TwoSeconds() {
 205         s.setMaximumCumulativePeriod(Duration.seconds(10));
 206         s.setCumulativePeriod(Duration.seconds(2));
 207         assertEquals(Duration.seconds(2), s.getCumulativePeriod());
 208     }
 209 
 210     @Test public void setCumulativePeriod_MaxIs10_TenSeconds() {
 211         s.setMaximumCumulativePeriod(Duration.seconds(10));
 212         s.setCumulativePeriod(Duration.seconds(10));
 213         assertEquals(Duration.seconds(10), s.getCumulativePeriod());
 214     }
 215 
 216     @Test public void setCumulativePeriod_MaxIs10_TwelveSeconds() {
 217         s.setMaximumCumulativePeriod(Duration.seconds(10));
 218         s.setCumulativePeriod(Duration.seconds(12));
 219         assertEquals(Duration.seconds(10), s.getCumulativePeriod());
 220     }
 221 
 222     @Test public void setCumulativePeriod_MaxIs10_Negative() {
 223         s.setMaximumCumulativePeriod(Duration.seconds(10));
 224         s.setCumulativePeriod(Duration.seconds(-2));
 225         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 226     }
 227 
 228     @Test public void setCumulativePeriod_MaxIs10_NegativeInfinity() {
 229         s.setMaximumCumulativePeriod(Duration.seconds(10));
 230         s.setCumulativePeriod(Duration.seconds(Double.NEGATIVE_INFINITY));
 231         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 232     }
 233 
 234     @Test public void setCumulativePeriod_MaxIs10_NaN() {
 235         s.setMaximumCumulativePeriod(Duration.seconds(10));
 236         s.setCumulativePeriod(Duration.seconds(Double.NaN));
 237         assertEquals(Duration.UNKNOWN, s.getCumulativePeriod());
 238     }
 239 
 240     @Test public void setCumulativePeriod_MaxIs10_PositiveInfinity() {
 241         s.setMaximumCumulativePeriod(Duration.seconds(10));
 242         s.setCumulativePeriod(Duration.seconds(Double.POSITIVE_INFINITY));
 243         assertEquals(Duration.seconds(10), s.getCumulativePeriod());
 244     }
 245 
 246     @Test public void setCumulativePeriod_MaxIs10_MAX_VALUE() {
 247         s.setMaximumCumulativePeriod(Duration.seconds(10));
 248         s.setCumulativePeriod(Duration.millis(Double.MAX_VALUE));
 249         assertEquals(Duration.seconds(10), s.getCumulativePeriod());
 250     }
 251 
 252     @Test public void setCumulativePeriod_MaxIs0_TwoSeconds() {
 253         s.setMaximumCumulativePeriod(Duration.ZERO);
 254         s.setCumulativePeriod(Duration.seconds(2));
 255         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 256     }
 257 
 258     @Test public void setCumulativePeriod_MaxIs0_TenSeconds() {
 259         s.setMaximumCumulativePeriod(Duration.ZERO);
 260         s.setCumulativePeriod(Duration.seconds(10));
 261         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 262     }
 263 
 264     @Test public void setCumulativePeriod_MaxIs0_TwelveSeconds() {
 265         s.setMaximumCumulativePeriod(Duration.ZERO);
 266         s.setCumulativePeriod(Duration.seconds(12));
 267         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 268     }
 269 
 270     @Test public void setCumulativePeriod_MaxIs0_Negative() {
 271         s.setMaximumCumulativePeriod(Duration.ZERO);
 272         s.setCumulativePeriod(Duration.seconds(-2));
 273         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 274     }
 275 
 276     @Test public void setCumulativePeriod_MaxIs0_NegativeInfinity() {
 277         s.setMaximumCumulativePeriod(Duration.ZERO);
 278         s.setCumulativePeriod(Duration.seconds(Double.NEGATIVE_INFINITY));
 279         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 280     }
 281 
 282     @Test public void setCumulativePeriod_MaxIs0_NaN() {
 283         s.setMaximumCumulativePeriod(Duration.ZERO);
 284         s.setCumulativePeriod(Duration.seconds(Double.NaN));
 285         assertEquals(Duration.UNKNOWN, s.getCumulativePeriod());
 286     }
 287 
 288     @Test public void setCumulativePeriod_MaxIs0_PositiveInfinity() {
 289         s.setMaximumCumulativePeriod(Duration.ZERO);
 290         s.setCumulativePeriod(Duration.seconds(Double.POSITIVE_INFINITY));
 291         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 292     }
 293 
 294     @Test public void setCumulativePeriod_MaxIs0_MAX_VALUE() {
 295         s.setMaximumCumulativePeriod(Duration.ZERO);
 296         s.setCumulativePeriod(Duration.millis(Double.MAX_VALUE));
 297         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 298     }
 299 
 300     @Test public void setCumulativePeriod_MaxIsNegative_TwoSeconds() {
 301         s.setMaximumCumulativePeriod(Duration.seconds(-1));
 302         s.setCumulativePeriod(Duration.seconds(2));
 303         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 304     }
 305 
 306     @Test public void setCumulativePeriod_MaxIsNegative_TenSeconds() {
 307         s.setMaximumCumulativePeriod(Duration.seconds(-1));
 308         s.setCumulativePeriod(Duration.seconds(10));
 309         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 310     }
 311 
 312     @Test public void setCumulativePeriod_MaxIsNegative_TwelveSeconds() {
 313         s.setMaximumCumulativePeriod(Duration.seconds(-1));
 314         s.setCumulativePeriod(Duration.seconds(12));
 315         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 316     }
 317 
 318     @Test public void setCumulativePeriod_MaxIsNegative_Negative() {
 319         s.setMaximumCumulativePeriod(Duration.seconds(-1));
 320         s.setCumulativePeriod(Duration.seconds(-2));
 321         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 322     }
 323 
 324     @Test public void setCumulativePeriod_MaxIsNegative_NegativeInfinity() {
 325         s.setMaximumCumulativePeriod(Duration.seconds(-1));
 326         s.setCumulativePeriod(Duration.seconds(Double.NEGATIVE_INFINITY));
 327         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 328     }
 329 
 330     @Test public void setCumulativePeriod_MaxIsNegative_NaN() {
 331         s.setMaximumCumulativePeriod(Duration.seconds(-1));
 332         s.setCumulativePeriod(Duration.seconds(Double.NaN));
 333         assertEquals(Duration.UNKNOWN, s.getCumulativePeriod());
 334     }
 335 
 336     @Test public void setCumulativePeriod_MaxIsNegative_PositiveInfinity() {
 337         s.setMaximumCumulativePeriod(Duration.seconds(-1));
 338         s.setCumulativePeriod(Duration.seconds(Double.POSITIVE_INFINITY));
 339         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 340     }
 341 
 342     @Test public void setCumulativePeriod_MaxIsNegative_MAX_VALUE() {
 343         s.setMaximumCumulativePeriod(Duration.seconds(-1));
 344         s.setCumulativePeriod(Duration.millis(Double.MAX_VALUE));
 345         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 346     }
 347 
 348     // TODO I think Duration boundary condition checking is wrong. It doesn't use isInfinite, but checks
 349     // directly with POSITIVE_INFINITY, neglecting to check NEGATIVE_INFINITY.
 350 
 351     // DELAY
 352     // Test that:
 353     //     delay (positive, unknown, zero) works the first time
 354     //     delay is not used on the next iteration
 355     //     delay works on restart
 356     //     delay works on reset / start
 357 
 358     @Test public void delayIsHonored_Positive() throws InterruptedException {
 359         s.setDelay(Duration.seconds(1));
 360         s.start();
 361         assertEquals(1000, wallClock);
 362     }
 363 
 364     @Test public void delayIsHonored_Unknown() throws InterruptedException {
 365         s.setDelay(Duration.UNKNOWN);
 366         s.start();
 367         assertEquals(0, wallClock);
 368     }
 369 
 370     @Test public void delayIsHonored_Infinite() throws InterruptedException {
 371         s.setDelay(Duration.INDEFINITE);
 372         s.start();
 373         assertEquals(Long.MAX_VALUE, wallClock);
 374     }
 375 
 376     @Test public void delayIsHonored_ZERO() throws InterruptedException {
 377         s.setDelay(Duration.ZERO);
 378         s.start();
 379         assertEquals(0, wallClock);
 380     }
 381 
 382     @Test public void delayIsNotUsedOnSubsequentIteration() {
 383         s.setDelay(Duration.seconds(1));
 384         s.setPeriod(Duration.seconds(3));
 385         s.start();
 386         s.iterate();
 387         assertEquals(4000, wallClock); // 1 sec initial delay + 3 second iteration delay
 388     }
 389 
 390     @Test public void delayIsUsedOnRestart() {
 391         s.setDelay(Duration.seconds(1));
 392         s.setPeriod(Duration.seconds(3));
 393         s.start();
 394         s.iterate();
 395         s.cancel();
 396         wallClock = 0;
 397         s.restart();
 398         assertEquals(1000, wallClock);
 399     }
 400 
 401     @Test public void delayIsUsedOnStartFollowingReset() {
 402         s.setDelay(Duration.seconds(1));
 403         s.setPeriod(Duration.seconds(3));
 404         s.start();
 405         s.iterate();
 406         s.cancel();
 407         wallClock = 0;
 408         s.reset();
 409         s.start();
 410         assertEquals(1000, wallClock);
 411     }
 412 
 413     // PERIOD
 414     // Test that:
 415     //     period does not contribute to the delay
 416     //     amount of time from start of one iteration (run) to another (run) is never < period
 417     //         run time < period
 418     //         run time == period
 419     //         run time > period
 420     //     start of last period is reset after "reset" call (or restart)
 421 
 422     @Test public void periodDoesNotContributeToDelay() {
 423         s.setDelay(Duration.seconds(1));
 424         s.setPeriod(Duration.seconds(3));
 425         s.start();
 426         assertEquals(1000, wallClock);
 427     }
 428 
 429     @Test public void executionTimeLessThanPeriod() {
 430         s.setDelay(Duration.seconds(1));
 431         s.setPeriod(Duration.seconds(3));
 432         s.start();
 433         s.iterate();
 434         assertEquals(4000, wallClock); // 1 sec initial delay + 3 second iteration delay
 435     }
 436 
 437     @Test public void executionTimeEqualsPeriod() {
 438         s.setDelay(Duration.seconds(1));
 439         s.setPeriod(Duration.seconds(3));
 440         s.start();
 441         wallClock += 3000; // simulate execution time
 442         s.iterate();
 443         assertEquals(4000, wallClock);
 444     }
 445 
 446     @Test public void executionTimeExceedsPeriod() {
 447         s.setDelay(Duration.seconds(1));
 448         s.setPeriod(Duration.seconds(3));
 449         s.start();
 450         wallClock += 6000; // simulate execution time
 451         s.iterate();
 452         assertEquals(7000, wallClock);
 453     }
 454 
 455     @Test public void startOfPeriodIsResetAfterReset() {
 456         s.setDelay(Duration.seconds(1));
 457         s.setPeriod(Duration.seconds(3));
 458         s.start();
 459         wallClock += 6000; // simulate execution time
 460         s.iterate();
 461         s.cancel();
 462         wallClock = 0;
 463         s.reset();
 464         s.start();
 465         s.iterate();
 466         assertEquals(4000, wallClock);
 467     }
 468 
 469     @Test public void startOfPeriodIsResetAfterRestart() {
 470         s.setDelay(Duration.seconds(1));
 471         s.setPeriod(Duration.seconds(3));
 472         s.start();
 473         wallClock += 6000; // simulate execution time
 474         s.iterate();
 475         s.cancel();
 476         wallClock = 0;
 477         s.reset();
 478         s.start();
 479         s.iterate();
 480         assertEquals(4000, wallClock);
 481     }
 482 
 483     // COMPUTE BACKOFF
 484     // Test that:
 485     //     on task failure, cumulative period is increased according to compute backoff
 486     //          EXPONENTIAL_BACKOFF_STRATEGY, LOGARITHMIC_BACKOFF_STRATEGY, LINEAR_BACKOFF_STRATEGY, custom backoff
 487 
 488     @Test public void onFailureCumulativePeriodIsIncreased_EXPONENTIAL_BACKOFF_zero() {
 489         s.setBackoffStrategy(ScheduledService.EXPONENTIAL_BACKOFF_STRATEGY);
 490         s.setPeriod(Duration.ZERO);
 491         taskFactory = EPIC_FAIL_FACTORY;
 492         s.start();
 493         assertEquals(Duration.millis(Math.exp(1)), s.getCumulativePeriod());
 494     }
 495 
 496     @Test public void onFailureCumulativePeriodIsIncreased_EXPONENTIAL_BACKOFF_one() {
 497         s.setBackoffStrategy(ScheduledService.EXPONENTIAL_BACKOFF_STRATEGY);
 498         s.setPeriod(Duration.seconds(1));
 499         taskFactory = EPIC_FAIL_FACTORY;
 500         s.start();
 501         assertEquals(Duration.millis(1000 + (1000 * Math.exp(1))), s.getCumulativePeriod());
 502     }
 503 
 504     @Test public void onFailureCumulativePeriodIsIncreased_EXPONENTIAL_BACKOFF_indefinite() {
 505         s.setBackoffStrategy(ScheduledService.EXPONENTIAL_BACKOFF_STRATEGY);
 506         s.setPeriod(Duration.INDEFINITE);
 507         taskFactory = EPIC_FAIL_FACTORY;
 508         s.start();
 509         assertEquals(Duration.INDEFINITE, s.getCumulativePeriod());
 510     }
 511 
 512     @Test public void onFailureCumulativePeriodIsIncreased_EXPONENTIAL_BACKOFF_unknown() {
 513         s.setBackoffStrategy(ScheduledService.EXPONENTIAL_BACKOFF_STRATEGY);
 514         s.setPeriod(Duration.UNKNOWN);
 515         taskFactory = EPIC_FAIL_FACTORY;
 516         s.start();
 517         assertEquals(Duration.UNKNOWN, s.getCumulativePeriod());
 518     }
 519 
 520     @Test public void onFailureCumulativePeriodIsIncreased_LOGARITHMIC_BACKOFF_zero() {
 521         s.setBackoffStrategy(ScheduledService.LOGARITHMIC_BACKOFF_STRATEGY);
 522         s.setPeriod(Duration.ZERO);
 523         taskFactory = EPIC_FAIL_FACTORY;
 524         s.start();
 525         assertEquals(Duration.millis(Math.log1p(1)), s.getCumulativePeriod());
 526     }
 527 
 528     @Test public void onFailureCumulativePeriodIsIncreased_LOGARITHMIC_BACKOFF_one() {
 529         s.setBackoffStrategy(ScheduledService.LOGARITHMIC_BACKOFF_STRATEGY);
 530         s.setPeriod(Duration.seconds(1));
 531         taskFactory = EPIC_FAIL_FACTORY;
 532         s.start();
 533         assertEquals(Duration.millis(1000 + (1000 * Math.log1p(1))), s.getCumulativePeriod());
 534     }
 535 
 536     @Test public void onFailureCumulativePeriodIsIncreased_LOGARITHMIC_BACKOFF_indefinite() {
 537         s.setBackoffStrategy(ScheduledService.LOGARITHMIC_BACKOFF_STRATEGY);
 538         s.setPeriod(Duration.INDEFINITE);
 539         taskFactory = EPIC_FAIL_FACTORY;
 540         s.start();
 541         assertEquals(Duration.INDEFINITE, s.getCumulativePeriod());
 542     }
 543 
 544     @Test public void onFailureCumulativePeriodIsIncreased_LOGARITHMIC_BACKOFF_unknown() {
 545         s.setBackoffStrategy(ScheduledService.LOGARITHMIC_BACKOFF_STRATEGY);
 546         s.setPeriod(Duration.UNKNOWN);
 547         taskFactory = EPIC_FAIL_FACTORY;
 548         s.start();
 549         assertEquals(Duration.UNKNOWN, s.getCumulativePeriod());
 550     }
 551 
 552     @Test public void onFailureCumulativePeriodIsIncreased_LINEAR_BACKOFF_zero() {
 553         s.setBackoffStrategy(ScheduledService.LINEAR_BACKOFF_STRATEGY);
 554         s.setPeriod(Duration.ZERO);
 555         taskFactory = EPIC_FAIL_FACTORY;
 556         s.start();
 557         assertEquals(Duration.millis(1), s.getCumulativePeriod());
 558     }
 559 
 560     @Test public void onFailureCumulativePeriodIsIncreased_LINEAR_BACKOFF_one() {
 561         s.setBackoffStrategy(ScheduledService.LINEAR_BACKOFF_STRATEGY);
 562         s.setPeriod(Duration.seconds(1));
 563         taskFactory = EPIC_FAIL_FACTORY;
 564         s.start();
 565         assertEquals(Duration.millis(1000 + (1000 * 1)), s.getCumulativePeriod());
 566     }
 567 
 568     @Test public void onFailureCumulativePeriodIsIncreased_LINEAR_BACKOFF_indefinite() {
 569         s.setBackoffStrategy(ScheduledService.LINEAR_BACKOFF_STRATEGY);
 570         s.setPeriod(Duration.INDEFINITE);
 571         taskFactory = EPIC_FAIL_FACTORY;
 572         s.start();
 573         assertEquals(Duration.INDEFINITE, s.getCumulativePeriod());
 574     }
 575 
 576     @Test public void onFailureCumulativePeriodIsIncreased_LINEAR_BACKOFF_unknown() {
 577         s.setBackoffStrategy(ScheduledService.LINEAR_BACKOFF_STRATEGY);
 578         s.setPeriod(Duration.UNKNOWN);
 579         taskFactory = EPIC_FAIL_FACTORY;
 580         s.start();
 581         assertEquals(Duration.UNKNOWN, s.getCumulativePeriod());
 582     }
 583 
 584     // CUMULATIVE PERIOD
 585     // Test that:
 586     //     cumulative period is initially equivalent to period
 587     //         Cumulative period is set when the service enters "scheduled" state
 588     //     cumulative period is unchanged after successful iteration
 589     //     cumulative period is modified on failure (tested in onFailure*** tests)
 590     //     cumulative period is not modified on cancel
 591 
 592     @Test public void cumulativePeriodSetWhenScheduled() {
 593         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 594         s.setPeriod(Duration.seconds(1));
 595         assertEquals(Duration.ZERO, s.getCumulativePeriod());
 596         s.start();
 597         assertEquals(Duration.seconds(1), s.getCumulativePeriod());
 598     }
 599 
 600     @Test public void cumulativePeriodDoesNotChangeOnSuccessfulRun() {
 601         s.setPeriod(Duration.seconds(1));
 602         s.start();
 603         s.iterate();
 604         assertEquals(Duration.seconds(1), s.getCumulativePeriod());
 605     }
 606 
 607     @Test public void cumulativePeriodResetOnSuccessfulRun() {
 608         final AtomicInteger counter = new AtomicInteger();
 609         taskFactory = param -> new AbstractTask() {
 610             @Override protected String call() throws Exception {
 611                 int c = counter.incrementAndGet();
 612                 if (c < 10) throw new Exception("Kaboom!");
 613                 return "Success";
 614             }
 615         };
 616         s.setPeriod(Duration.seconds(1));
 617         s.start();
 618         for (int i=0; i<8; i++) s.iterate();
 619         assertTrue(Duration.seconds(1).lessThan(s.getCumulativePeriod()));
 620         s.iterate();
 621         assertEquals(Duration.seconds(1), s.getCumulativePeriod());
 622     }
 623 
 624     @Test public void cumulativePeriodDoesNotChangeOnCancelRun() {
 625         s.setPeriod(Duration.seconds(1));
 626         s.start();
 627         s.iterate();
 628         s.cancel();
 629         assertEquals(Duration.seconds(1), s.getCumulativePeriod());
 630     }
 631 
 632     // RESTART ON FAILURE
 633     // Test that:
 634     //     value of true causes a new iteration if the task fails
 635     //     value of false causes the Service to enter Failed state if the task fails.
 636 
 637     @Test public void restartOnFailure_True() {
 638         final AtomicInteger counter = new AtomicInteger();
 639         taskFactory = new Callback<Void, AbstractTask>() {
 640             @Override public AbstractTask call(Void param) {
 641                 return new EpicFailTask() {
 642                     @Override protected String call() throws Exception {
 643                         counter.incrementAndGet();
 644                         return super.call();
 645                     }
 646                 };
 647             }
 648         };
 649         s.start();
 650         assertEquals(Worker.State.SCHEDULED, s.getState());
 651         s.iterate();
 652         assertEquals(2, counter.get());
 653     }
 654 
 655     @Test public void restartOnFailure_False() {
 656         final AtomicInteger counter = new AtomicInteger();
 657         taskFactory = new Callback<Void, AbstractTask>() {
 658             @Override public AbstractTask call(Void param) {
 659                 return new EpicFailTask() {
 660                     @Override protected String call() throws Exception {
 661                         counter.incrementAndGet();
 662                         return super.call();
 663                     }
 664                 };
 665             }
 666         };
 667         s.setRestartOnFailure(false);
 668         s.start();
 669         assertEquals(Worker.State.FAILED, s.getState());
 670         assertEquals(1, counter.get());
 671     }
 672 
 673     // MAXIMUM FAILURE COUNT / CURRENT FAILURE COUNT
 674     // Test that:
 675     //     service iterates while currentFailureCount < maximumFailureCount
 676     //     service fails after currentFailureCount == maximumFailureCount
 677     //         service halts when this happens.
 678     //     currentFailureCount increments on each failure by 1
 679     //     currentFailureCount is reset on "reset" and "restart"
 680 
 681     @Test public void serviceIteratesWhile_CurrentFailureCount_IsLessThan_MaximumFailureCount() {
 682         final AtomicInteger counter = new AtomicInteger();
 683         taskFactory = new Callback<Void, AbstractTask>() {
 684             @Override public AbstractTask call(Void param) {
 685                 return new EpicFailTask() {
 686                     @Override protected String call() throws Exception {
 687                         counter.incrementAndGet();
 688                         return super.call();
 689                     }
 690                 };
 691             }
 692         };
 693         s.setMaximumFailureCount(10);
 694         s.start();
 695         while (s.getState() != Worker.State.FAILED) {
 696             assertEquals(counter.get(), s.getCurrentFailureCount());
 697             s.iterate();
 698         }
 699         assertEquals(10, counter.get());
 700         assertEquals(counter.get(), s.getCurrentFailureCount());
 701     }
 702 
 703     @Test public void currentFailureCountIsResetOnRestart() {
 704         taskFactory = EPIC_FAIL_FACTORY;
 705         s.start();
 706         for (int i=0; i<10; i++) s.iterate();
 707         taskFactory = null;
 708         s.restart();
 709         assertEquals(0, s.getCurrentFailureCount());
 710     }
 711 
 712     @Test public void currentFailureCountIsResetOnReset() {
 713         taskFactory = EPIC_FAIL_FACTORY;
 714         s.start();
 715         for (int i=0; i<10; i++) s.iterate();
 716         s.cancel();
 717         s.reset();
 718         assertEquals(0, s.getCurrentFailureCount());
 719     }
 720 
 721     @Test public void currentFailureCountIsNotResetOnCancel() {
 722         taskFactory = EPIC_FAIL_FACTORY;
 723         s.start();
 724         for (int i=0; i<10; i++) s.iterate();
 725         s.cancel();
 726         assertEquals(11, s.getCurrentFailureCount());
 727     }
 728 
 729     // LAST VALUE
 730     // Test that:
 731     //     last value starts as null
 732     //     last value is still null if first iteration fails
 733     //     last value equals value from successful iteration (1 & 2)
 734     //     last value is cleared on "restart" / "reset"
 735 
 736     @Test public void lastValueIsInitiallyNull() {
 737         assertNull(s.getLastValue());
 738     }
 739 
 740     @Test public void lastValueIsNullAfterFailedFirstIteration() {
 741         taskFactory = EPIC_FAIL_FACTORY;
 742         s.start();
 743         assertNull(s.getLastValue());
 744     }
 745 
 746     @Test public void lastValueIsSetAfterSuccessfulFirstIteration() {
 747         s.start();
 748         assertEquals("Sentinel", s.getLastValue());
 749         assertNull(s.getValue());
 750     }
 751 
 752     @Test public void lastValueIsSetAfterFailedFirstIterationAndSuccessfulSecondIteration() {
 753         final AtomicInteger counter = new AtomicInteger();
 754         taskFactory = param -> new AbstractTask() {
 755             @Override protected String call() throws Exception {
 756                 int c = counter.incrementAndGet();
 757                 if (c == 1) throw new Exception("Bombed out!");
 758                 return "Success";
 759             }
 760         };
 761         s.start();
 762         assertNull(s.getLastValue());
 763         assertNull(s.getValue());
 764         s.iterate();
 765         assertEquals("Success", s.getLastValue());
 766         assertNull(s.getValue());
 767     }
 768 
 769     @Test public void lastValueIsUnchangedAfterSuccessfulFirstIterationAndFailedSecondIteration() {
 770         final AtomicInteger counter = new AtomicInteger();
 771         taskFactory = param -> new AbstractTask() {
 772             @Override protected String call() throws Exception {
 773                 int c = counter.incrementAndGet();
 774                 if (c == 1) return "Success";
 775                 throw new Exception("Bombed out!");
 776             }
 777         };
 778         s.start();
 779         assertEquals("Success", s.getLastValue());
 780         assertNull(s.getValue());
 781         s.iterate();
 782         assertEquals("Success", s.getLastValue());
 783         assertNull(s.getValue());
 784     }
 785 
 786     @Test public void lastValueIsClearedOnReset() {
 787         s.start();
 788         assertEquals("Sentinel", s.getLastValue());
 789         s.cancel();
 790         assertEquals("Sentinel", s.getLastValue());
 791         s.reset();
 792         assertNull(s.getLastValue());
 793     }
 794 
 795     @Test public void callingCancelFromOnSucceededEventHandlerShouldStopScheduledService() {
 796         AtomicBoolean onReadyCalled = new AtomicBoolean();
 797         AtomicBoolean onScheduledCalled = new AtomicBoolean();
 798         AtomicBoolean onCancelledCalled = new AtomicBoolean();
 799         s.setOnSucceeded(event -> {
 800             s.cancel();
 801             // Reset these so that they only get set to true if called
 802             // after the cancel step
 803             onReadyCalled.set(false);
 804             onScheduledCalled.set(false);
 805             onCancelledCalled.set(false);
 806         });
 807         s.setOnReady(event -> onReadyCalled.set(true));
 808         s.setOnScheduled(event -> onScheduledCalled.set(true));
 809         s.setOnCancelled(event -> onCancelledCalled.set(true));
 810 
 811         s.start();
 812         assertFalse(s.isRunning());
 813         assertEquals(Worker.State.CANCELLED, s.getState());
 814         assertTrue(onReadyCalled.get());
 815         assertTrue(onScheduledCalled.get());
 816         assertTrue(onCancelledCalled.get());
 817     }
 818 
 819     /**
 820      * Allows us to monkey with how the threading works for the sake of testing.
 821      * Basically, you just call start() in order to go through an entire first
 822      * iteration, and a call to iterate() causes it to go through a subsequent
 823      * iteration. At the end of each iteration, you are in the SCHEDULED state,
 824      * unless failures occurred while running the task that caused the service
 825      * to finally enter the FAILED state.
 826      */
 827     private final class ScheduledServiceMock extends ScheduledService<String> {
 828         private TestServiceFactory factory;
 829         private Task<String> nextTask = null;
 830 
 831         ScheduledServiceMock(TestServiceFactory f) {
 832             this.factory = f;
 833         }
 834 
 835         @Override protected Task<String> createTask() {
 836             factory.currentTask = factory.createTestTask();
 837             factory.currentTask.test = factory.test;
 838             return factory.currentTask;
 839         }
 840 
 841         @Override void checkThread() { }
 842 
 843         @Override void schedule(TimerTask task, long delay) {
 844             wallClock += delay;
 845             task.run();
 846         }
 847 
 848         @Override protected void executeTask(Task<String> task) {
 849             nextTask = task;
 850             if (isFreshStart()) iterate();
 851         }
 852 
 853         @Override long clock() {
 854             return wallClock;
 855         }
 856 
 857         @Override boolean isFxApplicationThread() {
 858             return Thread.currentThread() == factory.appThread;
 859         }
 860 
 861         void iterate() {
 862             assert nextTask != null;
 863             Task<String> task = nextTask;
 864             nextTask = null;
 865 
 866             super.executeTask(task);
 867             handleEvents();
 868         }
 869     }
 870 }