/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.brooklyn.util.core.task;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.task.BasicExecutionManager;
import org.apache.brooklyn.util.core.task.BasicTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Callables;
/**
* Test the operation of the {@link BasicTask} class.
*
* TODO clarify test purpose
*/
public class BasicTaskExecutionTest {
private static final Logger log = LoggerFactory.getLogger(BasicTaskExecutionTest.class);
private static final int TIMEOUT_MS = 10*1000;
private BasicExecutionManager em;
private Map<Object, Object> data;
@BeforeMethod(alwaysRun=true)
public void setUp() {
em = new BasicExecutionManager("mycontext");
data = Collections.synchronizedMap(new HashMap<Object, Object>());
data.clear();
}
@AfterMethod(alwaysRun=true)
public void tearDown() throws Exception {
if (em != null) em.shutdownNow();
if (data != null) data.clear();
}
@Test
public void runSimpleBasicTask() throws Exception {
BasicTask<Object> t = new BasicTask<Object>(newPutCallable(1, "b"));
data.put(1, "a");
Task<Object> t2 = em.submit(MutableMap.of("tag", "A"), t);
assertEquals("a", t.get());
assertEquals("a", t2.get());
assertEquals("b", data.get(1));
}
@Test
public void runSimpleRunnable() throws Exception {
data.put(1, "a");
Task<?> t = em.submit(MutableMap.of("tag", "A"), newPutRunnable(1, "b"));
assertEquals(null, t.get());
assertEquals("b", data.get(1));
}
@Test
public void runSimpleCallable() throws Exception {
data.put(1, "a");
Task<?> t = em.submit(MutableMap.of("tag", "A"), newPutCallable(1, "b"));
assertEquals("a", t.get());
assertEquals("b", data.get(1));
}
@Test
public void runBasicTaskWithWaits() throws Exception {
final CountDownLatch signalStarted = new CountDownLatch(1);
final CountDownLatch allowCompletion = new CountDownLatch(1);
final BasicTask<Object> t = new BasicTask<Object>(new Callable<Object>() {
public Object call() throws Exception {
Object result = data.put(1, "b");
signalStarted.countDown();
assertTrue(allowCompletion.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
return result;
}});
data.put(1, "a");
Task<?> t2 = em.submit(MutableMap.of("tag", "A"), t);
assertEquals(t, t2);
assertFalse(t.isDone());
assertTrue(signalStarted.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertEquals("b", data.get(1));
assertFalse(t.isDone());
log.debug("runBasicTaskWithWaits, BasicTask status: {}", t.getStatusDetail(false));
Asserts.succeedsEventually(new Runnable() {
public void run() {
String status = t.getStatusDetail(false);
assertTrue(status != null && status.toLowerCase().contains("waiting"), "status="+status);
}});
allowCompletion.countDown();
assertEquals("a", t.get());
}
@Test
public void runMultipleBasicTasks() throws Exception {
data.put(1, 1);
BasicExecutionManager em = new BasicExecutionManager("mycontext");
for (int i = 0; i < 2; i++) {
em.submit(MutableMap.of("tag", "A"), new BasicTask<Integer>(newIncrementCallable(1)));
em.submit(MutableMap.of("tag", "B"), new BasicTask<Integer>(newIncrementCallable((1))));
}
int total = 0;
for (Object tag : em.getTaskTags()) {
log.debug("tag {}", tag);
for (Task<?> task : em.getTasksWithTag(tag)) {
log.debug("BasicTask {}, has {}", task, task.get());
total += (Integer)task.get();
}
}
assertEquals(10, total);
//now that all have completed:
assertEquals(5, data.get(1));
}
@Test
public void runMultipleBasicTasksMultipleTags() throws Exception {
data.put(1, 1);
Collection<Task<Integer>> tasks = Lists.newArrayList();
tasks.add(em.submit(MutableMap.of("tag", "A"), new BasicTask<Integer>(newIncrementCallable(1))));
tasks.add(em.submit(MutableMap.of("tags", ImmutableList.of("A","B")), new BasicTask<Integer>(newIncrementCallable(1))));
tasks.add(em.submit(MutableMap.of("tags", ImmutableList.of("B","C")), new BasicTask<Integer>(newIncrementCallable(1))));
tasks.add(em.submit(MutableMap.of("tags", ImmutableList.of("D")), new BasicTask<Integer>(newIncrementCallable(1))));
int total = 0;
for (Task<Integer> t : tasks) {
log.debug("BasicTask {}, has {}", t, t.get());
total += t.get();
}
assertEquals(10, total);
//now that all have completed:
assertEquals(data.get(1), 5);
assertEquals(em.getTasksWithTag("A").size(), 2);
assertEquals(em.getTasksWithAnyTag(ImmutableList.of("A")).size(), 2);
assertEquals(em.getTasksWithAllTags(ImmutableList.of("A")).size(), 2);
assertEquals(em.getTasksWithAnyTag(ImmutableList.of("A", "B")).size(), 3);
assertEquals(em.getTasksWithAllTags(ImmutableList.of("A", "B")).size(), 1);
assertEquals(em.getTasksWithAllTags(ImmutableList.of("B", "C")).size(), 1);
assertEquals(em.getTasksWithAnyTag(ImmutableList.of("A", "D")).size(), 3);
}
@Test
public void testGetTaskById() throws Exception {
Task<?> t = new BasicTask<Void>(newNoop());
em.submit(MutableMap.of("tag", "A"), t);
assertEquals(em.getTask(t.getId()), t);
}
@Test
public void testRetrievingTasksWithTagsReturnsExpectedTask() throws Exception {
Task<?> t = new BasicTask<Void>(newNoop());
em.submit(MutableMap.of("tag", "A"), t);
t.get();
assertEquals(em.getTasksWithTag("A"), ImmutableList.of(t));
assertEquals(em.getTasksWithAnyTag(ImmutableList.of("A")), ImmutableList.of(t));
assertEquals(em.getTasksWithAnyTag(ImmutableList.of("A", "B")), ImmutableList.of(t));
assertEquals(em.getTasksWithAllTags(ImmutableList.of("A")), ImmutableList.of(t));
}
@Test
public void testRetrievingTasksWithTagsExcludesNonMatchingTasks() throws Exception {
Task<?> t = new BasicTask<Void>(newNoop());
em.submit(MutableMap.of("tag", "A"), t);
t.get();
assertEquals(em.getTasksWithTag("B"), ImmutableSet.of());
assertEquals(em.getTasksWithAnyTag(ImmutableList.of("B")), ImmutableSet.of());
assertEquals(em.getTasksWithAllTags(ImmutableList.of("A", "B")), ImmutableSet.of());
}
@Test
public void testRetrievingTasksWithMultipleTags() throws Exception {
Task<?> t = new BasicTask<Void>(newNoop());
em.submit(MutableMap.of("tags", ImmutableList.of("A", "B")), t);
t.get();
assertEquals(em.getTasksWithTag("A"), ImmutableList.of(t));
assertEquals(em.getTasksWithTag("B"), ImmutableList.of(t));
assertEquals(em.getTasksWithAnyTag(ImmutableList.of("A")), ImmutableList.of(t));
assertEquals(em.getTasksWithAnyTag(ImmutableList.of("B")), ImmutableList.of(t));
assertEquals(em.getTasksWithAnyTag(ImmutableList.of("A", "B")), ImmutableList.of(t));
assertEquals(em.getTasksWithAllTags(ImmutableList.of("A", "B")), ImmutableList.of(t));
assertEquals(em.getTasksWithAllTags(ImmutableList.of("A")), ImmutableList.of(t));
assertEquals(em.getTasksWithAllTags(ImmutableList.of("B")), ImmutableList.of(t));
}
// ENGR-1796: if nothing matched first tag, then returned whatever matched second tag!
@Test
public void testRetrievingTasksWithAllTagsWhenFirstNotMatched() throws Exception {
Task<?> t = new BasicTask<Void>(newNoop());
em.submit(MutableMap.of("tags", ImmutableList.of("A")), t);
t.get();
assertEquals(em.getTasksWithAllTags(ImmutableList.of("not_there","A")), ImmutableSet.of());
}
@Test
public void testRetrievedTasksIncludesTasksInProgress() throws Exception {
final CountDownLatch runningLatch = new CountDownLatch(1);
final CountDownLatch finishLatch = new CountDownLatch(1);
Task<Void> t = new BasicTask<Void>(new Callable<Void>() {
public Void call() throws Exception {
runningLatch.countDown();
finishLatch.await();
return null;
}});
em.submit(MutableMap.of("tags", ImmutableList.of("A")), t);
try {
runningLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertEquals(em.getTasksWithTag("A"), ImmutableList.of(t));
} finally {
finishLatch.countDown();
}
}
@Test
public void cancelBeforeRun() throws Exception {
final CountDownLatch blockForever = new CountDownLatch(1);
BasicTask<Integer> t = new BasicTask<Integer>(new Callable<Integer>() {
public Integer call() throws Exception {
blockForever.await(); return 42;
}});
t.cancel(true);
assertTrue(t.isCancelled());
assertTrue(t.isDone());
assertTrue(t.isError());
em.submit(MutableMap.of("tag", "A"), t);
try {
t.get();
fail("get should have failed due to cancel");
} catch (CancellationException e) {
// expected
}
assertTrue(t.isCancelled());
assertTrue(t.isDone());
assertTrue(t.isError());
log.debug("cancelBeforeRun status: {}", t.getStatusDetail(false));
assertTrue(t.getStatusDetail(false).toLowerCase().contains("cancel"));
}
@Test
public void cancelDuringRun() throws Exception {
final CountDownLatch signalStarted = new CountDownLatch(1);
final CountDownLatch blockForever = new CountDownLatch(1);
BasicTask<Integer> t = new BasicTask<Integer>(new Callable<Integer>() {
public Integer call() throws Exception {
synchronized (data) {
signalStarted.countDown();
blockForever.await();
}
return 42;
}});
em.submit(MutableMap.of("tag", "A"), t);
assertFalse(t.isCancelled());
assertFalse(t.isDone());
assertFalse(t.isError());
assertTrue(signalStarted.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
t.cancel(true);
assertTrue(t.isCancelled());
assertTrue(t.isError());
try {
t.get();
fail("get should have failed due to cancel");
} catch (CancellationException e) {
// expected
}
assertTrue(t.isCancelled());
assertTrue(t.isDone());
assertTrue(t.isError());
}
@Test
public void cancelAfterRun() throws Exception {
BasicTask<Integer> t = new BasicTask<Integer>(Callables.returning(42));
em.submit(MutableMap.of("tag", "A"), t);
assertEquals(t.get(), (Integer)42);
t.cancel(true);
assertFalse(t.isCancelled());
assertFalse(t.isError());
assertTrue(t.isDone());
}
@Test
public void errorDuringRun() throws Exception {
BasicTask<Void> t = new BasicTask<Void>(new Callable<Void>() {
public Void call() throws Exception {
throw new IllegalStateException("Simulating failure in errorDuringRun");
}});
em.submit(MutableMap.of("tag", "A"), t);
try {
t.get();
fail("get should have failed due to error");
} catch (Exception eo) {
Throwable e = Throwables.getRootCause(eo);
assertEquals("Simulating failure in errorDuringRun", e.getMessage());
}
assertFalse(t.isCancelled());
assertTrue(t.isError());
assertTrue(t.isDone());
log.debug("errorDuringRun status: {}", t.getStatusDetail(false));
assertTrue(t.getStatusDetail(false).contains("Simulating failure in errorDuringRun"), "details="+t.getStatusDetail(false));
}
@Test
public void fieldsSetForSimpleBasicTask() throws Exception {
final CountDownLatch signalStarted = new CountDownLatch(1);
final CountDownLatch allowCompletion = new CountDownLatch(1);
BasicTask<Integer> t = new BasicTask<Integer>(new Callable<Integer>() {
public Integer call() throws Exception {
signalStarted.countDown();
allowCompletion.await();
return 42;
}});
assertEquals(null, t.getSubmittedByTask());
assertEquals(-1, t.submitTimeUtc);
assertNull(t.getInternalFuture());
em.submit(MutableMap.of("tag", "A"), t);
assertTrue(signalStarted.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertTrue(t.submitTimeUtc > 0);
assertTrue(t.startTimeUtc >= t.submitTimeUtc);
assertNotNull(t.getInternalFuture());
assertEquals(-1, t.endTimeUtc);
assertEquals(false, t.isCancelled());
allowCompletion.countDown();
assertEquals(t.get(), (Integer)42);
assertTrue(t.endTimeUtc >= t.startTimeUtc);
log.debug("BasicTask duration (millis): {}", (t.endTimeUtc - t.submitTimeUtc));
}
@Test
public void fieldsSetForBasicTaskSubmittedBasicTask() throws Exception {
//submitted BasicTask B is started by A, and waits for A to complete
BasicTask<Integer> t = new BasicTask<Integer>(MutableMap.of("displayName", "sample", "description", "some descr"), new Callable<Integer>() {
public Integer call() throws Exception {
em.submit(MutableMap.of("tag", "B"), new Callable<Integer>() {
public Integer call() throws Exception {
assertEquals(45, em.getTasksWithTag("A").iterator().next().get());
return 46;
}});
return 45;
}});
em.submit(MutableMap.of("tag", "A"), t);
t.blockUntilEnded();
// assertEquals(em.getAllTasks().size(), 2
BasicTask<?> tb = (BasicTask<?>) em.getTasksWithTag("B").iterator().next();
assertEquals( 46, tb.get() );
assertEquals( t, em.getTasksWithTag("A").iterator().next() );
assertNull( t.getSubmittedByTask() );
BasicTask<?> submitter = (BasicTask<?>) tb.getSubmittedByTask();
assertNotNull(submitter);
assertEquals("sample", submitter.displayName);
assertEquals("some descr", submitter.description);
assertEquals(t, submitter);
assertTrue(submitter.submitTimeUtc <= tb.submitTimeUtc);
assertTrue(submitter.endTimeUtc <= tb.endTimeUtc);
log.debug("BasicTask {} was submitted by {}", tb, submitter);
}
private Callable<Object> newPutCallable(final Object key, final Object val) {
return new Callable<Object>() {
public Object call() {
return data.put(key, val);
}
};
}
private Callable<Integer> newIncrementCallable(final Object key) {
return new Callable<Integer>() {
public Integer call() {
synchronized (data) {
return (Integer) data.put(key, (Integer)data.get(key) + 1);
}
}
};
}
private Runnable newPutRunnable(final Object key, final Object val) {
return new Runnable() {
public void run() {
data.put(key, val);
}
};
}
private Runnable newNoop() {
return new Runnable() {
public void run() {
}
};
}
}