/* * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * Contributors: * Florent Guillaume */ package org.nuxeo.ecm.core.work; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import static org.nuxeo.ecm.core.work.api.Work.State.CANCELED; import static org.nuxeo.ecm.core.work.api.Work.State.COMPLETED; import static org.nuxeo.ecm.core.work.api.Work.State.RUNNING; import static org.nuxeo.ecm.core.work.api.Work.State.SCHEDULED; import java.io.File; import java.io.Serializable; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.nuxeo.ecm.core.work.api.WorkManager; import org.nuxeo.ecm.core.work.api.WorkManager.Scheduling; import org.nuxeo.ecm.core.work.api.WorkQueueDescriptor; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.test.NXRuntimeTestCase; import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.FeaturesRunner; import org.nuxeo.runtime.test.runner.FileEventsTrackingFeature; import org.nuxeo.runtime.trackers.files.FileEvent; @Features(FileEventsTrackingFeature.class) public class WorkManagerTest extends NXRuntimeTestCase { protected static class CreateFile extends AbstractWork implements Serializable { private final File file; private static final long serialVersionUID = 1L; protected CreateFile(File file) { this.file = file; } @Override public String getTitle() { return "pfouh"; } @Override public void work() throws Exception { FileEvent.onFile(this, file, this).send(); } } protected static final String CATEGORY = "SleepWork"; protected static final String QUEUE = "SleepWork"; protected WorkManager service; protected boolean dontClearCompletedWork; private static void assertSetEquals(List<String> expected, List<String> actual) { assertEquals(new HashSet<String>(expected), new HashSet<String>(actual)); } @Override @Before public void setUp() throws Exception { super.setUp(); doDeploy(); fireFrameworkStarted(); service = Framework.getLocalService(WorkManager.class); } protected void doDeploy() throws Exception { deployBundle("org.nuxeo.ecm.core.event"); deployContrib("org.nuxeo.ecm.core.event.test", "test-workmanager-config.xml"); } @Override @After public void tearDown() throws Exception { if (service != null && !dontClearCompletedWork) { service.clearCompletedWork(0); } super.tearDown(); } // overridden for persistence public boolean persistent() { return false; // in-memory, no persistence } @Test public void testBasics() { assertNotNull(service); service.clearCompletedWork(0); assertEquals(0, service.getQueueSize(QUEUE, COMPLETED)); assertEquals(0, service.getQueueSize(QUEUE, RUNNING)); assertEquals(0, service.getQueueSize(QUEUE, SCHEDULED)); } @Test public void testWorkManagerConfig() throws Exception { SleepWork work = new SleepWork(1); assertEquals(CATEGORY, work.getCategory()); assertEquals(QUEUE, service.getCategoryQueueId(CATEGORY)); WorkQueueDescriptor qd = service.getWorkQueueDescriptor(QUEUE); assertEquals("SleepWork", qd.id); assertEquals("Sleep Work Queue", qd.name); assertEquals(2, qd.maxThreads); assertFalse(qd.usePriority); assertEquals(1234, qd.clearCompletedAfterSeconds); assertEquals(Collections.singleton("SleepWork"), qd.categories); } @Test public void testWorkManagerWork() throws Exception { int duration = 3000; // ms SleepWork work = new SleepWork(duration, false); service.schedule(work); Thread.sleep(duration / 3); assertEquals(RUNNING, service.getWorkState(work.getId())); assertEquals(0, service.getQueueSize(QUEUE, COMPLETED)); assertEquals(1, service.getQueueSize(QUEUE, RUNNING)); assertEquals(0, service.getQueueSize(QUEUE, SCHEDULED)); Thread.sleep(duration); assertEquals(1, service.getQueueSize(QUEUE, COMPLETED)); assertEquals(0, service.getQueueSize(QUEUE, RUNNING)); assertEquals(0, service.getQueueSize(QUEUE, SCHEDULED)); assertEquals(COMPLETED, service.getWorkState(work.getId())); assertTrue(work.getSchedulingTime() != 0); // assertTrue(work.getStartTime() != 0); // assertTrue(work.getCompletionTime() != 0); // assertTrue(work.getCompletionTime() - work.getStartTime() > 0); } @Test public void testWorkManagerScheduling() throws Exception { assertEquals(Collections.emptyList(), service.listWorkIds(QUEUE, COMPLETED)); int duration = 5000; // 2s SleepWork work1 = new SleepWork(duration, false, "1"); SleepWork work2 = new SleepWork(duration, false, "2"); SleepWork work3 = new SleepWork(duration, false, "3"); service.schedule(work1); service.schedule(work2); service.schedule(work3); Thread.sleep(duration / 2); assertEquals(RUNNING, service.getWorkState("1")); assertEquals(RUNNING, service.getWorkState("2")); assertEquals(SCHEDULED, service.getWorkState("3")); assertEquals(Arrays.asList("3"), service.listWorkIds(QUEUE, SCHEDULED)); assertSetEquals(Arrays.asList("1", "2"), service.listWorkIds(QUEUE, RUNNING)); assertSetEquals(Arrays.asList("1", "2", "3"), service.listWorkIds(QUEUE, null)); assertEquals(Collections.emptyList(), service.listWorkIds(QUEUE, COMPLETED)); // disabled IF_NOT_* features if (Boolean.FALSE.booleanValue()) { SleepWork work4 = new SleepWork(duration, false, "3"); // id=3 service.schedule(work4, Scheduling.IF_NOT_SCHEDULED); assertEquals(CANCELED, work4.getWorkInstanceState()); SleepWork work5 = new SleepWork(duration, false, "1"); // id=1 service.schedule(work5, Scheduling.IF_NOT_RUNNING); assertEquals(CANCELED, work5.getWorkInstanceState()); SleepWork work6 = new SleepWork(duration, false, "1"); // id=1 service.schedule(work6, Scheduling.IF_NOT_RUNNING_OR_SCHEDULED); assertEquals(CANCELED, work6.getWorkInstanceState()); } SleepWork work7 = new SleepWork(duration, false, "3"); // id=3 service.schedule(work7, Scheduling.CANCEL_SCHEDULED); assertEquals(SCHEDULED, work7.getWorkInstanceState()); boolean completed = service.awaitCompletion(duration * 2, TimeUnit.MILLISECONDS); assertTrue(completed); assertEquals(COMPLETED, service.getWorkState("1")); assertEquals(COMPLETED, service.getWorkState("2")); assertEquals(COMPLETED, service.getWorkState("3")); assertEquals(Collections.emptyList(), service.listWorkIds(QUEUE, SCHEDULED)); assertEquals(Collections.emptyList(), service.listWorkIds(QUEUE, RUNNING)); assertEquals(Collections.emptyList(), service.listWorkIds(QUEUE, null)); assertSetEquals(Arrays.asList("1", "2", "3"), service.listWorkIds(QUEUE, COMPLETED)); } @Test @Ignore public void testWorkManagerShutdown() throws Exception { int duration = 2000; // 2s SleepWork work1 = new SleepWork(duration, false, "1"); SleepWork work2 = new SleepWork(duration, false, "2"); SleepWork work3 = new SleepWork(duration, false, "3"); service.schedule(work1); service.schedule(work2); service.schedule(work3); Thread.sleep(duration / 2); assertEquals(RUNNING, service.getWorkState("1")); assertEquals(RUNNING, service.getWorkState("2")); assertEquals(SCHEDULED, service.getWorkState("3")); // shutdown workmanager service // work1 and work2 get a suspended notice and stop // work3 then gets scheduled immediately // and is either discarded (memory) // or put in the suspended queue (persistent) dontClearCompletedWork = true; boolean terminated = service.shutdown(duration * 2, TimeUnit.MILLISECONDS); assertTrue(terminated); // check work state assertEquals(SCHEDULED, work1.getWorkInstanceState()); assertEquals(SCHEDULED, work2.getWorkInstanceState()); assertEquals(persistent() ? SCHEDULED : CANCELED, work3.getWorkInstanceState()); long remaining1 = work1.durationMillis; long remaining2 = work2.durationMillis; long remaining3 = work3.durationMillis; assertTrue("remaining1 " + remaining1, remaining1 < duration); assertTrue("remaining2 " + remaining2, remaining2 < duration); assertEquals(duration, remaining3); } @Test public void testWorkManagerDisableProcessing() throws Exception { assumeTrue(persistent()); // disable SleepWork queue deployContrib("org.nuxeo.ecm.core.event.test", "test-workmanager-disablequeue.xml"); int duration = 2000; // 2s SleepWork work1 = new SleepWork(duration, false); service.schedule(work1); Thread.sleep(duration / 2); // stays scheduled assertEquals(1, service.getQueueSize(QUEUE, SCHEDULED)); assertEquals(0, service.getQueueSize(QUEUE, RUNNING)); assertEquals(0, service.getQueueSize(QUEUE, COMPLETED)); Thread.sleep(2 * duration); // still scheduled assertEquals(1, service.getQueueSize(QUEUE, SCHEDULED)); // now reactivate the queue // use a programmatic work queue descriptor WorkQueueDescriptor descr = new WorkQueueDescriptor(); descr.id = "SleepWork"; descr.processing = Boolean.TRUE; descr.categories = Collections.emptySet(); ((WorkManagerImpl) service).activateQueue(descr); Thread.sleep(duration / 2); assertEquals(0, service.getQueueSize(QUEUE, SCHEDULED)); assertEquals(1, service.getQueueSize(QUEUE, RUNNING)); assertEquals(0, service.getQueueSize(QUEUE, COMPLETED)); Thread.sleep(duration); assertEquals(0, service.getQueueSize(QUEUE, SCHEDULED)); assertEquals(0, service.getQueueSize(QUEUE, RUNNING)); assertEquals(1, service.getQueueSize(QUEUE, COMPLETED)); } @Test public void testWorkManagerDisableProcessing2() throws Exception { assumeTrue(persistent()); // disable all queues deployContrib("org.nuxeo.ecm.core.event.test", "test-workmanager-disablequeue2.xml"); int duration = 2000; // 2s SleepWork work1 = new SleepWork(duration, false); service.schedule(work1); Thread.sleep(duration / 2); // stays scheduled assertEquals(1, service.getQueueSize(QUEUE, SCHEDULED)); assertEquals(0, service.getQueueSize(QUEUE, RUNNING)); assertEquals(0, service.getQueueSize(QUEUE, COMPLETED)); // check that we can reenable the queue Thread.sleep(2 * duration); // still scheduled assertEquals(1, service.getQueueSize(QUEUE, SCHEDULED)); // now reactivate the queue // use a programmatic work queue descriptor WorkQueueDescriptor descr = new WorkQueueDescriptor(); descr.id = "SleepWork"; descr.processing = Boolean.TRUE; descr.categories = Collections.emptySet(); ((WorkManagerImpl) service).activateQueue(descr); Thread.sleep(duration / 2); assertEquals(0, service.getQueueSize(QUEUE, SCHEDULED)); assertEquals(1, service.getQueueSize(QUEUE, RUNNING)); assertEquals(0, service.getQueueSize(QUEUE, COMPLETED)); Thread.sleep(duration); assertEquals(0, service.getQueueSize(QUEUE, SCHEDULED)); assertEquals(0, service.getQueueSize(QUEUE, RUNNING)); assertEquals(1, service.getQueueSize(QUEUE, COMPLETED)); } @Inject public FeaturesRunner runner; protected FileEventsTrackingFeature feature; @Before public void injectFeature() { feature = runner.getFeature(FileEventsTrackingFeature.class); } @Test public void transientFilesWorkAreCleaned() throws Exception { final File file = feature.resolveAndCreate(new File("pfouh")); service.schedule(new CreateFile(file)); service.awaitCompletion(5, TimeUnit.SECONDS); } }