blob: 5f4210621ea83e307c33de215a44f5e91c6401c8 [file] [log] [blame]
/*
* Copyright (C) 2009 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.util.concurrent;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static java.lang.Thread.currentThread;
import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Service.Listener;
import com.google.common.util.concurrent.Service.State;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import junit.framework.TestCase;
/**
* Unit test for {@link AbstractService}.
*
* @author Jesse Wilson
*/
public class AbstractServiceTest extends TestCase {
private static final long LONG_TIMEOUT_MILLIS = 10000;
private Thread executionThread;
private Throwable thrownByExecutionThread;
public void testNoOpServiceStartStop() throws Exception {
NoOpService service = new NoOpService();
RecordingListener listener = RecordingListener.record(service);
assertEquals(State.NEW, service.state());
assertFalse(service.isRunning());
assertFalse(service.running);
service.startAsync();
assertEquals(State.RUNNING, service.state());
assertTrue(service.isRunning());
assertTrue(service.running);
service.stopAsync();
assertEquals(State.TERMINATED, service.state());
assertFalse(service.isRunning());
assertFalse(service.running);
assertEquals(
ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.TERMINATED),
listener.getStateHistory());
}
public void testNoOpServiceStartAndWaitStopAndWait() throws Exception {
NoOpService service = new NoOpService();
service.startAsync().awaitRunning();
assertEquals(State.RUNNING, service.state());
service.stopAsync().awaitTerminated();
assertEquals(State.TERMINATED, service.state());
}
public void testNoOpServiceStartAsyncAndAwaitStopAsyncAndAwait() throws Exception {
NoOpService service = new NoOpService();
service.startAsync().awaitRunning();
assertEquals(State.RUNNING, service.state());
service.stopAsync().awaitTerminated();
assertEquals(State.TERMINATED, service.state());
}
public void testNoOpServiceStopIdempotence() throws Exception {
NoOpService service = new NoOpService();
RecordingListener listener = RecordingListener.record(service);
service.startAsync().awaitRunning();
assertEquals(State.RUNNING, service.state());
service.stopAsync();
service.stopAsync();
assertEquals(State.TERMINATED, service.state());
assertEquals(
ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.TERMINATED),
listener.getStateHistory());
}
public void testNoOpServiceStopIdempotenceAfterWait() throws Exception {
NoOpService service = new NoOpService();
service.startAsync().awaitRunning();
service.stopAsync().awaitTerminated();
service.stopAsync();
assertEquals(State.TERMINATED, service.state());
}
public void testNoOpServiceStopIdempotenceDoubleWait() throws Exception {
NoOpService service = new NoOpService();
service.startAsync().awaitRunning();
assertEquals(State.RUNNING, service.state());
service.stopAsync().awaitTerminated();
service.stopAsync().awaitTerminated();
assertEquals(State.TERMINATED, service.state());
}
public void testNoOpServiceStartStopAndWaitUninterruptible() throws Exception {
NoOpService service = new NoOpService();
currentThread().interrupt();
try {
service.startAsync().awaitRunning();
assertEquals(State.RUNNING, service.state());
service.stopAsync().awaitTerminated();
assertEquals(State.TERMINATED, service.state());
assertTrue(currentThread().isInterrupted());
} finally {
Thread.interrupted(); // clear interrupt for future tests
}
}
private static class NoOpService extends AbstractService {
boolean running = false;
@Override
protected void doStart() {
assertFalse(running);
running = true;
notifyStarted();
}
@Override
protected void doStop() {
assertTrue(running);
running = false;
notifyStopped();
}
}
public void testManualServiceStartStop() throws Exception {
ManualSwitchedService service = new ManualSwitchedService();
RecordingListener listener = RecordingListener.record(service);
service.startAsync();
assertEquals(State.STARTING, service.state());
assertFalse(service.isRunning());
assertTrue(service.doStartCalled);
service.notifyStarted(); // usually this would be invoked by another thread
assertEquals(State.RUNNING, service.state());
assertTrue(service.isRunning());
service.stopAsync();
assertEquals(State.STOPPING, service.state());
assertFalse(service.isRunning());
assertTrue(service.doStopCalled);
service.notifyStopped(); // usually this would be invoked by another thread
assertEquals(State.TERMINATED, service.state());
assertFalse(service.isRunning());
assertEquals(
ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.TERMINATED),
listener.getStateHistory());
}
public void testManualServiceNotifyStoppedWhileRunning() throws Exception {
ManualSwitchedService service = new ManualSwitchedService();
RecordingListener listener = RecordingListener.record(service);
service.startAsync();
service.notifyStarted();
service.notifyStopped();
assertEquals(State.TERMINATED, service.state());
assertFalse(service.isRunning());
assertFalse(service.doStopCalled);
assertEquals(
ImmutableList.of(State.STARTING, State.RUNNING, State.TERMINATED),
listener.getStateHistory());
}
public void testManualServiceStopWhileStarting() throws Exception {
ManualSwitchedService service = new ManualSwitchedService();
RecordingListener listener = RecordingListener.record(service);
service.startAsync();
assertEquals(State.STARTING, service.state());
assertFalse(service.isRunning());
assertTrue(service.doStartCalled);
service.stopAsync();
assertEquals(State.STOPPING, service.state());
assertFalse(service.isRunning());
assertFalse(service.doStopCalled);
service.notifyStarted();
assertEquals(State.STOPPING, service.state());
assertFalse(service.isRunning());
assertTrue(service.doStopCalled);
service.notifyStopped();
assertEquals(State.TERMINATED, service.state());
assertFalse(service.isRunning());
assertEquals(
ImmutableList.of(State.STARTING, State.STOPPING, State.TERMINATED),
listener.getStateHistory());
}
/**
* This tests for a bug where if {@link Service#stopAsync()} was called while the service was
* {@link State#STARTING} more than once, the {@link Listener#stopping(State)} callback would get
* called multiple times.
*/
public void testManualServiceStopMultipleTimesWhileStarting() throws Exception {
ManualSwitchedService service = new ManualSwitchedService();
final AtomicInteger stoppingCount = new AtomicInteger();
service.addListener(
new Listener() {
@Override
public void stopping(State from) {
stoppingCount.incrementAndGet();
}
},
directExecutor());
service.startAsync();
service.stopAsync();
assertEquals(1, stoppingCount.get());
service.stopAsync();
assertEquals(1, stoppingCount.get());
}
public void testManualServiceStopWhileNew() throws Exception {
ManualSwitchedService service = new ManualSwitchedService();
RecordingListener listener = RecordingListener.record(service);
service.stopAsync();
assertEquals(State.TERMINATED, service.state());
assertFalse(service.isRunning());
assertFalse(service.doStartCalled);
assertFalse(service.doStopCalled);
assertEquals(ImmutableList.of(State.TERMINATED), listener.getStateHistory());
}
public void testManualServiceFailWhileStarting() throws Exception {
ManualSwitchedService service = new ManualSwitchedService();
RecordingListener listener = RecordingListener.record(service);
service.startAsync();
service.notifyFailed(EXCEPTION);
assertEquals(ImmutableList.of(State.STARTING, State.FAILED), listener.getStateHistory());
}
public void testManualServiceFailWhileRunning() throws Exception {
ManualSwitchedService service = new ManualSwitchedService();
RecordingListener listener = RecordingListener.record(service);
service.startAsync();
service.notifyStarted();
service.notifyFailed(EXCEPTION);
assertEquals(
ImmutableList.of(State.STARTING, State.RUNNING, State.FAILED), listener.getStateHistory());
}
public void testManualServiceFailWhileStopping() throws Exception {
ManualSwitchedService service = new ManualSwitchedService();
RecordingListener listener = RecordingListener.record(service);
service.startAsync();
service.notifyStarted();
service.stopAsync();
service.notifyFailed(EXCEPTION);
assertEquals(
ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.FAILED),
listener.getStateHistory());
}
public void testManualServiceUnrequestedStop() {
ManualSwitchedService service = new ManualSwitchedService();
service.startAsync();
service.notifyStarted();
assertEquals(State.RUNNING, service.state());
assertTrue(service.isRunning());
assertFalse(service.doStopCalled);
service.notifyStopped();
assertEquals(State.TERMINATED, service.state());
assertFalse(service.isRunning());
assertFalse(service.doStopCalled);
}
/**
* The user of this service should call {@link #notifyStarted} and {@link #notifyStopped} after
* calling {@link #startAsync} and {@link #stopAsync}.
*/
private static class ManualSwitchedService extends AbstractService {
boolean doStartCalled = false;
boolean doStopCalled = false;
@Override
protected void doStart() {
assertFalse(doStartCalled);
doStartCalled = true;
}
@Override
protected void doStop() {
assertFalse(doStopCalled);
doStopCalled = true;
}
}
public void testAwaitTerminated() throws Exception {
final NoOpService service = new NoOpService();
Thread waiter =
new Thread() {
@Override
public void run() {
service.awaitTerminated();
}
};
waiter.start();
service.startAsync().awaitRunning();
assertEquals(State.RUNNING, service.state());
service.stopAsync();
waiter.join(LONG_TIMEOUT_MILLIS); // ensure that the await in the other thread is triggered
assertFalse(waiter.isAlive());
}
public void testAwaitTerminated_FailedService() throws Exception {
final ManualSwitchedService service = new ManualSwitchedService();
final AtomicReference<Throwable> exception = Atomics.newReference();
Thread waiter =
new Thread() {
@Override
public void run() {
try {
service.awaitTerminated();
fail("Expected an IllegalStateException");
} catch (Throwable t) {
exception.set(t);
}
}
};
waiter.start();
service.startAsync();
service.notifyStarted();
assertEquals(State.RUNNING, service.state());
service.notifyFailed(EXCEPTION);
assertEquals(State.FAILED, service.state());
waiter.join(LONG_TIMEOUT_MILLIS);
assertFalse(waiter.isAlive());
assertThat(exception.get()).isInstanceOf(IllegalStateException.class);
assertThat(exception.get()).hasCauseThat().isEqualTo(EXCEPTION);
}
public void testThreadedServiceStartAndWaitStopAndWait() throws Throwable {
ThreadedService service = new ThreadedService();
RecordingListener listener = RecordingListener.record(service);
service.startAsync().awaitRunning();
assertEquals(State.RUNNING, service.state());
service.awaitRunChecks();
service.stopAsync().awaitTerminated();
assertEquals(State.TERMINATED, service.state());
throwIfSet(thrownByExecutionThread);
assertEquals(
ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.TERMINATED),
listener.getStateHistory());
}
public void testThreadedServiceStopIdempotence() throws Throwable {
ThreadedService service = new ThreadedService();
service.startAsync().awaitRunning();
assertEquals(State.RUNNING, service.state());
service.awaitRunChecks();
service.stopAsync();
service.stopAsync().awaitTerminated();
assertEquals(State.TERMINATED, service.state());
throwIfSet(thrownByExecutionThread);
}
public void testThreadedServiceStopIdempotenceAfterWait() throws Throwable {
ThreadedService service = new ThreadedService();
service.startAsync().awaitRunning();
assertEquals(State.RUNNING, service.state());
service.awaitRunChecks();
service.stopAsync().awaitTerminated();
service.stopAsync();
assertEquals(State.TERMINATED, service.state());
executionThread.join();
throwIfSet(thrownByExecutionThread);
}
public void testThreadedServiceStopIdempotenceDoubleWait() throws Throwable {
ThreadedService service = new ThreadedService();
service.startAsync().awaitRunning();
assertEquals(State.RUNNING, service.state());
service.awaitRunChecks();
service.stopAsync().awaitTerminated();
service.stopAsync().awaitTerminated();
assertEquals(State.TERMINATED, service.state());
throwIfSet(thrownByExecutionThread);
}
public void testManualServiceFailureIdempotence() {
ManualSwitchedService service = new ManualSwitchedService();
/*
* Set up a RecordingListener to perform its built-in assertions, even though we won't look at
* its state history.
*/
RecordingListener unused = RecordingListener.record(service);
service.startAsync();
service.notifyFailed(new Exception("1"));
service.notifyFailed(new Exception("2"));
assertThat(service.failureCause()).hasMessageThat().isEqualTo("1");
try {
service.awaitRunning();
fail();
} catch (IllegalStateException e) {
assertThat(e).hasCauseThat().hasMessageThat().isEqualTo("1");
}
}
private class ThreadedService extends AbstractService {
final CountDownLatch hasConfirmedIsRunning = new CountDownLatch(1);
/*
* The main test thread tries to stop() the service shortly after
* confirming that it is running. Meanwhile, the service itself is trying
* to confirm that it is running. If the main thread's stop() call happens
* before it has the chance, the test will fail. To avoid this, the main
* thread calls this method, which waits until the service has performed
* its own "running" check.
*/
void awaitRunChecks() throws InterruptedException {
assertTrue(
"Service thread hasn't finished its checks. "
+ "Exception status (possibly stale): "
+ thrownByExecutionThread,
hasConfirmedIsRunning.await(10, SECONDS));
}
@Override
protected void doStart() {
assertEquals(State.STARTING, state());
invokeOnExecutionThreadForTest(
new Runnable() {
@Override
public void run() {
assertEquals(State.STARTING, state());
notifyStarted();
assertEquals(State.RUNNING, state());
hasConfirmedIsRunning.countDown();
}
});
}
@Override
protected void doStop() {
assertEquals(State.STOPPING, state());
invokeOnExecutionThreadForTest(
new Runnable() {
@Override
public void run() {
assertEquals(State.STOPPING, state());
notifyStopped();
assertEquals(State.TERMINATED, state());
}
});
}
}
private void invokeOnExecutionThreadForTest(Runnable runnable) {
executionThread = new Thread(runnable);
executionThread.setUncaughtExceptionHandler(
new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable e) {
thrownByExecutionThread = e;
}
});
executionThread.start();
}
private static void throwIfSet(Throwable t) throws Throwable {
if (t != null) {
throw t;
}
}
public void testStopUnstartedService() throws Exception {
NoOpService service = new NoOpService();
RecordingListener listener = RecordingListener.record(service);
service.stopAsync();
assertEquals(State.TERMINATED, service.state());
try {
service.startAsync();
fail();
} catch (IllegalStateException expected) {
}
assertEquals(State.TERMINATED, Iterables.getOnlyElement(listener.getStateHistory()));
}
public void testFailingServiceStartAndWait() throws Exception {
StartFailingService service = new StartFailingService();
RecordingListener listener = RecordingListener.record(service);
try {
service.startAsync().awaitRunning();
fail();
} catch (IllegalStateException e) {
assertEquals(EXCEPTION, service.failureCause());
assertThat(e).hasCauseThat().isEqualTo(EXCEPTION);
}
assertEquals(ImmutableList.of(State.STARTING, State.FAILED), listener.getStateHistory());
}
public void testFailingServiceStopAndWait_stopFailing() throws Exception {
StopFailingService service = new StopFailingService();
RecordingListener listener = RecordingListener.record(service);
service.startAsync().awaitRunning();
try {
service.stopAsync().awaitTerminated();
fail();
} catch (IllegalStateException e) {
assertEquals(EXCEPTION, service.failureCause());
assertThat(e).hasCauseThat().isEqualTo(EXCEPTION);
}
assertEquals(
ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.FAILED),
listener.getStateHistory());
}
public void testFailingServiceStopAndWait_runFailing() throws Exception {
RunFailingService service = new RunFailingService();
RecordingListener listener = RecordingListener.record(service);
service.startAsync();
try {
service.awaitRunning();
fail();
} catch (IllegalStateException e) {
assertEquals(EXCEPTION, service.failureCause());
assertThat(e).hasCauseThat().isEqualTo(EXCEPTION);
}
assertEquals(
ImmutableList.of(State.STARTING, State.RUNNING, State.FAILED), listener.getStateHistory());
}
public void testThrowingServiceStartAndWait() throws Exception {
StartThrowingService service = new StartThrowingService();
RecordingListener listener = RecordingListener.record(service);
try {
service.startAsync().awaitRunning();
fail();
} catch (IllegalStateException e) {
assertEquals(service.exception, service.failureCause());
assertThat(e).hasCauseThat().isEqualTo(service.exception);
}
assertEquals(ImmutableList.of(State.STARTING, State.FAILED), listener.getStateHistory());
}
public void testThrowingServiceStopAndWait_stopThrowing() throws Exception {
StopThrowingService service = new StopThrowingService();
RecordingListener listener = RecordingListener.record(service);
service.startAsync().awaitRunning();
try {
service.stopAsync().awaitTerminated();
fail();
} catch (IllegalStateException e) {
assertEquals(service.exception, service.failureCause());
assertThat(e).hasCauseThat().isEqualTo(service.exception);
}
assertEquals(
ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.FAILED),
listener.getStateHistory());
}
public void testThrowingServiceStopAndWait_runThrowing() throws Exception {
RunThrowingService service = new RunThrowingService();
RecordingListener listener = RecordingListener.record(service);
service.startAsync();
try {
service.awaitTerminated();
fail();
} catch (IllegalStateException e) {
assertEquals(service.exception, service.failureCause());
assertThat(e).hasCauseThat().isEqualTo(service.exception);
}
assertEquals(
ImmutableList.of(State.STARTING, State.RUNNING, State.FAILED), listener.getStateHistory());
}
public void testFailureCause_throwsIfNotFailed() {
StopFailingService service = new StopFailingService();
try {
service.failureCause();
fail();
} catch (IllegalStateException expected) {
}
service.startAsync().awaitRunning();
try {
service.failureCause();
fail();
} catch (IllegalStateException expected) {
}
try {
service.stopAsync().awaitTerminated();
fail();
} catch (IllegalStateException e) {
assertEquals(EXCEPTION, service.failureCause());
assertThat(e).hasCauseThat().isEqualTo(EXCEPTION);
}
}
public void testAddListenerAfterFailureDoesntCauseDeadlock() throws InterruptedException {
final StartFailingService service = new StartFailingService();
service.startAsync();
assertEquals(State.FAILED, service.state());
service.addListener(new RecordingListener(service), directExecutor());
Thread thread =
new Thread() {
@Override
public void run() {
// Internally stopAsync() grabs a lock, this could be any such method on
// AbstractService.
service.stopAsync();
}
};
thread.start();
thread.join(LONG_TIMEOUT_MILLIS);
assertFalse(thread + " is deadlocked", thread.isAlive());
}
public void testListenerDoesntDeadlockOnStartAndWaitFromRunning() throws Exception {
final NoOpThreadedService service = new NoOpThreadedService();
service.addListener(
new Listener() {
@Override
public void running() {
service.awaitRunning();
}
},
directExecutor());
service.startAsync().awaitRunning(LONG_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
service.stopAsync();
}
public void testListenerDoesntDeadlockOnStopAndWaitFromTerminated() throws Exception {
final NoOpThreadedService service = new NoOpThreadedService();
service.addListener(
new Listener() {
@Override
public void terminated(State from) {
service.stopAsync().awaitTerminated();
}
},
directExecutor());
service.startAsync().awaitRunning();
Thread thread =
new Thread() {
@Override
public void run() {
service.stopAsync().awaitTerminated();
}
};
thread.start();
thread.join(LONG_TIMEOUT_MILLIS);
assertFalse(thread + " is deadlocked", thread.isAlive());
}
private static class NoOpThreadedService extends AbstractExecutionThreadService {
final CountDownLatch latch = new CountDownLatch(1);
@Override
protected void run() throws Exception {
latch.await();
}
@Override
protected void triggerShutdown() {
latch.countDown();
}
}
private static class StartFailingService extends AbstractService {
@Override
protected void doStart() {
notifyFailed(EXCEPTION);
}
@Override
protected void doStop() {
fail();
}
}
private static class RunFailingService extends AbstractService {
@Override
protected void doStart() {
notifyStarted();
notifyFailed(EXCEPTION);
}
@Override
protected void doStop() {
fail();
}
}
private static class StopFailingService extends AbstractService {
@Override
protected void doStart() {
notifyStarted();
}
@Override
protected void doStop() {
notifyFailed(EXCEPTION);
}
}
private static class StartThrowingService extends AbstractService {
final RuntimeException exception = new RuntimeException("deliberate");
@Override
protected void doStart() {
throw exception;
}
@Override
protected void doStop() {
fail();
}
}
private static class RunThrowingService extends AbstractService {
final RuntimeException exception = new RuntimeException("deliberate");
@Override
protected void doStart() {
notifyStarted();
throw exception;
}
@Override
protected void doStop() {
fail();
}
}
private static class StopThrowingService extends AbstractService {
final RuntimeException exception = new RuntimeException("deliberate");
@Override
protected void doStart() {
notifyStarted();
}
@Override
protected void doStop() {
throw exception;
}
}
private static class RecordingListener extends Listener {
static RecordingListener record(Service service) {
RecordingListener listener = new RecordingListener(service);
service.addListener(listener, directExecutor());
return listener;
}
final Service service;
RecordingListener(Service service) {
this.service = service;
}
@GuardedBy("this")
final List<State> stateHistory = Lists.newArrayList();
final CountDownLatch completionLatch = new CountDownLatch(1);
ImmutableList<State> getStateHistory() throws Exception {
completionLatch.await();
synchronized (this) {
return ImmutableList.copyOf(stateHistory);
}
}
@Override
public synchronized void starting() {
assertTrue(stateHistory.isEmpty());
assertNotSame(State.NEW, service.state());
stateHistory.add(State.STARTING);
}
@Override
public synchronized void running() {
assertEquals(State.STARTING, Iterables.getOnlyElement(stateHistory));
stateHistory.add(State.RUNNING);
service.awaitRunning();
assertNotSame(State.STARTING, service.state());
}
@Override
public synchronized void stopping(State from) {
assertEquals(from, Iterables.getLast(stateHistory));
stateHistory.add(State.STOPPING);
if (from == State.STARTING) {
try {
service.awaitRunning();
fail();
} catch (IllegalStateException expected) {
assertThat(expected).hasCauseThat().isNull();
assertThat(expected)
.hasMessageThat()
.isEqualTo("Expected the service " + service + " to be RUNNING, but was STOPPING");
}
}
assertNotSame(from, service.state());
}
@Override
public synchronized void terminated(State from) {
assertEquals(from, Iterables.getLast(stateHistory, State.NEW));
stateHistory.add(State.TERMINATED);
assertEquals(State.TERMINATED, service.state());
if (from == State.NEW) {
try {
service.awaitRunning();
fail();
} catch (IllegalStateException expected) {
assertThat(expected).hasCauseThat().isNull();
assertThat(expected)
.hasMessageThat()
.isEqualTo("Expected the service " + service + " to be RUNNING, but was TERMINATED");
}
}
completionLatch.countDown();
}
@Override
public synchronized void failed(State from, Throwable failure) {
assertEquals(from, Iterables.getLast(stateHistory));
stateHistory.add(State.FAILED);
assertEquals(State.FAILED, service.state());
assertEquals(failure, service.failureCause());
if (from == State.STARTING) {
try {
service.awaitRunning();
fail();
} catch (IllegalStateException e) {
assertThat(e).hasCauseThat().isEqualTo(failure);
}
}
try {
service.awaitTerminated();
fail();
} catch (IllegalStateException e) {
assertThat(e).hasCauseThat().isEqualTo(failure);
}
completionLatch.countDown();
}
}
public void testNotifyStartedWhenNotStarting() {
AbstractService service = new DefaultService();
try {
service.notifyStarted();
fail();
} catch (IllegalStateException expected) {
}
}
public void testNotifyStoppedWhenNotRunning() {
AbstractService service = new DefaultService();
try {
service.notifyStopped();
fail();
} catch (IllegalStateException expected) {
}
}
public void testNotifyFailedWhenNotStarted() {
AbstractService service = new DefaultService();
try {
service.notifyFailed(new Exception());
fail();
} catch (IllegalStateException expected) {
}
}
public void testNotifyFailedWhenTerminated() {
NoOpService service = new NoOpService();
service.startAsync().awaitRunning();
service.stopAsync().awaitTerminated();
try {
service.notifyFailed(new Exception());
fail();
} catch (IllegalStateException expected) {
}
}
private static class DefaultService extends AbstractService {
@Override
protected void doStart() {}
@Override
protected void doStop() {}
}
private static final Exception EXCEPTION = new Exception();
}