/*
* Copyright 2017 ThoughtWorks, 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 com.thoughtworks.go.domain.cctray;
import com.thoughtworks.go.config.CaseInsensitiveString;
import com.thoughtworks.go.config.CruiseConfig;
import com.thoughtworks.go.config.PipelineConfig;
import com.thoughtworks.go.domain.JobInstance;
import com.thoughtworks.go.domain.Stage;
import com.thoughtworks.go.helper.GoConfigMother;
import com.thoughtworks.go.helper.JobInstanceMother;
import com.thoughtworks.go.helper.StageMother;
import com.thoughtworks.go.listener.ConfigChangedListener;
import com.thoughtworks.go.listener.EntityConfigChangedListener;
import com.thoughtworks.go.server.service.GoConfigService;
import com.thoughtworks.go.util.LogFixture;
import org.apache.log4j.Level;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import java.util.List;
import static com.thoughtworks.go.util.LogFixture.logFixtureFor;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.*;
public class CcTrayActivityListenerTest {
private StubCcTrayJobStatusChangeHandler jobStatusChangeHandler;
private StubCcTrayStageStatusChangeHandler stageStatusChangeHandler;
private StubCcTrayConfigChangeHandler configChangeHandler;
private GoConfigService goConfigService;
@Before
public void setUp() throws Exception {
jobStatusChangeHandler = new StubCcTrayJobStatusChangeHandler();
stageStatusChangeHandler = new StubCcTrayStageStatusChangeHandler();
configChangeHandler = new StubCcTrayConfigChangeHandler();
goConfigService = mock(GoConfigService.class);
}
@Test
public void shouldRegisterSelfForConfigChangeHandlingOnInitialization() throws Exception {
CcTrayActivityListener listener = new CcTrayActivityListener(goConfigService, jobStatusChangeHandler, stageStatusChangeHandler, configChangeHandler);
listener.initialize();
verify(goConfigService).register(listener);
}
@Test
public void shouldMultiplexEventsFromDifferentThreadsOnToHandlersOnASingleThread() throws Exception {
CcTrayActivityListener listener = new CcTrayActivityListener(goConfigService, jobStatusChangeHandler, stageStatusChangeHandler, configChangeHandler);
listener.initialize();
Thread t1 = callJobStatusChangeInNewThread(listener);
Thread t2 = callStageStatusChangeInNewThread(listener);
Thread t3 = callConfigChangeInNewThread(listener);
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
waitForProcessingToHappen();
assertThat(jobStatusChangeHandler.threadOfCall, is(not(nullValue())));
assertThat(jobStatusChangeHandler.threadOfCall, is(not(Thread.currentThread())));
assertThat(jobStatusChangeHandler.threadOfCall, is(not(t1)));
assertThat(jobStatusChangeHandler.threadOfCall, is(stageStatusChangeHandler.threadOfCall));
assertThat(stageStatusChangeHandler.threadOfCall, is(configChangeHandler.threadOfCall));
}
@Test
public void shouldNotAllowTheQueueProcessorToBeStartedMultipleTimes() throws Exception {
CcTrayActivityListener listener = new CcTrayActivityListener(goConfigService, jobStatusChangeHandler, stageStatusChangeHandler, configChangeHandler);
listener.initialize();
try {
listener.initialize();
fail("Should have failed to start queue processor a second time.");
} catch (RuntimeException e) {
assertThat(e.getMessage(), is("Cannot start queue processor multiple times."));
}
}
@Test
public void shouldLogAndIgnoreAnyChangesWhichCannotBeHandled() throws Exception {
CcTrayStageStatusChangeHandler normalStageStatusChangeHandler = mock(CcTrayStageStatusChangeHandler.class);
CcTrayJobStatusChangeHandler failingJobStatusChangeHandler = mock(CcTrayJobStatusChangeHandler.class);
doThrow(new RuntimeException("Ouch. Failed.")).when(failingJobStatusChangeHandler).call(any(JobInstance.class));
try (LogFixture logFixture = logFixtureFor(CcTrayActivityListener.class, Level.DEBUG)) {
CcTrayActivityListener listener = new CcTrayActivityListener(goConfigService, failingJobStatusChangeHandler, normalStageStatusChangeHandler, configChangeHandler);
listener.initialize();
listener.jobStatusChanged(JobInstanceMother.passed("some-job-this-should-fail"));
listener.stageStatusChanged(StageMother.unrunStage("some-stage"));
waitForProcessingToHappen();
assertThat(logFixture.contains(Level.WARN, "Failed to handle action in CCTray queue"), is(true));
}
verify(normalStageStatusChangeHandler).call(StageMother.unrunStage("some-stage"));
}
@Test
public void shouldInvokeConfigChangeHandlerWhenPipelineConfigChanges() throws InterruptedException {
PipelineConfig pipelineConfig = mock(PipelineConfig.class);
CaseInsensitiveString p1 = new CaseInsensitiveString("p1");
when(pipelineConfig.name()).thenReturn(p1);
CcTrayConfigChangeHandler ccTrayConfigChangeHandler = mock(CcTrayConfigChangeHandler.class);
ArgumentCaptor<ConfigChangedListener> captor = ArgumentCaptor.forClass(ConfigChangedListener.class);
doNothing().when(goConfigService).register(captor.capture());
when(goConfigService.findGroupNameByPipeline(p1)).thenReturn("group1");
CcTrayActivityListener listener = new CcTrayActivityListener(goConfigService, mock(CcTrayJobStatusChangeHandler.class), mock(CcTrayStageStatusChangeHandler.class), ccTrayConfigChangeHandler);
listener.initialize();
List<ConfigChangedListener> listeners = captor.getAllValues();
assertThat(listeners.get(1) instanceof EntityConfigChangedListener, is(true));
EntityConfigChangedListener<PipelineConfig> pipelineConfigChangeListener = (EntityConfigChangedListener<PipelineConfig>) listeners.get(1);
pipelineConfigChangeListener.onEntityConfigChange(pipelineConfig);
waitForProcessingToHappen();
verify(ccTrayConfigChangeHandler).call(pipelineConfig, "group1");
}
private void waitForProcessingToHappen() throws InterruptedException {
Thread.sleep(1000); /* Prevent potential race, of queue not being processed. Being a little lazy. :( */
}
private Thread callJobStatusChangeInNewThread(final CcTrayActivityListener listener) {
return new Thread() {
@Override
public void run() {
listener.jobStatusChanged(JobInstanceMother.passed("some-job"));
}
};
}
private Thread callStageStatusChangeInNewThread(final CcTrayActivityListener listener) {
return new Thread() {
@Override
public void run() {
listener.stageStatusChanged(StageMother.unrunStage("some-stage"));
}
};
}
private Thread callConfigChangeInNewThread(final CcTrayActivityListener listener) {
return new Thread() {
@Override
public void run() {
listener.onConfigChange(GoConfigMother.defaultCruiseConfig());
}
};
}
private class StubCcTrayJobStatusChangeHandler extends CcTrayJobStatusChangeHandler {
private Thread threadOfCall;
public StubCcTrayJobStatusChangeHandler() {
super(null);
}
@Override
public void call(JobInstance job) {
threadOfCall = Thread.currentThread();
}
}
private class StubCcTrayStageStatusChangeHandler extends CcTrayStageStatusChangeHandler {
private Thread threadOfCall;
public StubCcTrayStageStatusChangeHandler() {
super(null, null, null);
}
@Override
public void call(Stage stage) {
threadOfCall = Thread.currentThread();
}
}
private class StubCcTrayConfigChangeHandler extends CcTrayConfigChangeHandler {
private Thread threadOfCall;
public StubCcTrayConfigChangeHandler() {
super(null, null, null);
}
@Override
public void call(CruiseConfig config) {
threadOfCall = Thread.currentThread();
}
}
}