changeset 356:53b430731041 remote-run/TW-19984

TW-19984 ensure map file cleaned as well
author Dmitry Neverov <dmitry.neverov@jetbrains.com>
date Fri, 27 Jan 2012 19:34:03 +0400
parents 69d66e4f60e3
children 55e8947b54ee
files mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerImpl.java mercurial-server/src/META-INF/build-server-plugin-mercurial.xml mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Cleanup.java mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java mercurial-tests/lib/hamcrest-integration-1.1.jar mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CleanupTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CurrentThreadScheduledExecutor.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/DagFeaturesTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProviderTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/UnrelatedResitoriesTest.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/VcsRootBuilder.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommandTest.java mercurial-tests/src/testng.xml mercurial.ipr
diffstat 21 files changed, 422 insertions(+), 82 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Fri Jan 27 19:34:03 2012 +0400
@@ -6,4 +6,5 @@
   <bean id="hgPathProvider" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentHgPathProvider" />
   <bean id="hgDetector" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.HgDetector" />
   <bean id="pluginConfig" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentPluginConfigImpl"/>
+  <bean id="mirrorManager" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManagerImpl" />
 </beans>
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Fri Jan 27 19:34:03 2012 +0400
@@ -34,10 +34,11 @@
   private final MirrorManager myMirrorManager;
 
   public MercurialAgentSideVcsSupport(@NotNull final AgentPluginConfig pluginConfig,
-                                      @NotNull final HgPathProvider hgPathProvider) {
+                                      @NotNull final HgPathProvider hgPathProvider,
+                                      @NotNull final MirrorManager mirrorManager) {
     myConfig = pluginConfig;
     myHgPathProvider = hgPathProvider;
-    myMirrorManager = new MirrorManagerImpl(myConfig);
+    myMirrorManager = mirrorManager;
   }
 
   public IncludeRuleUpdater getUpdater(@NotNull final VcsRoot vcsRoot, @NotNull final CheckoutRules checkoutRules, @NotNull final String toVersion, @NotNull final File checkoutDirectory, @NotNull final AgentRunningBuild build, boolean cleanCheckoutRequested) throws VcsException {
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java	Fri Jan 27 19:34:03 2012 +0400
@@ -4,6 +4,7 @@
 
 import java.io.File;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author dmitry.neverov
@@ -35,4 +36,10 @@
    */
   public void forgetDir(@NotNull final File dir);
 
+  @NotNull
+  public Map<String, File> getMappings();
+
+  public void lockDir(@NotNull File dir);
+
+  public void unlockDir(@NotNull File dir);
 }
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerImpl.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerImpl.java	Fri Jan 27 19:34:03 2012 +0400
@@ -8,7 +8,11 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import static jetbrains.buildServer.util.FileUtil.isEmptyDir;
@@ -32,6 +36,8 @@
   private final Map<String, File> myMirrors = new HashMap<String, File>();
   private HashCalculator myHash = new StandartHash();
 
+  private final ConcurrentMap<String, Lock> myDirLocks = new ConcurrentHashMap<String, Lock>();
+
   public MirrorManagerImpl(@NotNull PluginConfig config) {
     myRootDir = config.getCachesDir();
     myMappingFile = new File(myRootDir, MAPPING_FILE_NAME);
@@ -69,6 +75,31 @@
   }
 
 
+  @NotNull
+  public Map<String, File> getMappings() {
+    return new HashMap<String, File>(myMirrors);
+  }
+
+  public void lockDir(@NotNull final File dir) {
+    lockFor(dir).lock();
+  }
+
+  public void unlockDir(@NotNull File dir) {
+    lockFor(dir).unlock();
+  }
+
+  private Lock lockFor(final File dir) {
+    String path = dir.getAbsolutePath();
+    Lock lock = myDirLocks.get(path);
+    if (lock == null) {
+      lock = new ReentrantLock();
+      Lock curLock = myDirLocks.putIfAbsent(path, lock);
+      if (curLock != null)
+        lock = curLock;
+    }
+    return lock;
+  }
+
   /**
    * Forget specified dir. After call to this method with non-empty dir,
    * all urls which were mapped to this dir will be mapped to another.
@@ -265,6 +296,24 @@
   }
 
 
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    myLock.readLock().lock();
+    try {
+      Iterator<Map.Entry<String, File>> iter = myMirrors.entrySet().iterator();
+      while (iter.hasNext()) {
+        Map.Entry<String, File> entry = iter.next();
+        sb.append("[").append(entry.getKey()).append("]").append("->").append(entry.getValue().getAbsolutePath());
+        if (iter.hasNext())
+          sb.append("\n");
+      }
+    } finally {
+      myLock.readLock().unlock();
+    }
+    return sb.toString();
+  }
+
   final static class StandartHash implements HashCalculator {
     public long calc(String value) {
       return Hash.calc(value);
--- a/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-server/src/META-INF/build-server-plugin-mercurial.xml	Fri Jan 27 19:34:03 2012 +0400
@@ -6,4 +6,5 @@
   <bean id="config" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerPluginConfigImpl" />
   <bean id="commandFactory" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.CommandFactoryImpl" />
   <bean id="hgPathProvider" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerHgPathProvider"/>
+  <bean id="mirrorManager" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManagerImpl" />
 </beans>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Cleanup.java	Fri Jan 27 19:34:03 2012 +0400
@@ -0,0 +1,68 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.Settings;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.util.filters.Filter;
+import jetbrains.buildServer.util.filters.FilterUtil;
+import jetbrains.buildServer.vcs.VcsManager;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author dmitry.neverov
+ */
+public class Cleanup implements Runnable {
+
+  private final VcsManager myVcsManager;
+  private final MirrorManager myMirrorManager;
+  private final HgPathProvider myHgPathProvider;
+
+  public Cleanup(@NotNull final VcsManager vcsManager,
+                 @NotNull final MirrorManager mirrorManager,
+                 @NotNull final HgPathProvider hgPathProvider) {
+    myVcsManager = vcsManager;
+    myMirrorManager = mirrorManager;
+    myHgPathProvider = hgPathProvider;
+  }
+
+  public void run() {
+    Map<String, File> allMirrorDirs = myMirrorManager.getMappings();
+    for (VcsRoot root : mercurialVcsRoots()) {
+      allMirrorDirs.remove(urlOf(root));
+    }
+    deleteDirs(allMirrorDirs.values());
+  }
+
+  private String urlOf(VcsRoot root) {
+    Settings s = new Settings(myHgPathProvider, root);
+    return s.getRepositoryUrlWithCredentials();
+  }
+
+  private Collection<VcsRoot> mercurialVcsRoots() {
+    List<VcsRoot> roots = new ArrayList<VcsRoot>(myVcsManager.getAllRegisteredVcsRoots());
+    FilterUtil.filterCollection(roots, new Filter<VcsRoot>() {
+      public boolean accept(@NotNull final VcsRoot root) {
+        return Constants.VCS_NAME.equals(root.getVcsName());
+      }
+    });
+    return roots;
+  }
+
+  private void deleteDirs(Collection<File> dirs) {
+    for (File dir : dirs) {
+      myMirrorManager.lockDir(dir);
+      try {
+        myMirrorManager.forgetDir(dir);
+        FileUtil.delete(dir);
+      } finally {
+        myMirrorManager.unlockDir(dir);
+      }
+    }
+  }
+}
--- a/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java	Fri Jan 27 19:34:03 2012 +0400
@@ -24,8 +24,6 @@
 import jetbrains.buildServer.util.EventDispatcher;
 import jetbrains.buildServer.util.FileUtil;
 import jetbrains.buildServer.util.StringUtil;
-import jetbrains.buildServer.util.filters.Filter;
-import jetbrains.buildServer.util.filters.FilterUtil;
 import jetbrains.buildServer.vcs.*;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import jetbrains.buildServer.vcs.patches.PatchBuilder;
@@ -37,10 +35,6 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
 
 import static com.intellij.openapi.util.text.StringUtil.isEmptyOrSpaces;
 
@@ -57,9 +51,8 @@
  */
 public class MercurialVcsSupport extends ServerVcsSupport implements LabelingSupport, VcsFileContentProvider, BranchSupport,
         CollectChangesBetweenRoots {
-  private ConcurrentMap<String, Lock> myWorkDirLocks= new ConcurrentHashMap<String, Lock>();
-  private VcsManager myVcsManager;
-  private File myDefaultWorkFolderParent;
+  private final VcsManager myVcsManager;
+  private final File myDefaultWorkFolderParent;
   private final MirrorManager myMirrorManager;
   private final ServerPluginConfig myConfig;
   private final HgPathProvider myHgPathProvider;
@@ -72,22 +65,18 @@
                              @NotNull final EventDispatcher<BuildServerListener> dispatcher,
                              @NotNull final ServerPluginConfig config,
                              @NotNull final HgPathProvider hgPathProvider,
-                             @NotNull final CommandFactory commandFactory) {
+                             @NotNull final CommandFactory commandFactory,
+                             @NotNull final MirrorManager mirrorManager) {
     myVcsManager = vcsManager;
     myConfig = config;
     myDefaultWorkFolderParent = myConfig.getCachesDir();
-    myMirrorManager = new MirrorManagerImpl(config);
+    myMirrorManager = mirrorManager;
     myHgPathProvider = hgPathProvider;
     myCommandFactory = commandFactory;
     dispatcher.addListener(new BuildServerAdapter() {
       @Override
       public void cleanupFinished() {
-        super.cleanupFinished();
-        server.getExecutor().submit(new Runnable() {
-          public void run() {
-            removeOldWorkFolders();
-          }
-        });
+        server.getExecutor().submit(new Cleanup(myVcsManager, myMirrorManager, myHgPathProvider));
       }
 
       @Override
@@ -700,11 +689,11 @@
   }
 
   private void lockWorkDir(@NotNull File workDir) {
-    getWorkDirLock(workDir).lock();
+    myMirrorManager.lockDir(workDir);
   }
 
   private void unlockWorkDir(@NotNull File workDir) {
-    getWorkDirLock(workDir).unlock();
+    myMirrorManager.unlockDir(workDir);
   }
 
   @Override
@@ -714,45 +703,6 @@
     return false;
   }
 
-  private Lock getWorkDirLock(final File workDir) {
-    String path = workDir.getAbsolutePath();
-    Lock lock = myWorkDirLocks.get(path);
-    if (lock == null) {
-      lock = new ReentrantLock();
-      Lock curLock = myWorkDirLocks.putIfAbsent(path, lock);
-      if (curLock != null) {
-        lock = curLock;
-      }
-    }
-    return lock;
-  }
-
-  private void removeOldWorkFolders() {
-    Set<File> workDirs = new HashSet<File>(myMirrorManager.getMirrors());
-
-    for (VcsRoot vcsRoot: getMercurialVcsRoots()) {
-      try {
-        Settings s = createSettings(vcsRoot);
-        File workingDir = getWorkingDir(s);
-        workDirs.remove(PathUtil.getCanonicalFile(workingDir));
-      } catch (VcsException e) {
-        Loggers.VCS.error(e);
-      }
-    }
-
-    deleteWithLocking(workDirs);
-  }
-
-  private Collection<VcsRoot> getMercurialVcsRoots() {
-    List<VcsRoot> res = new ArrayList<VcsRoot>(myVcsManager.getAllRegisteredVcsRoots());
-    FilterUtil.filterCollection(res, new Filter<VcsRoot>() {
-      public boolean accept(@NotNull final VcsRoot data) {
-        return getName().equals(data.getVcsName());
-      }
-    });
-    return res;
-  }
-
   public String label(@NotNull String label, @NotNull String version, @NotNull VcsRoot root, @NotNull CheckoutRules checkoutRules) throws VcsException {
     File tmpDir = null;
     try {
Binary file mercurial-tests/lib/hamcrest-integration-1.1.jar has changed
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutTest.java	Fri Jan 27 19:34:03 2012 +0400
@@ -68,7 +68,8 @@
       allowing(agentConfig).getParametersResolver(); will(returnValue(new HgPathResolver()));
     }});
 
-    myVcsSupport = new MercurialAgentSideVcsSupport(new AgentPluginConfigImpl(agentConfig), new AgentHgPathProvider(agentConfig));
+    final AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
+    myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, new AgentHgPathProvider(agentConfig), new MirrorManagerImpl(pluginConfig));
 
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{
@@ -81,8 +82,8 @@
 
   public void should_work_when_path_to_hg_is_property() throws Exception {
     VcsRootImpl root = new VcsRootBuilder()
-            .repository(LocalRepositoryUtil.prepareRepository(simpleRepo()).getAbsolutePath())
-            .hgPath(HG_PATH_REFERENCE).build();
+            .withUrl(LocalRepositoryUtil.prepareRepository(simpleRepo()).getAbsolutePath())
+            .withHgPath(HG_PATH_REFERENCE).build();
     testUpdate(root, "4:b06a290a363b", "cleanPatch1/after", new IncludeRule(".", ".", null));
   }
 
@@ -255,8 +256,8 @@
   //TW-19703
   public void should_work_when_repository_is_locked() throws Exception{
     VcsRootImpl root = new VcsRootBuilder()
-            .repository(LocalRepositoryUtil.prepareRepository(simpleRepo()).getAbsolutePath())
-            .hgPath(HG_PATH_REFERENCE).build();
+            .withUrl(LocalRepositoryUtil.prepareRepository(simpleRepo()).getAbsolutePath())
+            .withHgPath(HG_PATH_REFERENCE).build();
     File workingDir = doUpdate(root, "4:b06a290a363b");
 
     lockRepository(workingDir);
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentSideCheckoutWithSubreposTest.java	Fri Jan 27 19:34:03 2012 +0400
@@ -55,7 +55,8 @@
       allowing(agentConfig).getParametersResolver(); will(returnValue(new HgPathResolver()));
     }});
 
-    myVcsSupport = new MercurialAgentSideVcsSupport(new AgentPluginConfigImpl(agentConfig), new AgentHgPathProvider(agentConfig));
+    final AgentPluginConfigImpl pluginConfig = new AgentPluginConfigImpl(agentConfig);
+    myVcsSupport = new MercurialAgentSideVcsSupport(pluginConfig, new AgentHgPathProvider(agentConfig), new MirrorManagerImpl(pluginConfig));
 
     myLogger = myContext.mock(BuildProgressLogger.class);
     myContext.checking(new Expectations() {{
@@ -76,7 +77,7 @@
 
   public void subrepository_url_changed() throws Exception {
     VcsRootImpl root = new VcsRootBuilder()
-            .repository(myR1Dir.getAbsolutePath())
+            .withUrl(myR1Dir.getAbsolutePath())
             .build();
     doUpdate(root, "34017377d9c3");
     doUpdate(root, "d350e7209906");
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/BaseMercurialTestCase.java	Fri Jan 27 19:34:03 2012 +0400
@@ -46,12 +46,12 @@
 
   protected VcsRootImpl createVcsRoot(@NotNull String repPath) throws IOException {
     File repository = LocalRepositoryUtil.prepareRepository(repPath);
-    return new VcsRootBuilder().repository(repository.getAbsolutePath()).build();
+    return new VcsRootBuilder().withUrl(repository.getAbsolutePath()).build();
   }
 
   protected VcsRootImpl createVcsRoot(@NotNull String repPath, @NotNull String branchName) throws IOException {
     File repository = LocalRepositoryUtil.prepareRepository(repPath);
-    return new VcsRootBuilder().repository(repository.getAbsolutePath()).branch(branchName).build();
+    return new VcsRootBuilder().withUrl(repository.getAbsolutePath()).withBranch(branchName).build();
   }
 
   protected void cleanRepositoryAfterTest(@NotNull String repPath) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CleanupTest.java	Fri Jan 27 19:34:03 2012 +0400
@@ -0,0 +1,158 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.TempFiles;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.SVcsRoot;
+import jetbrains.buildServer.vcs.VcsManager;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.jetbrains.annotations.NotNull;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.testng.AssertJUnit.assertEquals;
+
+/**
+ * @author dmitry.neverov
+ */
+@Test
+public class CleanupTest {
+
+  private Mockery myContext;
+  private TempFiles myTempFiles;
+  private File myCachesDir;
+  private Cleanup myCleanup;
+  private MirrorManager myMirrorManager;
+  private VcsManager myVcsManager;
+  private long myRootId = 0;
+
+  @BeforeMethod
+  public void setUp() throws Exception {
+    myContext = new Mockery();
+    myTempFiles = new TempFiles();
+    myCachesDir = myTempFiles.createTempDir();
+
+    ServerPluginConfig config = new ServerPluginConfigBuilder().cachesDir(myCachesDir).build();
+    myMirrorManager = new MirrorManagerImpl(config);
+
+    myVcsManager = myContext.mock(VcsManager.class);
+    myCleanup = new Cleanup(myVcsManager, myMirrorManager, new ServerHgPathProvider(config));
+  }
+
+  @AfterMethod
+  public void tearDown() {
+    myTempFiles.cleanup();
+  }
+
+
+  public void cleanup_should_remove_all_directories_that_are_not_mirrors() throws IOException {
+    final String url1 = "http://some.org/repository1";
+    final String url2 = "http://some.org/repository2";
+    final String url3 = "http://some.org/repository3";
+    createDirFor(url1);
+    createDirFor(url2);
+    createDirFor(url3);
+    myContext.checking(new Expectations() {{
+      atLeast(1).of(myVcsManager).getAllRegisteredVcsRoots();
+      will(returnValue(asList(build(vcsRoot().withUrl(url1)), build(vcsRoot().withUrl(url2)))));
+    }});
+
+    myCleanup.run();
+
+    myContext.assertIsSatisfied();
+    assertEquals(2, directoriesInside(myCachesDir).size());
+    assertThat(myMirrorManager, knowsAboutAll(directoriesInside(myCachesDir)));
+    assertThat(mappingsFile(), containsOnly(directoriesInside(myCachesDir)));
+  }
+
+
+  private void createDirFor(@NotNull String url) {
+    myMirrorManager.getMirrorDir(url);
+  }
+
+  private VcsRootBuilder vcsRoot() {
+    return new VcsRootBuilder().withId(myRootId++);
+  }
+
+  private SVcsRoot build(@NotNull VcsRootBuilder builder) {
+    return builder.build(myContext);
+  }
+
+  private List<File> directoriesInside(@NotNull File dir) {
+    return asList(dir.listFiles(new FileFilter() {
+      public boolean accept(File f) {
+        return f.isDirectory();
+      }
+    }));
+  }
+
+  private List<File> mappingsFile() throws IOException {
+    File mappingFile = new File(myCachesDir, "map");
+    List<File> files = new ArrayList<File>();
+    for (String line : FileUtil.readFile(mappingFile)) {
+      String[] fields = line.split("=");
+      files.add(new File(mappingFile.getParentFile(), fields[1].trim()));
+    }
+    return files;
+  }
+
+
+  static MirrorManagerKnowsAboutDirectories knowsAboutAll(@NotNull List<File> dirs) {
+    return new MirrorManagerKnowsAboutDirectories(dirs);
+  }
+
+  static class MirrorManagerKnowsAboutDirectories extends TypeSafeMatcher<MirrorManager> {
+    private final List<File> myActualMirrorDirs;
+
+    MirrorManagerKnowsAboutDirectories(@NotNull List<File> actualMirrorDirs) {
+      myActualMirrorDirs = actualMirrorDirs;
+    }
+
+    @Override
+    public boolean matchesSafely(MirrorManager mirrorManager) {
+      List<File> knownMirrors = mirrorManager.getMirrors();
+      return knownMirrors.size() == myActualMirrorDirs.size() && knownMirrors.containsAll(myActualMirrorDirs);
+    }
+
+    public void describeTo(Description description) {
+      description.appendText("mirrorManager knows following directories: ");
+      for (File dir : myActualMirrorDirs) {
+        description.appendText(dir.getAbsolutePath()).appendText("\n");
+      }
+    }
+  }
+
+
+  private static <T> ContainsOnly<T> containsOnly(Collection<T> expected) {
+    return new ContainsOnly<T>(expected);
+  }
+
+  static class ContainsOnly<T> extends TypeSafeMatcher<Collection<T>> {
+    private final Collection<T> myExpected;
+
+    ContainsOnly(@NotNull Collection<T> expected) {
+      myExpected = expected;
+    }
+
+    @Override
+    public boolean matchesSafely(Collection<T> col) {
+      return col.size() == myExpected.size() && col.containsAll(myExpected);
+    }
+
+    public void describeTo(Description description) {
+      description.appendText(myExpected.toString());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/CurrentThreadScheduledExecutor.java	Fri Jan 27 19:34:03 2012 +0400
@@ -0,0 +1,80 @@
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.*;
+
+/**
+ * @author dmitry.neverov
+ */
+public class CurrentThreadScheduledExecutor implements ScheduledExecutorService {
+
+  public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public void shutdown() {
+    //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public List<Runnable> shutdownNow() {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public boolean isShutdown() {
+    return false;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public boolean isTerminated() {
+    return false;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+    return false;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public <T> Future<T> submit(Callable<T> task) {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public <T> Future<T> submit(Runnable task, T result) {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public Future<?> submit(Runnable task) {
+    task.run();
+    return null;
+  }
+
+  public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+    return null;  //To change body of implemented methods use File | Settings | File Templates.
+  }
+
+  public void execute(Runnable command) {
+    //To change body of implemented methods use File | Settings | File Templates.
+  }
+}
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/DagFeaturesTest.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/DagFeaturesTest.java	Fri Jan 27 19:34:03 2012 +0400
@@ -54,7 +54,7 @@
 
   //TW-17882
   public void should_detect_changes_from_named_branches() throws Exception {
-    VcsRootImpl root = new VcsRootBuilder().repository(myRepository).build();
+    VcsRootImpl root = new VcsRootBuilder().withUrl(myRepository).build();
 
     List<ModificationData> changes = myHg.collectChanges(root, "8:b6e2d176fe8e", "12:1e620196c4b6", CheckoutRules.DEFAULT);
     assertEquals(4, changes.size());
@@ -74,7 +74,7 @@
 
   //TW-17882
   public void should_report_changes_only_from_merged_named_branches() throws Exception {
-    VcsRootImpl root = new VcsRootBuilder().repository(myRepository).build();
+    VcsRootImpl root = new VcsRootBuilder().withUrl(myRepository).build();
     List<ModificationData> changes = myHg.collectChanges(root, "1e620196c4b6", "505c5b9d01e6", CheckoutRules.DEFAULT);
     assertEquals(2, changes.size());
     for (ModificationData change : changes) {
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProviderTest.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ServerHgPathProviderTest.java	Fri Jan 27 19:34:03 2012 +0400
@@ -47,7 +47,7 @@
 
 
   private Settings createSettings(HgPathProvider hgPathProvider) throws Exception {
-    VcsRootImpl root = new VcsRootBuilder().hgPath(myVcsRootHgPath).build();
+    VcsRootImpl root = new VcsRootBuilder().withHgPath(myVcsRootHgPath).build();
     return new Settings(hgPathProvider, root);
   }
 
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/UnrelatedResitoriesTest.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/UnrelatedResitoriesTest.java	Fri Jan 27 19:34:03 2012 +0400
@@ -45,7 +45,7 @@
 
     myRepositoryLocation = myTempFiles.createTempDir();
     copyRepository(new File("mercurial-tests/testData/rep1"));
-    myRoot = new VcsRootBuilder().repository(myRepositoryLocation.getCanonicalPath()).build();
+    myRoot = new VcsRootBuilder().withUrl(myRepositoryLocation.getCanonicalPath()).build();
   }
 
   @AfterMethod
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Util.java	Fri Jan 27 19:34:03 2012 +0400
@@ -43,6 +43,6 @@
       allowing(server).getExecutor(); will(returnValue(executor));
     }});
     EventDispatcher<BuildServerListener> dispatcher = EventDispatcher.create(BuildServerListener.class);
-    return new MercurialVcsSupport(vcsManager, server, dispatcher, config, new ServerHgPathProvider(config), commandFactory);
+    return new MercurialVcsSupport(vcsManager, server, dispatcher, config, new ServerHgPathProvider(config), commandFactory, new MirrorManagerImpl(config));
   }
 }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/VcsRootBuilder.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/VcsRootBuilder.java	Fri Jan 27 19:34:03 2012 +0400
@@ -1,7 +1,10 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial;
 
+import jetbrains.buildServer.vcs.SVcsRoot;
 import jetbrains.buildServer.vcs.impl.VcsRootImpl;
 import org.jetbrains.annotations.NotNull;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
 
 import java.io.IOException;
 
@@ -28,37 +31,53 @@
   }
 
 
-  public VcsRootBuilder repository(@NotNull String repository) {
+  public SVcsRoot build(Mockery context) {
+    final SVcsRoot root = context.mock(SVcsRoot.class, "SVcsRoot" + myRootId);
+    context.checking(new Expectations() {{
+      allowing(root).getVcsName(); will(returnValue(Constants.VCS_NAME));
+      allowing(root).getProperty(with(Constants.REPOSITORY_PROP)); will(returnValue(myRepository));
+      allowing(root).getProperty(with(Constants.HG_COMMAND_PATH_PROP)); will(returnValue(myHgPath));
+      allowing(root).getProperty(with(Constants.BRANCH_NAME_PROP)); will(returnValue(myBranch));
+      allowing(root).getProperty(with(Constants.SERVER_CLONE_PATH_PROP)); will(returnValue(null));
+      allowing(root).getProperty(with(Constants.USERNAME)); will(returnValue(myUsername));
+      allowing(root).getProperty(with(Constants.PASSWORD)); will(returnValue(myPassword));
+      allowing(root).getProperty(with(Constants.UNCOMPRESSED_TRANSFER)); will(returnValue(null));
+    }});
+    return root;
+  }
+
+
+  public VcsRootBuilder withUrl(@NotNull String repository) {
     myRepository = repository;
     return this;
   }
 
 
-  public VcsRootBuilder username(@NotNull String username) {
+  public VcsRootBuilder withUserName(@NotNull String username) {
     myUsername = username;
     return this;
   }
 
 
-  public VcsRootBuilder password(@NotNull String password) {
+  public VcsRootBuilder withPassword(@NotNull String password) {
     myPassword = password;
     return this;
   }
 
 
-  public VcsRootBuilder branch(@NotNull String branch) {
+  public VcsRootBuilder withBranch(@NotNull String branch) {
     myBranch = branch;
     return this;
   }
 
 
-  public VcsRootBuilder rootId(long rootId) {
+  public VcsRootBuilder withId(long rootId) {
     myRootId = rootId;
     return this;
   }
 
 
-  public VcsRootBuilder hgPath(String hgPath) {
+  public VcsRootBuilder withHgPath(String hgPath) {
     myHgPath = hgPath;
     return this;
   }
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommandTest.java	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/VersionCommandTest.java	Fri Jan 27 19:34:03 2012 +0400
@@ -14,7 +14,7 @@
 public class VersionCommandTest extends TestCase {
 
   public void test() throws Exception {
-    VcsRootImpl root = new VcsRootBuilder().repository("some/repository").build();
+    VcsRootImpl root = new VcsRootBuilder().withUrl("some/repository").build();
     ServerPluginConfig config = new ServerPluginConfigBuilder().build();
     Settings settings = new Settings(new ServerHgPathProvider(config), root);
     VersionCommand versionCommand = new VersionCommand(settings, new File(".."));
--- a/mercurial-tests/src/testng.xml	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial-tests/src/testng.xml	Fri Jan 27 19:34:03 2012 +0400
@@ -20,6 +20,7 @@
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.ServerHgPathProviderTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.DagFeaturesTest"/>
       <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.UnrelatedResitoriesTest"/>
+      <class name="jetbrains.buildServer.buildTriggers.vcs.mercurial.CleanupTest"/>
     </classes>
   </test>
 </suite>
--- a/mercurial.ipr	Fri Jan 27 15:27:49 2012 +0400
+++ b/mercurial.ipr	Fri Jan 27 19:34:03 2012 +0400
@@ -404,9 +404,12 @@
         <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/hamcrest-core-1.1.jar!/" />
         <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/jmock-2.5.1.jar!/" />
         <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/jmock-SNAPSHOT.jar!/" />
+        <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/hamcrest-integration-1.1.jar!/" />
       </CLASSES>
       <JAVADOC />
-      <SOURCES />
+      <SOURCES>
+        <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/hamcrest-integration-1.1.jar!/" />
+      </SOURCES>
     </library>
     <library name="JUnit">
       <CLASSES>