/*
* 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.server.service;
import com.thoughtworks.go.config.CaseInsensitiveString;
import com.thoughtworks.go.config.PipelineConfig;
import com.thoughtworks.go.domain.materials.MaterialConfig;
import com.thoughtworks.go.helper.ScheduleCheckMessageMatcher;
import com.thoughtworks.go.listener.ConfigChangedListener;
import com.thoughtworks.go.listener.EntityConfigChangedListener;
import com.thoughtworks.go.server.domain.Username;
import com.thoughtworks.go.server.perf.SchedulingPerformanceLogger;
import com.thoughtworks.go.server.scheduling.*;
import com.thoughtworks.go.server.service.result.HttpOperationResult;
import com.thoughtworks.go.server.service.result.OperationResult;
import com.thoughtworks.go.serverhealth.HealthStateScope;
import com.thoughtworks.go.serverhealth.HealthStateType;
import com.thoughtworks.go.serverhealth.ServerHealthService;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.internal.verification.NoMoreInteractions;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.thoughtworks.go.helper.GoConfigMother.configWithPipelines;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;
public class PipelineSchedulerTest {
private ScheduleCheckQueue queue;
private ScheduleCheckCompletedTopic topic;
private PipelineScheduler scheduler;
private GoConfigService configService;
private BuildCauseProducerService buildCauseProducerService;
private SchedulingPerformanceLogger schedulingPerformanceLogger;
@Before
public void setUp() {
queue = mock(ScheduleCheckQueue.class);
configService = mock(GoConfigService.class);
ServerHealthService serverHealthService = mock(ServerHealthService.class);
SchedulingCheckerService schedulingCheckerService = mock(SchedulingCheckerService.class);
buildCauseProducerService = mock(BuildCauseProducerService.class);
topic = mock(ScheduleCheckCompletedTopic.class);
schedulingPerformanceLogger = mock(SchedulingPerformanceLogger.class);
scheduler = new PipelineScheduler(configService, serverHealthService, schedulingCheckerService,
buildCauseProducerService, queue, topic, schedulingPerformanceLogger);
}
@Test
public void shouldCheckAllPipelines() {
scheduler.onConfigChange(configWithPipelines("cruise"));
scheduler.onConfigChange(configWithPipelines("cruise", "mingle"));
scheduler.checkPipelines();
verify(queue).post(ScheduleCheckMessageMatcher.matchScheduleCheckMessage("cruise"));
verify(queue).post(ScheduleCheckMessageMatcher.matchScheduleCheckMessage("mingle"));
}
@Test
public void shouldNotCheckDeletedPipelines() {
scheduler.onConfigChange(configWithPipelines("cruise", "mingle", "twist"));
scheduler.onConfigChange(configWithPipelines("cruise"));
scheduler.checkPipelines();
verify(queue).post(ScheduleCheckMessageMatcher.matchScheduleCheckMessage("cruise"));
}
@Test
public void shouldNotCheckPipelineWhenChecking() {
scheduler.onConfigChange(configWithPipelines("cruise"));
scheduler.checkPipelines();
scheduler.checkPipelines();
verify(queue).post(ScheduleCheckMessageMatcher.matchScheduleCheckMessage("cruise"));
}
@Test
public void shouldCheckPipelineWhenItBecomesIdle() {
scheduler.onConfigChange(configWithPipelines("cruise"));
scheduler.checkPipelines();
scheduler.onMessage(new ScheduleCheckCompletedMessage("cruise", 1234));
scheduler.checkPipelines();
verify(queue, times(2)).post(ScheduleCheckMessageMatcher.matchScheduleCheckMessage("cruise"));
}
@Test
public void shouldAddErrorIfPipelineisNotFound() throws Exception {
when(configService.hasPipelineNamed(new CaseInsensitiveString("invalid"))).thenReturn(false);
OperationResult operationResult = mock(OperationResult.class);
final HashMap<String, String> revisions = new HashMap<>();
final HashMap<String, String> environmentVariables = new HashMap<>();
scheduler.manualProduceBuildCauseAndSave("invalid", Username.ANONYMOUS, new ScheduleOptions(revisions, environmentVariables, new HashMap<>()), operationResult);
verify(operationResult).notFound("Pipeline 'invalid' not found", "Pipeline 'invalid' not found", HealthStateType.general(
HealthStateScope.forPipeline("invalid")));
}
@Test
public void shouldReturn404WhenVariableIsNotInConfigScope() {
when(configService.hasPipelineNamed(new CaseInsensitiveString("blahPipeline"))).thenReturn(true);
when(configService.hasVariableInScope("blahPipeline", "blahVariable")).thenReturn(false);
OperationResult operationResult = mock(OperationResult.class);
final HashMap<String, String> revisions = new HashMap<>();
scheduler.manualProduceBuildCauseAndSave("blahPipeline", Username.ANONYMOUS, new ScheduleOptions(revisions, Collections.singletonMap("blahVariable", "blahValue"), new HashMap<>()), operationResult);
//noinspection unchecked
verify(buildCauseProducerService, new NoMoreInteractions()).manualSchedulePipeline(any(Username.class), any(CaseInsensitiveString.class), any(ScheduleOptions.class), any(OperationResult.class));
verify(operationResult).notFound("Variable 'blahVariable' has not been configured for pipeline 'blahPipeline'", "Variable 'blahVariable' has not been configured for pipeline 'blahPipeline'",
HealthStateType.general(HealthStateScope.forPipeline("blahPipeline")));
}
@Test
public void shouldSchedulePipelineWithEnvironmentVariableOverrides() {
PipelineConfig pipelineConfig = configWithPipelines("blahPipeline").pipelineConfigByName(new CaseInsensitiveString("blahPipeline"));
when(configService.pipelineConfigNamed(new CaseInsensitiveString("blahPipeline"))).thenReturn(pipelineConfig);
when(configService.hasPipelineNamed(new CaseInsensitiveString("blahPipeline"))).thenReturn(true);
when(configService.hasVariableInScope("blahPipeline", "blahVariable")).thenReturn(true);
OperationResult operationResult = mock(OperationResult.class);
Map<String, String> variables = Collections.singletonMap("blahVariable", "blahValue");
final HashMap<String, String> revisions = new HashMap<>();
scheduler.manualProduceBuildCauseAndSave("blahPipeline", Username.ANONYMOUS, new ScheduleOptions(revisions, variables, new HashMap<>()), operationResult);
//noinspection unchecked
verify(buildCauseProducerService).manualSchedulePipeline(Username.ANONYMOUS, pipelineConfig.name(), new ScheduleOptions(new HashMap<>(), variables, new HashMap<>()),
operationResult);
}
@Test
public void shouldUpdateOperationResultTo404WhenAnInvalidMaterialIsSpecified() throws Exception {
when(configService.hasPipelineNamed(new CaseInsensitiveString("pipeline"))).thenReturn(true);
when(configService.findMaterial(new CaseInsensitiveString("pipeline"), "invalid-material")).thenReturn(null);
HttpOperationResult result = new HttpOperationResult();
final HashMap<String, String> environmentVariables = new HashMap<>();
scheduler.manualProduceBuildCauseAndSave("pipeline", Username.ANONYMOUS, new ScheduleOptions(Collections.singletonMap("invalid-material", "blah-revision"), environmentVariables, new HashMap<>()), result);
assertThat(result.httpCode(), is(404));
assertThat(result.message(), is("material with fingerprint [invalid-material] not found in pipeline [pipeline]"));
}
@Test
public void shouldNotAcceptEmptyRevision() throws Exception {
when(configService.hasPipelineNamed(new CaseInsensitiveString("pipeline"))).thenReturn(true);
MaterialConfig materialConfig = mock(MaterialConfig.class);
when(configService.findMaterial(new CaseInsensitiveString("pipeline"), "invalid-material")).thenReturn(materialConfig);
HttpOperationResult result = new HttpOperationResult();
final HashMap<String, String> environmentVariables = new HashMap<>();
scheduler.manualProduceBuildCauseAndSave("pipeline", Username.ANONYMOUS, new ScheduleOptions(Collections.singletonMap("invalid-material", ""), environmentVariables, new HashMap<>()), result);
assertThat(result.httpCode(), is(406));
assertThat(result.message(), is("material with fingerprint [invalid-material] has empty revision"));
}
@Test
public void shouldAddPipelineConfigToPipelinesOnPipelineConfigChanged() {
ArgumentCaptor<ConfigChangedListener> captor = ArgumentCaptor.forClass(ConfigChangedListener.class);
doNothing().when(configService).register(captor.capture());
scheduler.initialize();
List<ConfigChangedListener> listeners = captor.getAllValues();
assertThat(listeners.contains(scheduler), is(true));
assertThat(listeners.get(1) instanceof EntityConfigChangedListener, is(true));
EntityConfigChangedListener<PipelineConfig> entityConfigChangedListener = (EntityConfigChangedListener<PipelineConfig>) listeners.get(1);
PipelineConfig newPipeline = mock(PipelineConfig.class);
String pipelineName = "newly-added-pipeline";
when(newPipeline.name()).thenReturn(new CaseInsensitiveString(pipelineName));
entityConfigChangedListener.onEntityConfigChange(newPipeline);
scheduler.checkPipelines();
verify(queue, times(1)).post(ScheduleCheckMessageMatcher.matchScheduleCheckMessage(pipelineName));
}
}