/*
* Copyright 2015 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.service;
import com.thoughtworks.go.GoConfigRevisions;
import com.thoughtworks.go.config.exceptions.ConfigFileHasChangedException;
import com.thoughtworks.go.config.exceptions.ConfigMergeException;
import com.thoughtworks.go.domain.GoConfigRevision;
import com.thoughtworks.go.helper.ConfigFileFixture;
import com.thoughtworks.go.util.SystemEnvironment;
import com.thoughtworks.go.util.TimeProvider;
import org.eclipse.jgit.api.ListBranchCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ConfigRepositoryTest {
private ConfigRepository configRepo;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private SystemEnvironment systemEnvironment;
@Before
public void setUp() throws IOException {
File configRepoDir = this.temporaryFolder.newFolder();
systemEnvironment = mock(SystemEnvironment.class);
when(systemEnvironment.getConfigRepoDir()).thenReturn(configRepoDir);
when(systemEnvironment.get(SystemEnvironment.GO_CONFIG_REPO_GC_AGGRESSIVE)).thenReturn(true);
when(systemEnvironment.get(SystemEnvironment.GO_CONFIG_REPO_PERIODIC_GC)).thenReturn(true);
configRepo = new ConfigRepository(systemEnvironment);
configRepo.initialize();
}
@Test
public void shouldBeAbleToCheckin() throws Exception {
configRepo.checkin(new GoConfigRevision("v1", "md5-v1", "user-name", "100.3.9", new TimeProvider()));
configRepo.checkin(new GoConfigRevision("v1 v2", "md5-v2", "user-name", "100.9.8", new TimeProvider()));
assertThat(configRepo.getRevision("md5-v1").getContent(), is("v1"));
assertThat(configRepo.getRevision("md5-v2").getContent(), is("v1 v2"));
}
@Test
public void shouldGetCommitsCorrectly() throws Exception {
configRepo.checkin(new GoConfigRevision("v1", "md5-v1", "user-name", "100.3.9", new TimeProvider()));
configRepo.checkin(new GoConfigRevision("v2", "md5-v2", "user-name", "100.3.9", new TimeProvider()));
configRepo.checkin(new GoConfigRevision("v3", "md5-v3", "user-name", "100.3.9", new TimeProvider()));
configRepo.checkin(new GoConfigRevision("v4", "md5-v4", "user-name", "100.3.9", new TimeProvider()));
GoConfigRevisions goConfigRevisions = configRepo.getCommits(3, 0);
assertThat(goConfigRevisions.size(), is(3));
assertThat(goConfigRevisions.get(0).getContent(), is(nullValue()));
assertThat(goConfigRevisions.get(0).getMd5(), is("md5-v4"));
assertThat(goConfigRevisions.get(1).getMd5(), is("md5-v3"));
assertThat(goConfigRevisions.get(2).getMd5(), is("md5-v2"));
goConfigRevisions = configRepo.getCommits(3, 3);
assertThat(goConfigRevisions.size(), is(1));
assertThat(goConfigRevisions.get(0).getMd5(), is("md5-v1"));
}
@Test
public void shouldFailWhenDoesNotFindARev() throws Exception {
configRepo.checkin(new GoConfigRevision("v1", "md5-v1", "user-name", "100.3.9", new TimeProvider()));
try {
configRepo.getRevision("some-random-revision");
fail("should have failed as revision does not exist");
} catch (RuntimeException e) {
assertThat(e.getMessage(), is("There is no config version corresponding to md5: 'some-random-revision'"));
}
}
@Test
public void shouldUnderstandRevision_current_asLatestRevision() throws Exception {
configRepo.checkin(new GoConfigRevision("v1", "md5-v1", "user-name", "100.3.9", new TimeProvider()));
configRepo.checkin(new GoConfigRevision("v1 v2", "md5-v2", "user-name", "100.9.8", new TimeProvider()));
assertThat(configRepo.getRevision("current").getMd5(), is("md5-v2"));
}
@Test
public void shouldReturnNullWhenThereAreNoCheckIns() throws GitAPIException, IOException {
assertThat(configRepo.getRevision("current"), is(nullValue()));
}
@Test
public void shouldNotCommitWhenNothingChanged() throws Exception {
configRepo.checkin(new GoConfigRevision("v1", "md5-v1", "user-name", "100.3.9", new TimeProvider()));
configRepo.checkin(new GoConfigRevision("v1 v2", "md5-v1", "loser-name", "501.9.8", new TimeProvider()));//md5 is solely trusted
Iterator<RevCommit> commitIterator = configRepo.revisions().iterator();
int size = 0;
while (commitIterator.hasNext()) {
size++;
commitIterator.next();
}
assertThat(size, is(1));
}
@Test
public void shouldShowDiffBetweenTwoConsecutiveGitRevisions() throws Exception {
configRepo.checkin(goConfigRevision(ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 33), "md5-1"));
RevCommit previousCommit = configRepo.revisions().iterator().next();
configRepo.checkin(new GoConfigRevision(ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 60), "md5-2", "user-2", "13.2", new TimeProvider()));
RevCommit latestCommit = configRepo.revisions().iterator().next();
String configChangesLine1 = "-<cruise schemaVersion='33'>";
String configChangesLine2 = "+<cruise schemaVersion='60'>";
String actual = configRepo.findDiffBetweenTwoRevisions(latestCommit, previousCommit);
assertThat(actual, containsString(configChangesLine1));
assertThat(actual, containsString(configChangesLine2));
}
@Test
public void shouldShowDiffBetweenAnyTwoGitRevisionsGivenTheirMd5s() throws Exception {
configRepo.checkin(goConfigRevision(ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 33), "md5-1"));
configRepo.checkin(new GoConfigRevision(ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 60), "md5-2", "user-2", "13.2", new TimeProvider()));
configRepo.checkin(new GoConfigRevision(ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 55), "md5-3", "user-1", "13.2", new TimeProvider()));
String configChangesLine1 = "-<cruise schemaVersion='33'>";
String configChangesLine2 = "+<cruise schemaVersion='55'>";
String actual = configRepo.findDiffBetweenTwoRevisions(configRepo.getRevCommitForMd5("md5-3"), configRepo.getRevCommitForMd5("md5-1"));
assertThat(actual, containsString(configChangesLine1));
assertThat(actual, containsString(configChangesLine2));
}
@Test
public void shouldReturnNullForFirstCommit() throws Exception {
configRepo.checkin(goConfigRevision("something", "md5-1"));
RevCommit firstCommit = configRepo.revisions().iterator().next();
String actual = configRepo.findDiffBetweenTwoRevisions(firstCommit, null);
assertThat(actual, is(nullValue()));
}
@Test
public void shouldShowDiffForAnyTwoConfigMd5s() throws Exception {
configRepo.checkin(goConfigRevision(ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 33), "md5-1"));
configRepo.checkin(new GoConfigRevision(ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 60), "md5-2", "user-2", "13.2", new TimeProvider()));
configRepo.checkin(new GoConfigRevision(ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 55), "md5-3", "user-2", "13.2", new TimeProvider()));
String configChangesLine1 = "-<cruise schemaVersion='33'>";
String configChangesLine2 = "+<cruise schemaVersion='60'>";
String configChangesLine3 = "+<cruise schemaVersion='55'>";
String actual = configRepo.configChangesFor("md5-2", "md5-1");
assertThat(actual, containsString(configChangesLine1));
assertThat(actual, containsString(configChangesLine2));
actual = configRepo.configChangesFor("md5-3", "md5-1");
assertThat(actual, containsString(configChangesLine1));
assertThat(actual, containsString(configChangesLine3));
}
@Test
public void shouldShowDiffForAnyTwoCommitSHAs() throws Exception {
configRepo.checkin(goConfigRevision(ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 33), "md5-1"));
configRepo.checkin(new GoConfigRevision(ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 60), "md5-2", "user-2", "13.2", new TimeProvider()));
configRepo.checkin(new GoConfigRevision(ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 55), "md5-3", "user-2", "13.2", new TimeProvider()));
GoConfigRevisions commits = configRepo.getCommits(10, 0);
String firstCommitSHA = commits.get(2).getCommitSHA();
String secondCommitSHA = commits.get(1).getCommitSHA();
String thirdCommitSHA = commits.get(0).getCommitSHA();
String configChangesLine1 = "-<cruise schemaVersion='33'>";
String configChangesLine2 = "+<cruise schemaVersion='60'>";
String configChangesLine3 = "+<cruise schemaVersion='55'>";
String actual = configRepo.configChangesForCommits(secondCommitSHA, firstCommitSHA);
assertThat(actual, containsString(configChangesLine1));
assertThat(actual, containsString(configChangesLine2));
actual = configRepo.configChangesForCommits(thirdCommitSHA, firstCommitSHA);
assertThat(actual, containsString(configChangesLine1));
assertThat(actual, containsString(configChangesLine3));
}
@Test
public void shouldRemoveUnwantedDataFromDiff() throws Exception {
configRepo.checkin(goConfigRevision(ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 33), "md5-1"));
String configXml = ConfigFileFixture.configWithPipeline(ConfigFileFixture.SIMPLE_PIPELINE, 60);
configRepo.checkin(new GoConfigRevision(configXml, "md5-2", "user-2", "13.2", new TimeProvider()));
String configChangesLine1 = "-<cruise schemaVersion='33'>";
String configChangesLine2 = "+<cruise schemaVersion='60'>";
String actual = configRepo.configChangesFor("md5-2", "md5-1");
assertThat(actual, containsString(configChangesLine1));
assertThat(actual, containsString(configChangesLine2));
assertThat(actual, not(containsString("--- a/cruise-config.xml")));
assertThat(actual, not(containsString("+++ b/cruise-config.xml")));
}
@Test
public void shouldThrowExceptionIfRevisionNotFound() throws Exception {
configRepo.checkin(goConfigRevision("v1", "md5-1"));
try {
configRepo.configChangesFor("md5-1", "md5-not-found");
fail("Should have failed");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), is("There is no config version corresponding to md5: 'md5-not-found'"));
}
try {
configRepo.configChangesFor("md5-not-found", "md5-1");
fail("Should have failed");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), is("There is no config version corresponding to md5: 'md5-not-found'"));
}
}
@Test
public void shouldCreateBranchForARevCommit() throws Exception {
configRepo.checkin(goConfigRevision("something", "md5-1"));
RevCommit revCommit = configRepo.getCurrentRevCommit();
configRepo.createBranch("branch1", revCommit);
Ref branch = getBranch("branch1");
assertThat(branch, is(notNullValue()));
assertThat(branch.getObjectId(), is(revCommit.getId()));
}
@Test
public void shouldCommitIntoGivenBranch() throws Exception {
configRepo.checkin(goConfigRevision("something", "md5-1"));
RevCommit revCommitOnMaster = configRepo.getCurrentRevCommit();
String branchName = "branch1";
configRepo.createBranch(branchName, revCommitOnMaster);
String newConfigXML = "config-xml";
GoConfigRevision configRevision = new GoConfigRevision(newConfigXML, "md5", "user", "version", new TimeProvider());
RevCommit branchRevCommit = configRepo.checkinToBranch(branchName, configRevision);
assertThat(branchRevCommit, is(notNullValue()));
assertThat(getLatestConfigAt(branchName), is(newConfigXML));
assertThat(configRepo.getCurrentRevCommit(), is(revCommitOnMaster));
}
@Test
public void shouldMergeNewCommitOnBranchWithHeadWhenThereIsNoConflict() throws Exception {
String original = "first\nsecond\n";
String changeOnBranch = "first\nsecond\nthird\n";
String changeOnMaster = "1st\nsecond\n";
String oldMd5 = "md5-1";
configRepo.checkin(goConfigRevision(original, oldMd5));
configRepo.checkin(goConfigRevision(changeOnMaster, "md5-2"));
String mergedConfig = configRepo.getConfigMergedWithLatestRevision(goConfigRevision(changeOnBranch, "md5-3"), oldMd5);
assertThat(mergedConfig, is("1st\nsecond\nthird\n"));
}
@Test
public void shouldThrowExceptionWhenThereIsMergeConflict() throws Exception {
String original = "first\nsecond\n";
String nextUpdate = "1st\nsecond\n";
String latestUpdate = "2nd\nsecond\n";
configRepo.checkin(goConfigRevision(original, "md5-1"));
configRepo.checkin(goConfigRevision(nextUpdate, "md5-2"));
RevCommit currentRevCommitOnMaster = configRepo.getCurrentRevCommit();
try {
configRepo.getConfigMergedWithLatestRevision(goConfigRevision(latestUpdate, "md5-3"), "md5-1");
fail("should have bombed for merge conflict");
} catch (ConfigMergeException e) {
assertThat(e.getMessage(), is(ConfigFileHasChangedException.CONFIG_CHANGED_PLEASE_REFRESH));
}
List<Ref> branches = getAllBranches();
assertThat(branches.size(), is(1));
assertThat(branches.get(0).getName().endsWith("master"), is(true));
assertThat(configRepo.getCurrentRevCommit(), is(currentRevCommitOnMaster));
}
@Test
public void shouldBeOnMasterAndTemporaryBranchesDeletedAfterGettingMergeConfig() throws Exception {
String original = "first\nsecond\n";
String nextUpdate = "1st\nsecond\n";
String latestUpdate = "first\nsecond\nthird\n";
configRepo.checkin(goConfigRevision(original, "md5-1"));
configRepo.checkin(goConfigRevision(nextUpdate, "md5-2"));
RevCommit currentRevCommitOnMaster = configRepo.getCurrentRevCommit();
String mergedConfig = configRepo.getConfigMergedWithLatestRevision(goConfigRevision(latestUpdate, "md5-3"), "md5-1");
assertThat(mergedConfig, is("1st\nsecond\nthird\n"));
List<Ref> branches = getAllBranches();
assertThat(branches.size(), is(1));
assertThat(branches.get(0).getName().endsWith("master"), is(true));
assertThat(configRepo.getCurrentRevCommit(), is(currentRevCommitOnMaster));
}
@Test
public void shouldSwitchToMasterAndDeleteTempBranches() throws Exception, GitAPIException {
configRepo.checkin(goConfigRevision("v1", "md5-1"));
configRepo.createBranch(ConfigRepository.BRANCH_AT_HEAD, configRepo.getCurrentRevCommit());
configRepo.createBranch(ConfigRepository.BRANCH_AT_REVISION, configRepo.getCurrentRevCommit());
configRepo.git().checkout().setName(ConfigRepository.BRANCH_AT_REVISION).call();
assertThat(configRepo.git().getRepository().getBranch(), is(ConfigRepository.BRANCH_AT_REVISION));
assertThat(configRepo.git().branchList().call().size(), is(3));
configRepo.cleanAndResetToMaster();
assertThat(configRepo.git().getRepository().getBranch(), is("master"));
assertThat(configRepo.git().branchList().call().size(), is(1));
}
@Test
public void shouldCleanAndResetToMasterDuringInitialization() throws Exception {
configRepo.checkin(goConfigRevision("v1", "md5-1"));
configRepo.createBranch(ConfigRepository.BRANCH_AT_REVISION, configRepo.getCurrentRevCommit());
configRepo.git().checkout().setName(ConfigRepository.BRANCH_AT_REVISION).call();
assertThat(configRepo.git().getRepository().getBranch(), is(ConfigRepository.BRANCH_AT_REVISION));
new ConfigRepository(systemEnvironment).initialize();
assertThat(configRepo.git().getRepository().getBranch(), is("master"));
assertThat(configRepo.git().branchList().call().size(), is(1));
}
@Test
public void shouldCleanAndResetToMasterOnceMergeFlowIsComplete() throws Exception {
String original = "first\nsecond\n";
String changeOnBranch = "first\nsecond\nthird\n";
String changeOnMaster = "1st\nsecond\n";
String oldMd5 = "md5-1";
configRepo.checkin(goConfigRevision(original, oldMd5));
configRepo.checkin(goConfigRevision(changeOnMaster, "md5-2"));
configRepo.getConfigMergedWithLatestRevision(goConfigRevision(changeOnBranch, "md5-3"), oldMd5);
assertThat(configRepo.git().getRepository().getBranch(), is("master"));
assertThat(configRepo.git().branchList().call().size(), is(1));
}
@Test
public void shouldPerformGC() throws Exception {
configRepo.checkin(goConfigRevision("v1", "md5-1"));
Long numberOfLooseObjects = (Long) configRepo.git().gc().getStatistics().get("sizeOfLooseObjects");
assertThat(numberOfLooseObjects > 0l, is(true));
configRepo.garbageCollect();
numberOfLooseObjects = (Long) configRepo.git().gc().getStatistics().get("sizeOfLooseObjects");
assertThat(numberOfLooseObjects, is(0l));
}
@Test
public void shouldNotPerformGCWhenPeriodicGCIsTurnedOff() throws Exception {
when(systemEnvironment.get(SystemEnvironment.GO_CONFIG_REPO_PERIODIC_GC)).thenReturn(false);
configRepo.checkin(goConfigRevision("v1", "md5-1"));
Long numberOfLooseObjectsOld = (Long) configRepo.git().gc().getStatistics().get("sizeOfLooseObjects");
configRepo.garbageCollect();
Long numberOfLooseObjectsNow = (Long) configRepo.git().gc().getStatistics().get("sizeOfLooseObjects");
assertThat(numberOfLooseObjectsNow, is(numberOfLooseObjectsOld));
}
@Test
public void shouldGetLooseObjectCount() throws Exception {
configRepo.checkin(goConfigRevision("v1", "md5-1"));
Long numberOfLooseObjects = (Long) configRepo.git().gc().getStatistics().get("numberOfLooseObjects");
assertThat(configRepo.getLooseObjectCount(), is(numberOfLooseObjects));
}
@Test
public void shouldReturnNumberOfCommitsOnMaster() throws Exception {
configRepo.checkin(goConfigRevision("v1", "md5-1"));
configRepo.checkin(goConfigRevision("v2", "md5-2"));
assertThat(configRepo.commitCountOnMaster(), is(2L));
}
private GoConfigRevision goConfigRevision(String fileContent, String md5) {
return new GoConfigRevision(fileContent, md5, "user-1", "13.2", new TimeProvider());
}
private String getLatestConfigAt(String branchName) throws GitAPIException, IOException {
configRepo.git().checkout().setName(branchName).call();
String content = configRepo.getCurrentRevision().getContent();
configRepo.git().checkout().setName("master").call();
return content;
}
Ref getBranch(String branchName) throws GitAPIException {
List<Ref> branches = getAllBranches();
for (Ref branch : branches) {
if (branch.getName().endsWith(branchName)) {
return branch;
}
}
return null;
}
private List<Ref> getAllBranches() throws GitAPIException {
return configRepo.git().branchList().setListMode(ListBranchCommand.ListMode.ALL).call();
}
}