view mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java @ 976:7bf4d943d5bb

Update copyright
author pavel.sher
date Mon, 22 Jan 2018 11:39:20 +0100
parents 3e7fde939ccc
children 10dc26b32c35
line wrap: on
line source
/*
 * Copyright 2000-2018 JetBrains s.r.o.
 *
 * 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 jetbrains.buildServer.buildTriggers.vcs.mercurial;

import jetbrains.buildServer.TestInternalProperties;
import jetbrains.buildServer.agent.*;
import jetbrains.buildServer.agent.vcs.AgentCheckoutAbility;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsForRootImpl;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.TestCommandSettingsFactory;
import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtensionManager;
import jetbrains.buildServer.util.FileUtil;
import jetbrains.buildServer.util.TestFor;
import jetbrains.buildServer.vcs.*;
import jetbrains.buildServer.vcs.impl.VcsRootImpl;
import org.hamcrest.CoreMatchers;
import org.jetbrains.annotations.NotNull;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

import static java.util.Arrays.asList;
import static jetbrains.buildServer.buildTriggers.vcs.mercurial.Util.copyRepository;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.text.StringContains.containsString;

/**
 * @author Pavel.Sher
 *         Date: 30.07.2008
 */
@Test
public class AgentSideCheckoutTest extends BaseMercurialPatchTestCase {

  final static String HG_PATH_REFERENCE = "%" + HgDetector.AGENT_HG_PATH_PROPERTY + "%";
  private MercurialAgentSideVcsSupport myVcsSupport;
  private File myWorkDir;
  private File myMirrorsRootDir;
  private Mockery myContext;
  private BuildProgressLogger myLogger;
  private int myBuildCounter = 0;

  @Override
  @BeforeMethod
  protected void setUp() throws Exception {
    super.setUp();
    TestInternalProperties.init();

    myContext = new Mockery();

    myMirrorsRootDir = myTempFiles.createTempDir();

    final BuildAgentConfiguration agentConfig = myContext.mock(BuildAgentConfiguration.class);
    myContext.checking(new Expectations() {{
      allowing(agentConfig).getCacheDirectory("mercurial"); will(returnValue(myMirrorsRootDir));
      allowing(agentConfig).getTempDirectory(); will(returnValue(myTempFiles.createTempDir()));
      allowing(agentConfig).getParametersResolver(); will(returnValue(new HgPathResolver()));
    }});

    final AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
    myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig,
            new MirrorManagerImpl(pluginConfig),
            new AgentRepoFactory(pluginConfig, new CommandSettingsForRootImpl(new TestCommandSettingsFactory(), new ExtensionsWeaver(), new CommandlineViaFileWrapperWeaver()), new AgentHgPathProvider(agentConfig)),
            new MercurialExtensionManager());

    myLogger = myContext.mock(FlowLogger.class);
    myContext.checking(new Expectations() {{
      allowing(myLogger).message(with(any(String.class)));
      allowing(myLogger).getFlowLogger(with(any(String.class))); will(returnValue(myLogger));
      allowing(myLogger).activityStarted(with(any(String.class)), with(any(String.class)));
      allowing(myLogger).activityFinished(with(any(String.class)), with(any(String.class)));
      allowing(myLogger).getFlowLogger(with(any(String.class))); will(returnValue(myLogger));
    }});

    myWorkDir = myTempFiles.createTempDir();

  }

  public void should_work_when_path_to_hg_is_property() throws Exception {
    VcsRootImpl root = new VcsRootBuilder()
            .withUrl(copyRepository(myTempFiles, simpleRepo()).getAbsolutePath())
            .withHgPath(HG_PATH_REFERENCE).build();
    testUpdate(root, "b06a290a363b", "cleanPatch1/after", new IncludeRule(".", ".", null));
  }

  public void auto_checkout_when_hg_client_not_found() throws Exception {
    VcsRootImpl root = new VcsRootBuilder().withUrl(copyRepository(myTempFiles, simpleRepo()).getAbsolutePath()).withHgPath("invalid_hg_path").build();

    AgentCheckoutAbility agentCheckoutAbility = myVcsSupport.canCheckout(root, CheckoutRules.DEFAULT, myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++));

    assertThat(agentCheckoutAbility.getCanNotCheckoutReason().getType(), CoreMatchers.equalTo(AgentCanNotCheckoutReason.NO_VCS_CLIENT));
  }

  public void auto_checkout_when_unsupported_include_rule_is_used() throws Exception {
    VcsRootImpl root = createVcsRoot(simpleRepo());

    AgentCheckoutAbility agentCheckoutAbility = myVcsSupport.canCheckout(root, new CheckoutRules("+:subdir=>subdir2"), myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++));

    assertThat(agentCheckoutAbility.getCanNotCheckoutReason().getType(), CoreMatchers.equalTo(AgentCanNotCheckoutReason.NOT_SUPPORTED_CHECKOUT_RULES));
    assertThat(agentCheckoutAbility.getCanNotCheckoutReason().getDetails(), containsString("Invalid include rule: subdir=>subdir2"));
  }


  @DataProvider
  public static Object[][] severalRootsSetups() throws Exception {
    return new Object[][]{
            new Object[]{new Setup()
                    .setShouldFail(true)
            },
            new Object[]{new Setup()
                    .setCheckoutRules1("+:.=>dir")
                    .setCheckoutRules2("+:.=>dir")
                    .setShouldFail(true)
            },
            new Object[]{new Setup()
                    .setCheckoutRules1("+:.=>dir1")
                    .setCheckoutRules2("+:.=>dir2\n+:.=>dir1")
                    .setShouldFail(true)
            },
            new Object[]{new Setup()
                    .setCheckoutRules2("+:.=>dir1")
                    .setCheckoutRules2("+:.=>dir2")
                    .setShouldFail(false)
            }
    };
  }


  @TestFor(issues = "TW-49811")
  @Test(dataProvider = "severalRootsSetups")
  public void auto_checkout_many_roots(@NotNull Setup setup) throws Exception {
    VcsRootImpl root1 = new VcsRootBuilder().withId(1).withUrl("http://some.org/repo1").build();
    VcsRootImpl root2 = new VcsRootBuilder().withId(2).withUrl("http://some.org/repo2").build();

    CheckoutRules rules1 = new CheckoutRules(setup.getCheckoutRules1());
    CheckoutRules rules2 = new CheckoutRules(setup.getCheckoutRules2());
    AgentRunningBuild build = myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++);
    myContext.checking(new Expectations() {{
      allowing(build).getVcsRootEntries();
      will(returnValue(asList(new VcsRootEntry(root1, rules1), new VcsRootEntry(root2, rules2))));
    }});

    AgentCheckoutAbility canCheckout1 = myVcsSupport.canCheckout(root1, rules1, build);
    AgentCheckoutAbility canCheckout2 = myVcsSupport.canCheckout(root2, rules2, build);

    if (setup.isShouldFail()) {
      assertThat(canCheckout1.getCanNotCheckoutReason().getDetails(), CoreMatchers.equalTo(
              "Cannot checkout VCS root '" + root1.getName() + "' into the same directory as VCS root '" + root2.getName() + "'"));
      assertThat(canCheckout2.getCanNotCheckoutReason().getDetails(), CoreMatchers.equalTo(
              "Cannot checkout VCS root '" + root2.getName() + "' into the same directory as VCS root '" + root1.getName() + "'"));
    } else {
      assertNull(canCheckout1.getCanNotCheckoutReason());
      assertNull(canCheckout2.getCanNotCheckoutReason());
    }
  }


  @TestFor(issues = "TW-49811")
  public void auto_checkout_many_roots_ignore_broken() throws Exception {
    //root1 and root2 have intersecting checkout dir (dir1), but
    //root2 also has an unsupported checkout rule; we should ignore
    //root2 during canCheckout() call for root1
    VcsRootImpl root1 = new VcsRootBuilder().withId(1).withUrl("http://some.org/repo1").build();
    VcsRootImpl root2 = new VcsRootBuilder().withId(2).withUrl("http://some.org/repo2").build();

    CheckoutRules rules1 = new CheckoutRules("+:.=>dir1");
    CheckoutRules rules2 = new CheckoutRules("+:.=>dir1\n+:dir2=>dir3");
    AgentRunningBuild build = myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++);
    myContext.checking(new Expectations() {{
      allowing(build).getVcsRootEntries();
      will(returnValue(asList(new VcsRootEntry(root1, rules1), new VcsRootEntry(root2, rules2))));
    }});

    AgentCheckoutAbility canCheckout1 = myVcsSupport.canCheckout(root1, rules1, build);
    AgentCheckoutAbility canCheckout2 = myVcsSupport.canCheckout(root2, rules2, build);
    assertNull(canCheckout1.getCanNotCheckoutReason());
    assertThat(canCheckout2.getCanNotCheckoutReason().getType(), CoreMatchers.equalTo(AgentCanNotCheckoutReason.NOT_SUPPORTED_CHECKOUT_RULES));
    assertThat(canCheckout2.getCanNotCheckoutReason().getDetails(), containsString("Invalid include rule: dir2=>dir3"));
  }


  public void checkout_on_agent() throws IOException, VcsException {
    testUpdate(createVcsRoot(simpleRepo()), "4:b06a290a363b", "cleanPatch1/after", new IncludeRule(".", ".", null));
  }

  public void checkout_on_agent_include_rule_with_mapping() throws IOException, VcsException {
    testUpdate(createVcsRoot(simpleRepo()), "4:b06a290a363b", "cleanPatch1/after", new IncludeRule("+:.", "subdir", null));
  }

  @TestFor(issues = "TW-19761")
  public void include_subdir_is_not_supported() throws Exception {
    try {
      testUpdate(createVcsRoot(simpleRepo()), "4:b06a290a363b", "cleanPatch1/after", new IncludeRule("+:subdir", ".", null));
      fail("should fail");
    } catch (VcsException e) {
      assertTrue(e.getMessage().contains("Invalid include rule"));
    }
  }

  private void testUpdate(final VcsRoot vcsRoot, String version, String expected, final IncludeRule includeRule) throws VcsException, IOException {
    File workDir = doUpdate(vcsRoot, version, includeRule);

    checkWorkingDir(expected, workDir);
  }

  private void checkWorkingDir(final String expected, final File workDir) throws IOException {
    FileUtil.delete(new File(workDir, ".hg"));
    checkDirectoriesAreEqual(new File(getTestDataPath(), expected), workDir);
  }

  private File doUpdate(@NotNull final VcsRoot root, @NotNull final String version, int timeoutSeconds) throws Exception {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Future<File> future = executor.submit(new Callable<File>() {
      public File call() throws Exception {
        return doUpdate(root, version);
      }
    });
    executor.shutdown();
    executor.awaitTermination(timeoutSeconds, TimeUnit.SECONDS);
    if (!future.isDone())
      fail("Update failed due to timeout");
    return future.get();
  }

  protected File doUpdate(@NotNull VcsRoot root, @NotNull String version) throws VcsException {
    return doUpdate(root, version, IncludeRule.createDefaultInstance());
  }

  private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule) throws VcsException {
    return doUpdate(vcsRoot, version, includeRule, false);
  }

  private File doUpdate(final VcsRoot vcsRoot, final String version, final IncludeRule includeRule, boolean useLocalMirrors) throws VcsException {
    File actualWorkDir = new File(myWorkDir, includeRule.getTo());
    final Map<String, String> sharedConfigParameters = new HashMap<String, String>();
    sharedConfigParameters.put("teamcity.hg.use.local.mirrors", String.valueOf(useLocalMirrors));
    final AgentRunningBuild build = myContext.mock(AgentRunningBuild.class, "build" + myBuildCounter++);
    myContext.checking(new Expectations() {{
      allowing(build).getBuildLogger(); will(returnValue(myLogger));
      allowing(build).getSharedConfigParameters(); will(returnValue(sharedConfigParameters));
    }});
    myVcsSupport.getUpdater(vcsRoot, new CheckoutRules(""), version, myWorkDir, build, false).process(includeRule, actualWorkDir);

    File hgDir = new File(actualWorkDir, ".hg");
    assertTrue(hgDir.isDirectory());
    return actualWorkDir;
  }

  public void checkout_on_agent_from_branch() throws IOException, VcsException {
    testUpdate(createVcsRoot(simpleRepo(), "test_branch"), "7:376dcf05cd2a", "patch3/after", new IncludeRule(".", ".", null));
  }

  public void update_on_agent() throws IOException, VcsException {
    VcsRoot vcsRoot = createVcsRoot(simpleRepo());
    doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", ".", null));
    File workDir = doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", ".", null));

    checkWorkingDir("patch1/after", workDir);
  }

  public void update_on_agent_with_include_rule() throws IOException, VcsException {
    VcsRoot vcsRoot = createVcsRoot(simpleRepo());
    doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", "subdir", null));
    File workDir = doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", "subdir", null));

    checkWorkingDir("patch1/after", workDir);
  }

  public void update_on_agent_from_branch() throws IOException, VcsException {
    VcsRoot vcsRoot = createVcsRoot(simpleRepo(), "test_branch");
    doUpdate(vcsRoot, "7:376dcf05cd2a", new IncludeRule(".", ".", null));
    File workDir = doUpdate(vcsRoot, "8:04c3ae4c6312", new IncludeRule(".", ".", null));

    checkWorkingDir("patch4/after", workDir);
  }

  public void by_default_local_mirror_not_created() throws IOException, VcsException {
    List<File> mirrors = FileUtil.getSubDirectories(myMirrorsRootDir);
    assertTrue(mirrors.isEmpty());
    VcsRoot root = createVcsRoot(simpleRepo());
    doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null));
    mirrors = FileUtil.getSubDirectories(myMirrorsRootDir);
    //though some dirs are created - they are empty => there were no clones into local mirrors
    for (File mirror : mirrors) {
      assertTrue(FileUtil.getSubDirectories(mirror).isEmpty());
    }
  }

  public void local_mirror_is_created() throws IOException, VcsException {
    List<File> mirrors = FileUtil.getSubDirectories(myMirrorsRootDir);
    assertTrue(mirrors.isEmpty());
    VcsRoot root = createVcsRoot(simpleRepo());
    doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true);
    mirrors = FileUtil.getSubDirectories(myMirrorsRootDir);
    assertEquals(1, mirrors.size());
    File mirror = mirrors.get(0);
    File dotHg = new File(mirror, ".hg");
    assertTrue(dotHg.exists());
    File hgrc = new File(dotHg, "hgrc");
    String hgrcContent = FileUtil.readText(hgrc);
    assertTrue(hgrcContent.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));
  }

  private void new_repository_is_cloned_from_local_mirror() throws IOException, VcsException {
    VcsRoot root = createVcsRoot(simpleRepo());
    File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true);
    File mirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0);
    File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc");
    String hgrcContent = FileUtil.readText(hgrc);
    assertTrue(hgrcContent.contains("default = " + mirrorDir.getCanonicalPath()));
  }

  private void repository_cloned_from_remote_start_cloning_from_local_mirror() throws IOException, VcsException {
    VcsRoot root = createVcsRoot(simpleRepo());
    //clone from remote repository
    File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null));
    String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc"));

    File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true);
    File newMirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0);
    String hgrcContent2 = FileUtil.readText(new File(workingDir2, ".hg" + File.separator + "hgrc"));
    assertFalse(hgrcContent2.equals(hgrcContent));//repository settings are changed
    assertTrue(hgrcContent2.contains("default = " + newMirrorDir.getCanonicalPath()));//now it clones from local mirror
  }

  private void repository_cloned_from_local_mirror_start_cloning_from_remote() throws IOException, VcsException {
    VcsRoot root = createVcsRoot(simpleRepo());
    //clone from remote repository
    File workingDir = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null), true);
    String hgrcContent = FileUtil.readText(new File(workingDir, ".hg" + File.separator + "hgrc"));
    File newMirrorDir = FileUtil.getSubDirectories(myMirrorsRootDir).get(0);
    assertTrue(hgrcContent.contains("default = " + newMirrorDir.getCanonicalPath()));//now it clones from local mirror

    File workingDir2 = doUpdate(root, "3:9522278aa38d", new IncludeRule(".", ".", null));
    String hgrcContent2 = FileUtil.readText(new File(workingDir2, ".hg" + File.separator + "hgrc"));
    assertFalse(hgrcContent2.equals(hgrcContent));//repository settings are changed
    assertTrue(hgrcContent2.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));//now it clones from remote
  }

  /**
   * TW-15984
   */
  public void should_be_able_to_clone_into_non_empty_dir() throws IOException, VcsException {
    VcsRoot vcsRoot = createVcsRoot(simpleRepo());
    doUpdate(vcsRoot, "3:9522278aa38d", new IncludeRule(".", "subdir", null));
    doUpdate(vcsRoot, "4:b06a290a363b", new IncludeRule(".", ".", null));
  }

  public void cloned_repo_should_contains_default_parameter_in_hgrc() throws VcsException, IOException {
    VcsRoot root = createVcsRoot(simpleRepo());
    File workingDir = doUpdate(root, "4:b06a290a363b", new IncludeRule(".", ".", null));
    File hgrc = new File(workingDir, ".hg" + File.separator + "hgrc");
    String hgrcContent = FileUtil.readText(hgrc);
    assertTrue(hgrcContent.contains("default = " + root.getProperty(Constants.REPOSITORY_PROP)));
  }

  @TestFor(issues = "TW-19703")
  public void should_work_when_repository_is_locked() throws Exception{
    VcsRootImpl root = new VcsRootBuilder()
            .withUrl(copyRepository(myTempFiles, simpleRepo()).getAbsolutePath())
            .withHgPath(HG_PATH_REFERENCE).build();
    File workingDir = doUpdate(root, "4:b06a290a363b");

    lockRepository(workingDir);
    doUpdate(root, "6:b9deb9a1c6f4", 10);

    lockWorkingDir(workingDir);
    doUpdate(root, "10:9c6a6b4aede0", 10);
  }

  private void lockRepository(@NotNull File workingDir) {
    File lock = new File(workingDir, ".hg" + File.separator + "store" + File.separator + "lock");
    FileUtil.writeFile(lock, "");
  }

  private void lockWorkingDir(@NotNull File workingDir) {
    File lock = new File(workingDir, ".hg" + File.separator + "wlock");
    FileUtil.writeFile(lock, "");
  }

  protected String getTestDataPath() {
    return "mercurial-tests/testData";
  }

  private static class Setup {
    private String myCheckoutRules1 = CheckoutRules.DEFAULT.getAsString();
    private String myCheckoutRules2 = CheckoutRules.DEFAULT.getAsString();
    private boolean myShouldFail;

    @NotNull
    public String getCheckoutRules1() {
      return myCheckoutRules1;
    }

    @NotNull
    public Setup setCheckoutRules1(@NotNull String checkoutRules1) {
      myCheckoutRules1 = checkoutRules1;
      return this;
    }

    @NotNull
    public String getCheckoutRules2() {
      return myCheckoutRules2;
    }

    @NotNull
    public Setup setCheckoutRules2(@NotNull String checkoutRules2) {
      myCheckoutRules2 = checkoutRules2;
      return this;
    }

    public boolean isShouldFail() {
      return myShouldFail;
    }

    @NotNull
    public Setup setShouldFail(boolean shouldFail) {
      myShouldFail = shouldFail;
      return this;
    }

    @Override
    public String toString() {
      return "rules1: '" + myCheckoutRules1 + "', rules2: '" + myCheckoutRules2 + "'";
    }
  }
}