/* * 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.config.materials.mercurial; import com.thoughtworks.go.domain.materials.*; import com.thoughtworks.go.domain.materials.mercurial.HgCommand; import com.thoughtworks.go.domain.materials.mercurial.StringRevision; import com.thoughtworks.go.helper.HgTestRepo; import com.thoughtworks.go.helper.MaterialsMother; import com.thoughtworks.go.helper.TestRepo; import com.thoughtworks.go.util.*; import com.thoughtworks.go.util.command.ConsoleResult; import com.thoughtworks.go.util.command.InMemoryStreamConsumer; import org.hamcrest.Matchers; import org.hamcrest.core.StringContains; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; import java.io.IOException; import java.util.*; import static com.thoughtworks.go.util.DateUtils.parseISO8601; import static com.thoughtworks.go.util.JsonUtils.from; import static com.thoughtworks.go.util.command.ProcessOutputStreamConsumer.inMemoryConsumer; import static java.lang.String.format; import static org.hamcrest.Matchers.not; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.nullValue; import static org.hamcrest.core.StringContains.containsString; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class HgMaterialTest { private HgMaterial hgMaterial; private static final Date FROM = parseISO8601("2008-03-03 18:40:37 +0800"); private static final Date TO = parseISO8601("2008-03-03 23:13:50 +0800"); private HgTestRepo hgTestRepo; private File workingFolder; private InMemoryStreamConsumer outputStreamConsumer; private static final String LINUX_HG_094 = "Mercurial Distributed SCM (version 0.9.4)\n" + "\n" + "Copyright (C) 2005-2007 Matt Mackall <mpm@selenic.com> and others\n" + "This is free software; see the source for copying conditions. There is NO\n" + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."; private static final String LINUX_HG_101 = "Mercurial Distributed SCM (version 1.0.1)\n" + "\n" + "Copyright (C) 2005-2007 Matt Mackall <mpm@selenic.com> and others\n" + "This is free software; see the source for copying conditions. There is NO\n" + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."; private static final String LINUX_HG_10 = "Mercurial Distributed SCM (version 1.0)\n" + "\n" + "Copyright (C) 2005-2007 Matt Mackall <mpm@selenic.com> and others\n" + "This is free software; see the source for copying conditions. There is NO\n" + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."; private static final String WINDOWS_HG_OFFICAL_102 = "Mercurial Distributed SCM (version 1.0.2+20080813)\n" + "\n" + "Copyright (C) 2005-2008 Matt Mackall <mpm@selenic.com>; and others\n" + "This is free software; see the source for copying conditions. There is NO\n" + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."; private static final String WINDOWS_HG_TORTOISE = "Mercurial Distributed SCM (version 626cb86a6523+tortoisehg)"; private static final String REVISION_0 = "b61d12de515d82d3a377ae3aae6e8abe516a2651"; private static final String REVISION_1 = "35ff2159f303ecf986b3650fc4299a6ffe5a14e1"; private static final String REVISION_2 = "ca3ebb67f527c0ad7ed26b789056823d8b9af23f"; @Before public void setUp() throws Exception { hgTestRepo = new HgTestRepo("hgTestRepo1"); hgMaterial = MaterialsMother.hgMaterial(hgTestRepo.projectRepositoryUrl()); workingFolder = TestFileUtil.createTempFolder("workingFolder"); outputStreamConsumer = inMemoryConsumer(); } @After public void teardown() { FileUtil.deleteFolder(workingFolder); TestRepo.internalTearDown(); } @Test public void shouldRefreshWorkingFolderWhenRepositoryChanged() throws Exception { new HgCommand(null, workingFolder, "default", hgTestRepo.url().forCommandline(), null).clone(inMemoryConsumer(), hgTestRepo.url()); File testFile = createNewFileInWorkingFolder(); HgTestRepo hgTestRepo2 = new HgTestRepo("hgTestRepo2"); hgMaterial = MaterialsMother.hgMaterial(hgTestRepo2.projectRepositoryUrl()); hgMaterial.latestModification(workingFolder, new TestSubprocessExecutionContext()); String workingUrl = new HgCommand(null, workingFolder, "default", hgTestRepo.url().forCommandline(), null).workingRepositoryUrl().outputAsString(); assertThat(workingUrl, is(hgTestRepo2.projectRepositoryUrl())); assertThat(testFile.exists(), is(false)); } @Test public void shouldNotRefreshWorkingFolderWhenFileProtocolIsUsed() throws Exception { new HgCommand(null, workingFolder, "default", hgTestRepo.url().forCommandline(), null).clone(inMemoryConsumer(), hgTestRepo.url()); File testFile = createNewFileInWorkingFolder(); hgMaterial = MaterialsMother.hgMaterial("file://" + hgTestRepo.projectRepositoryUrl()); updateMaterial(hgMaterial, new StringRevision("0")); String workingUrl = new HgCommand(null, workingFolder, "default", hgTestRepo.url().forCommandline(), null).workingRepositoryUrl().outputAsString(); assertThat(workingUrl, is(hgTestRepo.projectRepositoryUrl())); assertThat(testFile.exists(), is(true)); } private void updateMaterial(HgMaterial hgMaterial, StringRevision revision) { hgMaterial.updateTo(outputStreamConsumer, workingFolder, new RevisionContext(revision), new TestSubprocessExecutionContext()); } @Test public void shouldNotRefreshWorkingDirectoryIfDefaultRemoteUrlDoesNotContainPasswordButMaterialUrlDoes() throws Exception { final HgMaterial material = new HgMaterial("http://user:pwd@domain:9999/path", null); final HgCommand hgCommand = mock(HgCommand.class); final ConsoleResult consoleResult = mock(ConsoleResult.class); when(consoleResult.outputAsString()).thenReturn("http://user@domain:9999/path"); when(hgCommand.workingRepositoryUrl()).thenReturn(consoleResult); assertFalse((Boolean) ReflectionUtil.invoke(material, "isRepositoryChanged", hgCommand)); } @Test public void shouldRefreshWorkingDirectoryIfUsernameInDefaultRemoteUrlIsDifferentFromOneInMaterialUrl() throws Exception { final HgMaterial material = new HgMaterial("http://some_new_user:pwd@domain:9999/path", null); final HgCommand hgCommand = mock(HgCommand.class); final ConsoleResult consoleResult = mock(ConsoleResult.class); when(consoleResult.outputAsString()).thenReturn("http://user:pwd@domain:9999/path"); when(hgCommand.workingRepositoryUrl()).thenReturn(consoleResult); assertTrue((Boolean) ReflectionUtil.invoke(material, "isRepositoryChanged", hgCommand)); } @Test public void shouldGetModifications() throws Exception { List<Modification> mods = hgMaterial.modificationsSince(workingFolder, new StringRevision(REVISION_0), new TestSubprocessExecutionContext()); assertThat(mods.size(), is(2)); Modification modification = mods.get(0); assertThat(modification.getRevision(), is(REVISION_2)); assertThat(modification.getModifiedFiles().size(), is(1)); } @Test public void shouldNotAppendDestinationDirectoryWhileFetchingModifications() throws Exception { hgMaterial.setFolder("dest"); hgMaterial.modificationsSince(workingFolder, new StringRevision(REVISION_0), new TestSubprocessExecutionContext()); assertThat(new File(workingFolder, "dest").exists(), is(false)); } @Test public void shouldGetModificationsBasedOnRevision() throws Exception { List<Modification> modificationsSince = hgMaterial.modificationsSince(workingFolder, new StringRevision(REVISION_0), new TestSubprocessExecutionContext()); assertThat(modificationsSince.get(0).getRevision(), is(REVISION_2)); assertThat(modificationsSince.get(1).getRevision(), is(REVISION_1)); assertThat(modificationsSince.size(), is(2)); } @Test public void shouldReturnLatestRevisionIfNoModificationsDetected() throws Exception { List<Modification> modification = hgMaterial.modificationsSince(workingFolder, new StringRevision(REVISION_2), new TestSubprocessExecutionContext()); assertThat(modification.isEmpty(), is(true)); } @Test public void shouldUpdateToSpecificRevision() throws Exception { updateMaterial(hgMaterial, new StringRevision("0")); File end2endFolder = new File(workingFolder, "end2end"); assertThat(end2endFolder.listFiles().length, is(3)); assertThat(outputStreamConsumer.getStdOut(), is(not(""))); updateMaterial(hgMaterial, new StringRevision("1")); assertThat(end2endFolder.listFiles().length, is(4)); } @Test public void shouldUpdateToDestinationFolder() throws Exception { hgMaterial.setFolder("dest"); updateMaterial(hgMaterial, new StringRevision("0")); File end2endFolder = new File(workingFolder, "dest/end2end"); assertThat(end2endFolder.exists(), is(true)); } @Test public void shouldLogRepoInfoToConsoleOutWithoutFolder() throws Exception { updateMaterial(hgMaterial, new StringRevision("0")); assertThat(outputStreamConsumer.getStdOut(), containsString( format("Start updating %s at revision %s from %s", "files", "0", hgMaterial.getUrl()))); } @Test public void shouldDeleteWorkingFolderWhenItIsNotAnHgRepository() throws Exception { File testFile = createNewFileInWorkingFolder(); hgMaterial.latestModification(workingFolder, new TestSubprocessExecutionContext()); assertThat(testFile.exists(), is(false)); } @Test public void shouldThrowExceptionWithUsefulInfoIfFailedToFindModifications() throws Exception { final String url = "/tmp/notExistDir"; hgMaterial = MaterialsMother.hgMaterial(url); try { hgMaterial.latestModification(workingFolder, new TestSubprocessExecutionContext()); fail("Should have thrown an exception when failed to clone from an invalid url"); } catch (Exception e) { assertThat(e.getMessage(), StringContains.containsString("abort: repository " + url + " not found!")); } } private File createNewFileInWorkingFolder() throws IOException { if (!workingFolder.exists()) { workingFolder.mkdirs(); } File testFile = new File(workingFolder, "not_in_hg_repository.txt"); testFile.createNewFile(); return testFile; } @Test public void shouldBeEqualWhenUrlSameForHgMaterial() throws Exception { final Material material = MaterialsMother.hgMaterials("url1", "hgdir").get(0); final Material anotherMaterial = MaterialsMother.hgMaterials("url1", "hgdir").get(0); assertThat(material.equals(anotherMaterial), is(true)); assertThat(anotherMaterial.equals(material), is(true)); } @Test public void shouldNotBeEqualWhenUrlDifferent() throws Exception { final Material material1 = MaterialsMother.hgMaterials("url1", "hgdir").get(0); final Material material2 = MaterialsMother.hgMaterials("url2", "hgdir").get(0); assertThat(material1.equals(material2), is(false)); assertThat(material2.equals(material1), is(false)); } @Test public void shouldNotBeEqualWhenTypeDifferent() throws Exception { final Material material = MaterialsMother.hgMaterials("url1", "hgdir").get(0); final Material svnMaterial = MaterialsMother.defaultSvnMaterialsWithUrl("url1").get(0); assertThat(material.equals(svnMaterial), is(false)); assertThat(svnMaterial.equals(material), is(false)); } @Test public void shouldBeEqual() throws Exception { final Material hgMaterial1 = MaterialsMother.hgMaterials("url1", "hgdir").get(0); final Material hgMaterial2 = MaterialsMother.hgMaterials("url1", "hgdir").get(0); assertThat(hgMaterial1.equals(hgMaterial2), is(true)); assertThat(hgMaterial1.hashCode(), is(hgMaterial2.hashCode())); } @Test public void shouldReturnTrueForLinuxDistributeLowerThanOneZero() throws Exception { assertThat(hgMaterial.isVersionOnedotZeorOrHigher(LINUX_HG_094), is(false)); } @Test public void shouldReturnTrueForLinuxDistributeHigerThanOneZero() throws Exception { assertThat(hgMaterial.isVersionOnedotZeorOrHigher(LINUX_HG_101), is(true)); } @Test public void shouldReturnTrueForLinuxDistributeEqualsOneZero() throws Exception { assertThat(hgMaterial.isVersionOnedotZeorOrHigher(LINUX_HG_10), is(true)); } @Test public void shouldReturnTrueForWindowsDistributionHigerThanOneZero() throws Exception { assertThat(hgMaterial.isVersionOnedotZeorOrHigher(WINDOWS_HG_OFFICAL_102), is(true)); } @Test(expected = Exception.class) public void shouldReturnFalseWhenVersionIsNotRecgonized() throws Exception { hgMaterial.isVersionOnedotZeorOrHigher(WINDOWS_HG_TORTOISE); } @Test public void shouldCheckConnection() { ValidationBean validation = hgMaterial.checkConnection(new TestSubprocessExecutionContext()); assertThat(validation.isValid(), is(true)); String notExistUrl = "http://notExisthost/hg"; hgMaterial = MaterialsMother.hgMaterial(notExistUrl); validation = hgMaterial.checkConnection(new TestSubprocessExecutionContext()); assertThat(validation.isValid(), is(false)); } @Test public void shouldReturnInvalidBeanWithRootCauseAsLowerVersionInstalled() throws Exception { ValidationBean validationBean = hgMaterial.handleException(new Exception(), LINUX_HG_094); assertThat(validationBean.isValid(), is(false)); assertThat(validationBean.getError(), containsString("Please install Mercurial Version 1.0 or above")); } @Test public void shouldReturnInvalidBeanWithRootCauseAsRepositoryURLIsNotFound() throws Exception { ValidationBean validationBean = hgMaterial.handleException(new Exception(), WINDOWS_HG_OFFICAL_102); assertThat(validationBean.isValid(), is(false)); assertThat(validationBean.getError(), containsString("not found!")); } @Test public void shouldReturnInvalidBeanWithRootCauseAsRepositoryURLIsNotFoundIfVersionIsNotKnown() throws Exception { ValidationBean validationBean = hgMaterial.handleException(new Exception(), WINDOWS_HG_TORTOISE); assertThat(validationBean.isValid(), is(false)); assertThat(validationBean.getError(), containsString("not found!")); } @Test public void shouldBeAbleToConvertToJson() throws Exception { Map<String, Object> json = new LinkedHashMap<>(); hgMaterial.toJson(json, new StringRevision("123")); JsonValue jsonValue = from(json); assertThat(jsonValue.getString("scmType"), is("Mercurial")); assertThat(new File(jsonValue.getString("location")), is(new File(hgTestRepo.projectRepositoryUrl()))); assertThat(jsonValue.getString("action"), is("Modified")); } @Test public void shouldRemoveTheForwardSlashAndApplyThePattern() throws Exception { Material material = MaterialsMother.hgMaterial(); assertThat(material.matches("a.doc", "/a.doc"), is(true)); assertThat(material.matches("/a.doc", "a.doc"), is(false)); } @Test public void shouldApplyThePatternDirectly() throws Exception { Material material = MaterialsMother.hgMaterial(); assertThat(material.matches("a.doc", "a.doc"), is(true)); } @Test // #3103 public void shouldParseComplexCommitMessage() throws Exception { String comment = "changeset: 8139:b1a0b0bbb4d1\n" + "branch: trunk\n" + "user: QYD\n" + "date: Tue Jun 30 14:56:37 2009 +0800\n" + "summary: add story #3001 - 'Pipelines should use the latest version of ..."; hgTestRepo.commitAndPushFile("SomeDocumentation.txt", comment); List<Modification> modification = hgMaterial.latestModification(workingFolder, new TestSubprocessExecutionContext()); assertThat(modification.size(), Matchers.is(1)); assertThat(modification.get(0).getComment(), is(comment)); } @Test public void shouldGenerateSqlCriteriaMapInSpecificOrder() throws Exception { Map<String, Object> map = hgMaterial.getSqlCriteria(); assertThat(map.size(), is(2)); Iterator<Map.Entry<String,Object>> iter = map.entrySet().iterator(); assertThat(iter.next().getKey(), is("type")); assertThat(iter.next().getKey(), is("url")); } /** * An hg abbreviated hash is 12 chars. See the hg documentation. * %h: short-form changeset hash (12 bytes of hexadecimal) */ @Test public void shouldtruncateHashTo12charsforAShortRevision() throws Exception { Material hgMaterial = new HgMaterial("file:///foo", null); assertThat(hgMaterial.getShortRevision("dc3d7e656831d1b203d8b7a63c4de82e26604e52"), is("dc3d7e656831")); assertThat(hgMaterial.getShortRevision("dc3d7e65683"), is("dc3d7e65683")); assertThat(hgMaterial.getShortRevision("dc3d7e6568312"), is("dc3d7e656831")); assertThat(hgMaterial.getShortRevision("24"), is("24")); assertThat(hgMaterial.getShortRevision(null), is(nullValue())); } @Test public void shouldNotDisplayPasswordInToString() { HgMaterial hgMaterial = new HgMaterial("https://user:loser@foo.bar/baz?quux=bang", null); assertThat(hgMaterial.toString(), not(containsString("loser"))); } @Test public void shouldGetLongDescriptionForMaterial(){ HgMaterial material = new HgMaterial("http://url/", "folder"); assertThat(material.getLongDescription(), is("URL: http://url/")); } @Test public void shouldFindTheBranchName(){ HgMaterial material = new HgMaterial("http://url/##foo", "folder"); assertThat(material.getBranch(), is("foo")); } @Test public void shouldSetDefaultAsBranchNameIfBranchNameIsNotSpecifiedInUrl(){ HgMaterial material = new HgMaterial("http://url/", "folder"); assertThat(material.getBranch(), is("default")); } @Test public void shouldMaskThePasswordInDisplayName(){ HgMaterial material = new HgMaterial("http://user:pwd@url##branch", "folder"); assertThat(material.getDisplayName(), is("http://user:******@url##branch")); } @Test public void shouldGetAttributesWithSecureFields() { HgMaterial material = new HgMaterial("http://username:password@hgrepo.com", null); Map<String, Object> attributes = material.getAttributes(true); assertThat(attributes.get("type"), is("mercurial")); Map<String, Object> configuration = (Map<String, Object>) attributes.get("mercurial-configuration"); assertThat(configuration.get("url"), is("http://username:password@hgrepo.com")); } @Test public void shouldGetAttributesWithoutSecureFields() { HgMaterial material = new HgMaterial("http://username:password@hgrepo.com", null); Map<String, Object> attributes = material.getAttributes(false); assertThat(attributes.get("type"), is("mercurial")); Map<String, Object> configuration = (Map<String, Object>) attributes.get("mercurial-configuration"); assertThat(configuration.get("url"), is("http://username:******@hgrepo.com")); } }