Skippable implements Runnable { @Override public void run() { final Availability availability = tenant.getAvailability(); final Instant nextCheck = availability.nextAvailabilityCheck(); LOGGER.trace("Next availability check: {}.", nextCheck); if (nextCheck.isAfter(DateUtil.now())) { LOGGER.debug("Not running {} on account of the robot being temporarily suspended.", this); return; } LOGGER.trace("Running {}.", this); try { final OffsetDateTime now = DateUtil.offsetNow(); toRun.run(); final Optional<Instant> becameAvailable = availability.registerSuccess(); becameAvailable.ifPresent(unavailableSince -> { final OffsetDateTime since = unavailableSince.atZone(Defaults.ZONE_ID) .toOffsetDateTime(); LOGGER.debug("Unavailability over, lasted since {}.", since); tenant.fire(roboZonkyDaemonResumed(since, now)); }); LOGGER.trace("Successfully finished {}.", this); } catch (final Throwable t) { if (SimpleSkippable.isRecoverable(t)) { final Exception ex = (Exception) t; final boolean becameUnavailable = availability.registerException(ex); if (becameUnavailable) { LOGGER.debug("Unavailability starting."); tenant.fire(roboZonkyDaemonSuspended(ex)); } } else { shutdownCall.accept(t); throw t; } } finally { LOGGER.trace("Finished {}.", this); } } Skippable(final Runnable toRun, final Class<?> type, final PowerTenant tenant,
            final Consumer<Throwable> shutdownCall);   Skippable(final Runnable toRun, final PowerTenant tenant, final Consumer<Throwable> shutdownCall);   Skippable(final Runnable toRun, final PowerTenant tenant); @Override void run(); @Override String toString();  }
@Test void fails() { final Runnable r = mock(Runnable.class); doThrow(IllegalStateException.class).when(r) .run(); final PowerTenant t = mockTenant(); final Skippable s = new Skippable(r, t); s.run(); assertThat(this.getEventsRequested()).hasSize(1) .first() .isInstanceOf(RoboZonkyDaemonSuspendedEvent.class); }
@Test void dies() { final Runnable r = mock(Runnable.class); doThrow(OutOfMemoryError.class).when(r) .run(); final PowerTenant t = mockTenant(); final Consumer<Throwable> c = mock(Consumer.class); final Skippable s = new Skippable(r, t, c); assertThatThrownBy(s::run).isInstanceOf(OutOfMemoryError.class); verify(c, only()).accept(any()); }
@Test void unavailable() { final Instant now = Instant.now(); setClock(Clock.fixed(now, Defaults.ZONE_ID)); final Runnable r = mock(Runnable.class); doThrow(new ClientErrorException(Response.Status.TOO_MANY_REQUESTS)).when(r) .run(); final PowerTenant t = new TenantBuilder() .withApi(new ApiProvider(null)) .withSecrets(SecretProvider.inMemory("someone@somewhere.cz")) .build(false); final Skippable s = new Skippable(r, t); logger.debug("First run."); s.run(); verify(r, times(1)).run(); assertThat(t.getAvailability() .isAvailable()).isFalse(); final int mandatoryDelay = 60; setClock(Clock.fixed(now.plus(Duration.ofSeconds(mandatoryDelay + 1)), Defaults.ZONE_ID)); logger.debug("Second run."); doThrow(ServerErrorException.class).when(r) .run(); s.run(); verify(r, times(2)).run(); assertThat(t.getAvailability() .isAvailable()).isFalse(); setClock(Clock.fixed(now.plus(Duration.ofSeconds(mandatoryDelay + 2)), Defaults.ZONE_ID)); logger.debug("Third run."); doThrow(ResponseProcessingException.class).when(r) .run(); s.run(); verify(r, times(3)).run(); assertThat(t.getAvailability() .isAvailable()).isFalse(); setClock(Clock.fixed(now.plus(Duration.ofSeconds(mandatoryDelay + 3)), Defaults.ZONE_ID)); logger.debug("Fourth run."); doNothing().when(r) .run(); s.run(); verify(r, times(3)).run(); assertThat(t.getAvailability() .isAvailable()).isFalse(); setClock(Clock.fixed(now.plus(Duration.ofSeconds(mandatoryDelay + 4)), Defaults.ZONE_ID)); logger.debug("Fourth run."); s.run(); verify(r, times(4)).run(); assertThat(t.getAvailability() .isAvailable()).isTrue(); }