/* * Copyright © 2014 Cask Data, Inc. * * 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 co.cask.cdap.app.runtime; import co.cask.cdap.common.app.RunIds; import co.cask.cdap.internal.app.runtime.AbstractListener; import co.cask.cdap.internal.app.runtime.ProgramControllerServiceAdapter; import co.cask.cdap.proto.Id; import co.cask.cdap.proto.ProgramType; import com.google.common.util.concurrent.AbstractIdleService; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.Service; import org.junit.Assert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; /** * Unit test for {@link ProgramController}. */ public class ProgramControllerTest { private static final Logger LOG = LoggerFactory.getLogger(ProgramControllerTest.class); @Test public void testInitState() throws ExecutionException, InterruptedException { // To test against race-condition, we start/stop service multiple times. // If there is race, there is a chance that this test will fail in some env. // Otherwise it should always pass ExecutorService executor = Executors.newCachedThreadPool(); int serviceCount = 1000; final CountDownLatch latch = new CountDownLatch(serviceCount); Id.Program programId = Id.Program.from(Id.Namespace.DEFAULT, "test", ProgramType.SERVICE, "test"); for (int i = 0; i < serviceCount; i++) { // Creates a controller for a guava service do nothing in start/stop. // The short time in start creates a chance to have out-of-order init() and alive() call if there is a race. Service service = new TestService(0, 0); ProgramController controller = new ProgramControllerServiceAdapter(service, programId, RunIds.generate()); ListenableFuture<Service.State> startCompletion = service.start(); controller.addListener(new AbstractListener() { private volatile boolean initCalled; @Override public void init(ProgramController.State currentState, @Nullable Throwable cause) { initCalled = true; if (currentState == ProgramController.State.ALIVE) { latch.countDown(); } } @Override public void alive() { if (initCalled) { latch.countDown(); } else { LOG.error("init() not called before alive()"); } } }, executor); startCompletion.get(); service.stopAndWait(); } Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); } private static final class TestService extends AbstractIdleService { private final long startDelay; private final long stopDelay; private TestService(long startDelay, long stopDelay) { this.startDelay = startDelay; this.stopDelay = stopDelay; } @Override protected void startUp() throws Exception { TimeUnit.MILLISECONDS.sleep(startDelay); } @Override protected void shutDown() throws Exception { TimeUnit.MILLISECONDS.sleep(stopDelay); } } }