/* * Copyright 2016 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.materials; import com.thoughtworks.go.config.*; import com.thoughtworks.go.config.materials.SubprocessExecutionContext; import com.thoughtworks.go.config.materials.mercurial.HgMaterial; import com.thoughtworks.go.config.materials.mercurial.HgMaterialConfig; import com.thoughtworks.go.config.remote.ConfigRepoConfig; import com.thoughtworks.go.config.remote.PartialConfig; import com.thoughtworks.go.domain.materials.MaterialConfig; import com.thoughtworks.go.helper.*; import com.thoughtworks.go.server.cronjob.GoDiskSpaceMonitor; import com.thoughtworks.go.server.dao.DatabaseAccessHelper; import com.thoughtworks.go.server.dao.PipelineDao; import com.thoughtworks.go.server.perf.MDUPerformanceLogger; import com.thoughtworks.go.server.persistence.MaterialRepository; import com.thoughtworks.go.server.scheduling.ScheduleHelper; import com.thoughtworks.go.server.service.*; import com.thoughtworks.go.server.transaction.TransactionTemplate; import com.thoughtworks.go.serverhealth.ServerHealthService; import com.thoughtworks.go.util.ConfigElementImplementationRegistryMother; import com.thoughtworks.go.util.GoConfigFileHelper; import com.thoughtworks.go.util.SystemEnvironment; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.io.File; import static junit.framework.Assert.fail; import static junit.framework.TestCase.assertNotNull; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNot.not; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.mockito.Mockito.mock; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:WEB-INF/applicationContext-global.xml", "classpath:WEB-INF/applicationContext-dataLocalAccess.xml", "classpath:WEB-INF/applicationContext-acegi-security.xml" }) public class ConfigMaterialUpdaterIntegrationTest { @Autowired private GoConfigDao goConfigDao; @Autowired private GoConfigService goConfigService; @Autowired private PipelineDao pipelineDao; @Autowired private ServerHealthService serverHealthService; @Autowired PipelineService pipelineService; @Autowired private ScheduleHelper scheduleHelper; @Autowired private DatabaseAccessHelper dbHelper; @Autowired private MaterialDatabaseUpdater materialDatabaseUpdater; @Autowired private MaterialRepository materialRepository; @Autowired private MaterialUpdateService materialUpdateService; @Autowired private SubprocessExecutionContext subprocessExecutionContext; @Autowired private ConfigMaterialUpdater materialUpdater; @Autowired private GoRepoConfigDataSource goRepoConfigDataSource; @Autowired private SystemEnvironment systemEnvironment; @Autowired private MaterialConfigConverter materialConfigConverter; @Autowired private ConfigCache configCache; @Autowired private CachedGoConfig cachedGoConfig; @Autowired private MaterialUpdateCompletedTopic topic; @Autowired private ConfigMaterialUpdateCompletedTopic configTopic; @Autowired private TransactionTemplate transactionTemplate; private MDUPerformanceLogger logger; private static GoConfigFileHelper configHelper = new GoConfigFileHelper(); public DiskSpaceSimulator diskSpaceSimulator; private MaterialConfig materialConfig; private MaterialUpdateListener worker; private HgTestRepo hgRepo; private HgMaterial material; private MagicalGoConfigXmlWriter xmlWriter; private ConfigTestRepo configTestRepo; @Before public void setup() throws Exception { diskSpaceSimulator = new DiskSpaceSimulator(); hgRepo = new HgTestRepo("testHgRepo"); dbHelper.onSetUp(); configHelper.onSetUp(); configHelper.usingCruiseConfigDao(goConfigDao).initializeConfigFile(); materialConfig = new HgMaterialConfig(hgRepo.projectRepositoryUrl(),"testHgRepo"); configHelper.addConfigRepo(new ConfigRepoConfig(materialConfig,"gocd-xml")); logger = mock(MDUPerformanceLogger.class); TestingEmailSender emailSender = new TestingEmailSender(); SystemDiskSpaceChecker mockDiskSpaceChecker = Mockito.mock(SystemDiskSpaceChecker.class); StageService stageService = mock(StageService.class); ConfigDbStateRepository configDbStateRepository = mock(ConfigDbStateRepository.class); GoDiskSpaceMonitor goDiskSpaceMonitor = new GoDiskSpaceMonitor(goConfigService, systemEnvironment, serverHealthService, emailSender, mockDiskSpaceChecker, mock(ArtifactsService.class), stageService, configDbStateRepository); goDiskSpaceMonitor.initialize(); worker = new MaterialUpdateListener(configTopic,materialDatabaseUpdater,logger,goDiskSpaceMonitor); xmlWriter = new MagicalGoConfigXmlWriter(configCache, ConfigElementImplementationRegistryMother.withNoPlugins()); configTestRepo = new ConfigTestRepo(hgRepo, xmlWriter); this.material = configTestRepo.getMaterial(); } @After public void teardown() throws Exception { diskSpaceSimulator.onTearDown(); TestRepo.internalTearDown(); dbHelper.onTearDown(); configHelper.onTearDown(); } @Test public void shouldBeInProgressUntilParsedWhenValid() throws Exception { materialUpdateService.updateMaterial(material); // because first result of parsing is left in repo data source // and then message is posted to take material from 'in progress' state assertInProgressState(); } @Test public void shouldBeInProgressUntilParsedWhenInvalid() throws Exception { configTestRepo.addCodeToRepositoryAndPush("bogus.gocd.xml", "added bad config file", "<?xml ve\"?>\n" + "<cru>\n" + "</cruise>"); materialUpdateService.updateMaterial(material); // because first result of parsing is left in repo data source // and then message is posted to take material from 'in progress' state assertInProgressState(); } private void assertInProgressState() throws InterruptedException { int i = 0; while (goRepoConfigDataSource.getRevisionAtLastAttempt(materialConfig) == null) { if(!materialUpdateService.isInProgress(material)) Assert.fail("should be still in progress"); Thread.sleep(1); if(i++ > 10000) fail("material is hung - more than 10 seconds in progress"); } } @Test public void shouldParseEmptyRepository() throws Exception { materialUpdateService.updateMaterial(material); waitForMaterialNotInProgress(); String revision = goRepoConfigDataSource.getRevisionAtLastAttempt(materialConfig); assertNotNull(revision); PartialConfig partial = goRepoConfigDataSource.latestPartialConfigForMaterial(materialConfig); assertThat(partial.getGroups().size(), is(0)); assertThat(partial.getEnvironments().size(), is(0)); } private void waitForMaterialNotInProgress() throws InterruptedException { // time for messages to pass through all services int i = 0; while (materialUpdateService.isInProgress(material)) { Thread.sleep(100); if(i++ > 100) fail("material is hung - more than 10 seconds in progress"); } } @Test public void shouldNotParseAgainWhenNoChangesInMaterial() throws Exception { materialUpdateService.updateMaterial(material); // time for messages to pass through all services waitForMaterialNotInProgress(); String revision = goRepoConfigDataSource.getRevisionAtLastAttempt(materialConfig); assertNotNull(revision); PartialConfig partial = goRepoConfigDataSource.latestPartialConfigForMaterial(materialConfig); materialUpdateService.updateMaterial(material); // time for messages to pass through all services waitForMaterialNotInProgress(); PartialConfig partial2 = goRepoConfigDataSource.latestPartialConfigForMaterial(materialConfig); assertSame(partial,partial2); } @Test public void shouldParseAgainWhenChangesInMaterial() throws Exception { materialUpdateService.updateMaterial(material); // time for messages to pass through all services waitForMaterialNotInProgress(); String revision = goRepoConfigDataSource.getRevisionAtLastAttempt(materialConfig); assertNotNull(revision); PartialConfig partial = goRepoConfigDataSource.latestPartialConfigForMaterial(materialConfig); hgRepo.commitAndPushFile("newFile.bla","could be config file"); materialUpdateService.updateMaterial(material); // time for messages to pass through all services waitForMaterialNotInProgress(); PartialConfig partial2 = goRepoConfigDataSource.latestPartialConfigForMaterial(materialConfig); assertNotSame(partial, partial2); assertThat("originsShouldDiffer", partial2.getOrigin(), is(not(partial.getOrigin()))); } @Test public void shouldParseAndLoadValidPartialConfig() throws Exception { String fileName = "pipe1.gocd.xml"; GoConfigMother mother = new GoConfigMother(); PipelineConfig pipelineConfig = mother.cruiseConfigWithOnePipelineGroup().getAllPipelineConfigs().get(0); configTestRepo.addPipelineToRepositoryAndPush(fileName, pipelineConfig); materialUpdateService.updateMaterial(material); // time for messages to pass through all services waitForMaterialNotInProgress(); PartialConfig partial = goRepoConfigDataSource.latestPartialConfigForMaterial(materialConfig); assertNotNull(partial); assertThat(partial.getGroups().get(0).size(),is(1)); assertThat(partial.getGroups().get(0).get(0), is(pipelineConfig)); } @Test public void shouldMergePipelineFromValidConfigRepository() throws Exception { String fileName = "pipe1.gocd.xml"; GoConfigMother mother = new GoConfigMother(); PipelineConfig pipelineConfig = mother.cruiseConfigWithOnePipelineGroup().getAllPipelineConfigs().get(0); configTestRepo.addPipelineToRepositoryAndPush(fileName, pipelineConfig); materialUpdateService.updateMaterial(material); Assert.assertThat(materialUpdateService.isInProgress(material),is(true)); // time for messages to pass through all services waitForMaterialNotInProgress(); cachedGoConfig.forceReload(); assertThat(goConfigService.hasPipelineNamed(pipelineConfig.name()), is(true)); assertThat(goConfigService.pipelineConfigNamed(pipelineConfig.name()), is(pipelineConfig)); } @Test public void shouldCheckoutNewMaterial() throws Exception { GoConfigMother mother = new GoConfigMother(); PipelineConfig pipelineConfig = mother.cruiseConfigWithOnePipelineGroup().getAllPipelineConfigs().get(0); configTestRepo.addPipelineToRepositoryAndPush("pipe1.gocd.xml", pipelineConfig); materialUpdateService.updateMaterial(material); // time for messages to pass through all services waitForMaterialNotInProgress(); File flyweightDir = materialRepository.folderFor(material); Assert.assertThat(flyweightDir.exists(),is(true)); Assert.assertThat(new File(flyweightDir,"pipe1.gocd.xml").exists(),is(true)); } @Test public void shouldCheckoutChangedInExistingMaterial() throws Exception { GoConfigMother mother = new GoConfigMother(); PipelineConfig pipelineConfig = mother.cruiseConfigWithOnePipelineGroup().getAllPipelineConfigs().get(0); configTestRepo.addPipelineToRepositoryAndPush("pipe1.gocd.xml", pipelineConfig); materialUpdateService.updateMaterial(material); // time for messages to pass through all services waitForMaterialNotInProgress(); configTestRepo.addPipelineToRepositoryAndPush("pipe2.gocd.xml", pipelineConfig); materialUpdateService.updateMaterial(material); // time for messages to pass through all services waitForMaterialNotInProgress(); File flyweightDir = materialRepository.folderFor(material); Assert.assertThat(flyweightDir.exists(),is(true)); Assert.assertThat(new File(flyweightDir,"pipe1.gocd.xml").exists(),is(true)); Assert.assertThat("shouldContainFilesAddedLater",new File(flyweightDir,"pipe2.gocd.xml").exists(),is(true)); } @Test public void shouldNotMergeFromInvalidConfigRepository_AndShouldKeepLastValidPart() throws Exception { String fileName = "pipe1.gocd.xml"; GoConfigMother mother = new GoConfigMother(); PipelineConfig pipelineConfig = mother.cruiseConfigWithOnePipelineGroup().getAllPipelineConfigs().get(0); configTestRepo.addPipelineToRepositoryAndPush(fileName, pipelineConfig); materialUpdateService.updateMaterial(material); // time for messages to pass through all services waitForMaterialNotInProgress(); cachedGoConfig.forceReload(); assertThat(goConfigService.hasPipelineNamed(pipelineConfig.name()), is(true)); assertThat(goConfigService.pipelineConfigNamed(pipelineConfig.name()),is(pipelineConfig)); configTestRepo.addCodeToRepositoryAndPush("badPipe.gocd.xml", "added bad config file", "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<cruise xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"cruise-config.xsd\" schemaVersion=\"38\">\n" + "<pipelines group=\"changed\">\n" + " <pipeline name=\"badPipe\">\n" + " <materials>\n" + " <svn url=\"file:///tmp/foo\" />\n" + " <svn url=\"file:///tmp/foo\" />\n" + " </materials>\n" + " </pipeline>\n" + "</pipelines>" + "</cruise>"); materialUpdateService.updateMaterial(material); // time for messages to pass through all services waitForMaterialNotInProgress(); cachedGoConfig.forceReload(); // but we still have the old part assertThat(goConfigService.hasPipelineNamed(pipelineConfig.name()), is(true)); assertThat(goConfigService.pipelineConfigNamed(pipelineConfig.name()), is(pipelineConfig)); // and no trace of badPipe assertThat(goConfigService.hasPipelineNamed(new CaseInsensitiveString("badPipe")), is(false)); } }