package net.johnewart.gearman.engine; import com.codahale.metrics.MetricRegistry; import com.google.common.collect.ImmutableSet; import net.johnewart.gearman.common.Job; import net.johnewart.gearman.common.JobStatus; import net.johnewart.gearman.common.interfaces.EngineClient; import net.johnewart.gearman.common.interfaces.EngineWorker; import net.johnewart.gearman.common.interfaces.JobHandleFactory; import net.johnewart.gearman.engine.core.JobManager; import net.johnewart.gearman.engine.core.UniqueIdFactory; import net.johnewart.gearman.engine.factories.JobFactory; import net.johnewart.gearman.engine.factories.TestJobHandleFactory; import net.johnewart.gearman.engine.factories.TestUniqueIdFactory; import net.johnewart.gearman.engine.metrics.MetricsEngine; import net.johnewart.gearman.engine.queue.factories.MemoryJobQueueFactory; import net.johnewart.gearman.engine.storage.NoopExceptionStorageEngine; import org.hamcrest.core.Is; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class JobManagerTest { private JobManager jobManager; private EngineWorker worker; private JobHandleFactory jobHandleFactory; private UniqueIdFactory uniqueIdFactory; private MetricsEngine metricsEngine; public JobManagerTest() { } @Before public void initialize() { MetricRegistry metricRegistry = new MetricRegistry(); metricsEngine = new MetricsEngine(metricRegistry); jobHandleFactory = new TestJobHandleFactory(); uniqueIdFactory = new TestUniqueIdFactory(); jobManager = new JobManager(new MemoryJobQueueFactory(metricRegistry), jobHandleFactory, uniqueIdFactory, new NoopExceptionStorageEngine(), metricsEngine); final ImmutableSet<String> abilities = ImmutableSet.of("reverseString", "computeBigStuff"); worker = mock(EngineWorker.class); when(worker.getAbilities()).thenReturn(abilities); } @Test public void insertsJobsIntoJobStore() throws Exception { Job job = JobFactory.generateForegroundJob("reverseString"); jobManager.storeJob(job); Assert.assertThat("There are no completed jobs", metricsEngine.getCompletedJobCount(), Is.is(0L)); Assert.assertThat("There is one pending job", metricsEngine.getPendingJobsCount(), Is.is(1L)); Assert.assertThat("One job has been enqueued", metricsEngine.getEnqueuedJobCount(), Is.is(1L)); } @Test public void fetchesJobsFromStorage() throws Exception { Job job = JobFactory.generateForegroundJob("reverseString"); jobManager.storeJob(job); Job nextJob = jobManager.nextJobForWorker(worker); byte[] result = {'r','e','s','u','l','t'}; Assert.assertThat("The jobs are the same", job.equals(nextJob), Is.is(true)); Assert.assertThat("The job store is now empty", metricsEngine.getPendingJobsCount(), Is.is(0L)); Assert.assertThat("There is one active job", metricsEngine.getActiveJobCount(), Is.is(1L)); Assert.assertThat("The job store has no complete jobs", metricsEngine.getCompletedJobCount(), Is.is(0L)); // Complete the job jobManager.handleWorkCompletion(nextJob, result); Assert.assertThat("The job store has one complete job", metricsEngine.getCompletedJobCount(), Is.is(1L)); } @Test public void reQueuesBackgroundJobsWhenWorkersDisconnect() throws Exception { Job job = JobFactory.generateBackgroundJob("reverseString"); jobManager.storeJob(job); jobManager.nextJobForWorker(worker); Assert.assertThat("Job queue has no jobs", jobManager.getOrCreateJobQueue("reverseString").size(), Is.is(0L)); // Simulate abort before completion jobManager.unregisterWorker(worker); Assert.assertThat("Job queue has one job", jobManager.getOrCreateJobQueue("reverseString").size(), Is.is(1L)); // Re-fetch the job, make sure it's the same one Job nextJob = jobManager.nextJobForWorker(worker); Assert.assertThat("The job was returned to the queue", nextJob.equals(job), Is.is(true)); } @Test public void reQueuesForegroundJobWhenClientConnected() throws Exception { EngineClient mockClient = mock(EngineClient.class); Job job = JobFactory.generateForegroundJob("reverseString"); jobManager.storeJobForClient(job, mockClient); jobManager.nextJobForWorker(worker); Assert.assertThat("Job queue has no jobs", jobManager.getOrCreateJobQueue("reverseString").size(), Is.is(0L)); // Simulate abort before completion jobManager.unregisterWorker(worker); Assert.assertThat("Job queue has one job", jobManager.getOrCreateJobQueue("reverseString").size(), Is.is(1L)); // Re-fetch the job, make sure it's the same one Job nextJob = jobManager.nextJobForWorker(worker); Assert.assertThat("The job was returned to the queue", nextJob.equals(job), Is.is(true)); } @Test public void dropsForegroundJobWhenNoClientAttached() throws Exception { Job job = JobFactory.generateForegroundJob("reverseString"); jobManager.storeJob(job); jobManager.nextJobForWorker(worker); Assert.assertThat("Job queue has no jobs", jobManager.getOrCreateJobQueue("reverseString").size(), Is.is(0L)); // Simulate abort before completion jobManager.unregisterWorker(worker); Assert.assertThat("Job queue has no jobs", jobManager.getOrCreateJobQueue("reverseString").size(), Is.is(0L)); } @Test public void wakesUpWorkerWhenJobComesIn() throws Exception { Job job = JobFactory.generateBackgroundJob("reverseString"); jobManager.registerWorkerAbility("removeString", worker); jobManager.markWorkerAsAsleep(worker); jobManager.storeJob(job); verify(worker).wakeUp(); } @Test public void handlesExceptionsWhenWakingWorkers() throws Exception { Job job = JobFactory.generateBackgroundJob("reverseString"); EngineWorker mockWorker = mock(EngineWorker.class); //when(mockWorker.wakeUp()).thenThrow(new Exception("Can't send that packet")); //jobManager.registerWorkerAbility("reverseString", spyWorker); //jobManager.markWorkerAsAsleep(spyWorker); //jobManager.storeJob(job); //verify(spyWorker).wakeUp(); } @Test public void checksAndUpdatesJobStatus() throws Exception { Job job = JobFactory.generateBackgroundJob("reverseString"); jobManager.storeJob(job); JobStatus jobStatus = jobManager.checkJobStatus(job.getJobHandle()); Assert.assertThat("Job status denominator is 0", jobStatus.getDenominator(), Is.is(0)); Assert.assertThat("Job status numerator is 0", jobStatus.getNumerator(), Is.is(0)); Assert.assertThat("Job is not running", jobStatus.isRunning(), Is.is(false)); Assert.assertThat("Job status is unknown yet", jobStatus.isStatusKnown(), Is.is(false)); jobManager.nextJobForWorker(worker); jobStatus = jobManager.checkJobStatus(job.getJobHandle()); Assert.assertThat("Job is running", jobStatus.isRunning(), Is.is(true)); Assert.assertThat("Job status is still not known", jobStatus.isStatusKnown(), Is.is(false)); jobManager.updateJobStatus(job.getJobHandle(), 5, 100); jobStatus = jobManager.checkJobStatus(job.getJobHandle()); Assert.assertThat("Job status denominator is 100", jobStatus.getDenominator(), Is.is(100)); Assert.assertThat("Job status numerator is 5", jobStatus.getNumerator(), Is.is(5)); Assert.assertThat("Job is running", jobStatus.isRunning(), Is.is(true)); Assert.assertThat("Job status is known", jobStatus.isStatusKnown(), Is.is(true)); } @Test public void coalescesResultsForMultipleClients() throws Exception { EngineClient mockClientOne = mock(EngineClient.class); EngineClient mockClientTwo = mock(EngineClient.class); Job jobOne = JobFactory.generateForegroundJob("reverseString"); Job jobTwo = JobFactory.generateForegroundJob("reverseString"); jobTwo.setUniqueID(jobOne.getUniqueID()); jobManager.storeJobForClient(jobOne, mockClientOne); jobManager.storeJobForClient(jobTwo, mockClientTwo); Assert.assertThat("Job queue has one job because they had the same unique id", jobManager.getOrCreateJobQueue("reverseString").size(), Is.is(1L)); Assert.assertThat("There is 1 job pending in the job store", metricsEngine.getPendingJobsCount(), Is.is(1L)); Assert.assertThat("There has been 1 job queued in the job store", metricsEngine.getEnqueuedJobCount(), Is.is(1L)); Job nextJob = jobManager.nextJobForWorker(worker); byte[] result = {'r','e','s','u','l','t'}; Assert.assertThat("Job pulled out is equal to job #1", nextJob.equals(jobOne), Is.is(true)); // Complete the job jobManager.handleWorkCompletion(nextJob, result); verify(mockClientOne).sendWorkResults(jobOne.getJobHandle(), result); verify(mockClientTwo).sendWorkResults(jobOne.getJobHandle(), result); } // TODO: Verify that it will coalesce results if a job is submitted while a worker is working on the same one @Test public void sendsWorkDataResultsToClients() throws Exception { EngineClient mockClient = mock(EngineClient.class); Job jobOne = JobFactory.generateForegroundJob("reverseString"); jobManager.storeJobForClient(jobOne, mockClient); Job nextJob = jobManager.nextJobForWorker(worker); byte[] data = {'r','e','s','u','l','t'}; // Send back some data jobManager.handleWorkData(nextJob, data); verify(mockClient).sendWorkData(jobOne.getJobHandle(), data); } }