/* * SonarQube * Copyright (C) 2009-2017 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.ce.cleaning; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.Lock; import org.junit.Test; import org.sonar.ce.CeDistributedInformation; import org.sonar.ce.configuration.CeConfiguration; import org.sonar.ce.queue.InternalCeQueue; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class CeCleaningSchedulerImplTest { private Lock jobLock = mock(Lock.class); @Test public void startScheduling_does_not_fail_if_cleaning_methods_send_even_an_Exception() { InternalCeQueue mockedInternalCeQueue = mock(InternalCeQueue.class); CeDistributedInformation mockedCeDistributedInformation = mockCeDistributedInformation(jobLock); CeCleaningSchedulerImpl underTest = mockCeCleaningSchedulerImpl(mockedInternalCeQueue, mockedCeDistributedInformation); Exception exception = new IllegalArgumentException("faking unchecked exception thrown by cancelWornOuts"); doThrow(exception).when(mockedInternalCeQueue).cancelWornOuts(); doThrow(exception).when(mockedInternalCeQueue).resetTasksWithUnknownWorkerUUIDs(any()); underTest.startScheduling(); verify(mockedInternalCeQueue).cancelWornOuts(); verify(mockedInternalCeQueue).resetTasksWithUnknownWorkerUUIDs(any()); } @Test public void startScheduling_fails_if_cancelWornOuts_send_an_Error() { InternalCeQueue mockedInternalCeQueue = mock(InternalCeQueue.class); CeDistributedInformation mockedCeDistributedInformation = mockCeDistributedInformation(jobLock); CeCleaningSchedulerImpl underTest = mockCeCleaningSchedulerImpl(mockedInternalCeQueue, mockedCeDistributedInformation); Error expected = new Error("faking Error thrown by cancelWornOuts"); doThrow(expected).when(mockedInternalCeQueue).cancelWornOuts(); try { underTest.startScheduling(); fail("the error should have been thrown"); } catch (Error e) { assertThat(e).isSameAs(expected); } verify(mockedInternalCeQueue).cancelWornOuts(); } @Test public void startScheduling_fails_if_resetTasksWithUnknownWorkerUUIDs_send_an_Error() { InternalCeQueue mockedInternalCeQueue = mock(InternalCeQueue.class); CeDistributedInformation mockedCeDistributedInformation = mockCeDistributedInformation(jobLock); CeCleaningSchedulerImpl underTest = mockCeCleaningSchedulerImpl(mockedInternalCeQueue, mockedCeDistributedInformation); Error expected = new Error("faking Error thrown by cancelWornOuts"); doThrow(expected).when(mockedInternalCeQueue).resetTasksWithUnknownWorkerUUIDs(any()); try { underTest.startScheduling(); fail("the error should have been thrown"); } catch (Error e) { assertThat(e).isSameAs(expected); } verify(mockedInternalCeQueue).resetTasksWithUnknownWorkerUUIDs(any()); } @Test public void startScheduling_must_call_the_lock_methods() { InternalCeQueue mockedInternalCeQueue = mock(InternalCeQueue.class); CeDistributedInformation mockedCeDistributedInformation = mockCeDistributedInformation(jobLock); CeCleaningSchedulerImpl underTest = mockCeCleaningSchedulerImpl(mockedInternalCeQueue, mockedCeDistributedInformation); underTest.startScheduling(); verify(mockedCeDistributedInformation, times(1)).acquireCleanJobLock(); verify(jobLock, times(1)).tryLock(); verify(jobLock, times(1)).unlock(); } @Test public void startScheduling_must_not_execute_method_if_lock_is_already_acquired() { InternalCeQueue mockedInternalCeQueue = mock(InternalCeQueue.class); CeDistributedInformation mockedCeDistributedInformation = mockCeDistributedInformation(jobLock); when(jobLock.tryLock()).thenReturn(false); CeCleaningSchedulerImpl underTest = mockCeCleaningSchedulerImpl(mockedInternalCeQueue, mockedCeDistributedInformation); underTest.startScheduling(); verify(mockedCeDistributedInformation, times(1)).acquireCleanJobLock(); verify(jobLock, times(1)).tryLock(); // since lock cannot be locked, unlock method is not been called verify(jobLock, times(0)).unlock(); // since lock cannot be locked, cleaning job methods must not be called verify(mockedInternalCeQueue, times(0)).resetTasksWithUnknownWorkerUUIDs(any()); verify(mockedInternalCeQueue, times(0)).cancelWornOuts(); } @Test public void startScheduling_calls_cleaning_methods_of_internalCeQueue_at_fixed_rate_with_value_from_CeConfiguration() { InternalCeQueue mockedInternalCeQueue = mock(InternalCeQueue.class); long wornOutInitialDelay = 10L; long wornOutDelay = 20L; long unknownWorkerInitialDelay = 11L; long unknownWorkerDelay = 21L; CeConfiguration mockedCeConfiguration = mockCeConfiguration(wornOutInitialDelay, wornOutDelay); CeCleaningAdapter executorService = new CeCleaningAdapter() { @Override public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initDelay, long period, TimeUnit unit) { schedulerCounter++; switch(schedulerCounter) { case 1: assertThat(initDelay).isEqualTo(wornOutInitialDelay); assertThat(period).isEqualTo(wornOutDelay); assertThat(unit).isEqualTo(TimeUnit.MINUTES); break; case 2: assertThat(initDelay).isEqualTo(unknownWorkerInitialDelay); assertThat(period).isEqualTo(unknownWorkerDelay); assertThat(unit).isEqualTo(TimeUnit.MINUTES); break; default: fail("Unknwon call of scheduleWithFixedDelay"); } // synchronously execute command command.run(); return null; } }; CeCleaningSchedulerImpl underTest = new CeCleaningSchedulerImpl(executorService, mockedCeConfiguration, mockedInternalCeQueue, mockCeDistributedInformation(jobLock)); underTest.startScheduling(); assertThat(executorService.schedulerCounter).isEqualTo(1); verify(mockedInternalCeQueue).cancelWornOuts(); } private CeConfiguration mockCeConfiguration(long cleanCeTasksInitialDelay, long cleanCeTasksDelay) { CeConfiguration mockedCeConfiguration = mock(CeConfiguration.class); when(mockedCeConfiguration.getCleanCeTasksInitialDelay()).thenReturn(cleanCeTasksInitialDelay); when(mockedCeConfiguration.getCleanCeTasksDelay()).thenReturn(cleanCeTasksDelay); return mockedCeConfiguration; } private CeCleaningSchedulerImpl mockCeCleaningSchedulerImpl(InternalCeQueue internalCeQueue, CeDistributedInformation ceDistributedInformation) { return new CeCleaningSchedulerImpl(new CeCleaningAdapter() { @Override public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { // synchronously execute command command.run(); return null; } }, mockCeConfiguration(1, 10), internalCeQueue, ceDistributedInformation); } private CeDistributedInformation mockCeDistributedInformation(Lock result) { CeDistributedInformation mocked = mock(CeDistributedInformation.class); when(mocked.acquireCleanJobLock()).thenReturn(result); when(result.tryLock()).thenReturn(true); return mocked; } /** * Implementation of {@link CeCleaningExecutorService} which throws {@link UnsupportedOperationException} for every * method. */ private static class CeCleaningAdapter implements CeCleaningExecutorService { protected int schedulerCounter = 0; @Override public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { throw createUnsupportedOperationException(); } @Override public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) { throw createUnsupportedOperationException(); } @Override public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { throw createUnsupportedOperationException(); } @Override public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { throw createUnsupportedOperationException(); } @Override public void shutdown() { throw createUnsupportedOperationException(); } @Override public List<Runnable> shutdownNow() { throw createUnsupportedOperationException(); } @Override public boolean isShutdown() { throw createUnsupportedOperationException(); } @Override public boolean isTerminated() { throw createUnsupportedOperationException(); } @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { throw createUnsupportedOperationException(); } @Override public <T> Future<T> submit(Callable<T> task) { throw createUnsupportedOperationException(); } @Override public <T> Future<T> submit(Runnable task, T result) { throw createUnsupportedOperationException(); } @Override public Future<?> submit(Runnable task) { throw createUnsupportedOperationException(); } @Override public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException { throw createUnsupportedOperationException(); } @Override public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException { throw createUnsupportedOperationException(); } @Override public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException { throw createUnsupportedOperationException(); } @Override public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { throw createUnsupportedOperationException(); } @Override public void execute(Runnable command) { throw createUnsupportedOperationException(); } private UnsupportedOperationException createUnsupportedOperationException() { return new UnsupportedOperationException("Unexpected call"); } } }