text: /*
   * Copyright 2000-2012 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 com.intellij.testFramework.vcs;
  
  import com.intellij.execution.process.ProcessOutput;
  import com.intellij.openapi.application.ApplicationManager;
  import com.intellij.openapi.application.Result;
  import com.intellij.openapi.command.WriteCommandAction;
  import com.intellij.openapi.components.ProjectComponent;
  import com.intellij.openapi.diff.LineTokenizer;
  import com.intellij.openapi.project.Project;
  import com.intellij.openapi.util.Ref;
  import com.intellij.openapi.util.io.FileUtil;
  import com.intellij.openapi.vcs.*;
  import com.intellij.openapi.vcs.changes.*;
  import com.intellij.openapi.vfs.CharsetToolkit;
  import com.intellij.openapi.vfs.LocalFileSystem;
  import com.intellij.openapi.vfs.VirtualFile;
  import com.intellij.testFramework.UsefulTestCase;
  import com.intellij.testFramework.builders.EmptyModuleFixtureBuilder;
  import com.intellij.testFramework.fixtures.IdeaProjectTestFixture;
  import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory;
  import com.intellij.testFramework.fixtures.TestFixtureBuilder;
  import com.intellij.util.Processor;
  import javax.annotation.Nullable;
  import org.junit.Assert;
  
  import java.io.File;
  import java.io.IOException;
  import java.util.*;
  import java.util.concurrent.atomic.AtomicReference;
  
  import static junit.framework.Assert.assertTrue;
  
  /**
   * @author yole
   */
  @SuppressWarnings({"UseOfSystemOutOrSystemErr"})
  public abstract class AbstractVcsTestCase {
    protected boolean myTraceClient = false;
    protected Project myProject;
    protected VirtualFile myWorkingCopyDir;
    protected File myClientBinaryPath;
    protected IdeaProjectTestFixture myProjectFixture;
    protected boolean myInitChangeListManager = true;
  
    protected String getPluginName() {
      throw new UnsupportedOperationException();
    }
  
    protected TestClientRunner createClientRunner() {
      return createClientRunner(null);
    }
  
    protected TestClientRunner createClientRunner(@Nullable Map<String, String> clientEnvironment) {
      return new TestClientRunner(myTraceClient, myClientBinaryPath, clientEnvironment);
    }
  
    public void setVcsMappings(VcsDirectoryMapping... mappings) {
      setVcsMappings(Arrays.asList(mappings));
    }
  
    protected void setVcsMappings(List<VcsDirectoryMapping> mappings) {
      ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
      vcsManager.setDirectoryMappings(mappings);
      vcsManager.updateActiveVcss();
      ChangeListManagerImpl.getInstanceImpl(myProject).waitUntilRefreshed();
    }
  
    protected static void refreshVfs() {
      UsefulTestCase.edt(new Runnable() {
        @Override
        public void run() {
          LocalFileSystem.getInstance().refresh(false);
        }
      });
    }
  
    protected void initProject(final File clientRoot, String testName) throws Exception {
      final String key = "idea.load.plugins.id";
      final String was = System.getProperty(key);
      try {
        final String pluginName = getPluginName();
        if (pluginName != null) {
          System.setProperty(key, pluginName);
        }
        String name = getClass().getName() + "." + testName;
        final TestFixtureBuilder<IdeaProjectTestFixture> testFixtureBuilder = IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(name);
        myProjectFixture = testFixtureBuilder.getFixture();
        testFixtureBuilder.addModule(EmptyModuleFixtureBuilder.class).addContentRoot(clientRoot.toString());
        myProjectFixture.setUp();
        myProject = myProjectFixture.getProject();
  
        projectCreated();
  
        if (myInitChangeListManager) {
          ((ProjectComponent) ChangeListManager.getInstance(myProject)).projectOpened();
        }
        ((ProjectComponent) VcsDirtyScopeManager.getInstance(myProject)).projectOpened();
  
        ApplicationManager.getApplication().runWriteAction(new Runnable() {
          @Override
          public void run() {
            myWorkingCopyDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(clientRoot);
            assert myWorkingCopyDir != null;
          }
        });
      } finally {
        if (was != null) {
          System.setProperty(key, was);
        } else {
          System.clearProperty(key);
        }
      }
    }
  
    protected void projectCreated() {
    }
  
    protected void activateVCS(final String vcsName) {
      ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
      vcsManager.setDirectoryMapping(myWorkingCopyDir.getPath(), vcsName);
      vcsManager.updateActiveVcss();
  
      AbstractVcs vcs = vcsManager.findVcsByName(vcsName);
      Assert.assertEquals(1, vcsManager.getRootsUnderVcs(vcs).length);
    }
  
    public VirtualFile createFileInCommand(final String name, @Nullable final String content) {
      return createFileInCommand(myWorkingCopyDir, name, content);
    }
  
    public VirtualFile createFileInCommand(final VirtualFile parent, final String name, @Nullable final String content) {
      final Ref<VirtualFile> result = new Ref<VirtualFile>();
      new WriteCommandAction.Simple(myProject) {
        @Override
        protected void run() throws Throwable {
          try {
            VirtualFile file = parent.createChildData(this, name);
            if (content != null) {
              file.setBinaryContent(CharsetToolkit.getUtf8Bytes(content));
            }
            result.set(file);
          }
          catch (IOException e) {
            throw new RuntimeException(e);
          }
        }
      }.execute();
      return result.get();
    }
  
    /**
     * Creates directory inside a write action and returns the resulting reference to it.
     * If the directory already exists, does nothing.
     * @param parent Parent directory.
     * @param name   Name of the directory.
     * @return reference to the created or already existing directory.
     */
    public VirtualFile createDirInCommand(final VirtualFile parent, final String name) {
      final Ref<VirtualFile> result = new Ref<VirtualFile>();
      new WriteCommandAction.Simple(myProject) {
        @Override
        protected void run() throws Throwable {
          try {
            VirtualFile dir = parent.findChild(name);
            if (dir == null) {
              dir = parent.createChildDirectory(this, name);
            }
            result.set(dir);
          }
          catch (IOException e) {
            throw new RuntimeException(e);
          }
        }
      }.execute();
      return result.get();
    }
  
    protected void clearDirInCommand(final VirtualFile dir, final Processor<VirtualFile> filter) {
      new WriteCommandAction.Simple(myProject) {
        @Override
        protected void run() throws Throwable {
          int numOfRuns = 5;
          for (int i = 0; i < numOfRuns; i++) {
            try {
              final VirtualFile[] children = dir.getChildren();
              for (VirtualFile child : children) {
                if (filter != null && filter.process(child)) {
                  child.delete(AbstractVcsTestCase.this);
                }
              }
              return;
            }
            catch (IOException e) {
              if (i == (numOfRuns - 1)) {
                // last run
                throw e;
              }
              Thread.sleep(50);
              continue;
            }
          }
        }
      }.execute();
    }
  
    protected void tearDownProject() throws Exception {
      if (myProject != null) {
        ((ProjectComponent) VcsDirtyScopeManager.getInstance(myProject)).projectClosed();
        ((ProjectComponent) ChangeListManager.getInstance(myProject)).projectClosed();
        ((ChangeListManagerImpl)ChangeListManager.getInstance(myProject)).stopEveryThingIfInTestMode();
        ((ProjectComponent)ProjectLevelVcsManager.getInstance(myProject)).projectClosed();
        myProject = null;
      }
      if (myProjectFixture != null) {
        myProjectFixture.tearDown();
        myProjectFixture = null;
      }
    }
  
    public void setStandardConfirmation(final String vcsName, final VcsConfiguration.StandardConfirmation op,
                                        final VcsShowConfirmationOption.Value value) {
      ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
      final AbstractVcs vcs = vcsManager.findVcsByName(vcsName);
      VcsShowConfirmationOption option = vcsManager.getStandardConfirmation(op, vcs);
      option.setValue(value);
    }
  
    public static void verify(final ProcessOutput runResult) {
      Assert.assertEquals(runResult.getStderr(), 0, runResult.getExitCode());
    }
  
    protected static void verify(final ProcessOutput runResult, final String... stdoutLines) {
      verify(runResult, false, stdoutLines);
    }
  
    protected static void verifySorted(final ProcessOutput runResult, final String... stdoutLines) {
      verify(runResult, true, stdoutLines);
    }
  
    private static void verify(final ProcessOutput runResult, final boolean sorted, final String... stdoutLines) {
      verify(runResult);
      final String[] lines = new LineTokenizer(runResult.getStdout()).execute();
      if (sorted) {
        Arrays.sort(lines);
      }
      Assert.assertEquals(runResult.getStdout(), stdoutLines.length, lines.length); 
      for(int i=0; i<stdoutLines.length; i++) {
        Assert.assertEquals(stdoutLines [i], compressWhitespace(lines [i]));
      }
    }
  
    private static String compressWhitespace(String line) {
      while(line.indexOf("  ") > 0) {
        line = line.replace("  ", " ");
      }
      return line.trim();
    }
  
    protected VcsDirtyScope getDirtyScopeForFile(VirtualFile file) {
      VcsDirtyScopeManager dirtyScopeManager = VcsDirtyScopeManager.getInstance(myProject);
      dirtyScopeManager.retrieveScopes();  // ensure that everything besides the file is clean
      dirtyScopeManager.fileDirty(file);
      List<VcsDirtyScope> scopes = dirtyScopeManager.retrieveScopes().getScopes();
      Assert.assertEquals(1, scopes.size());
      return scopes.get(0);
    }
  
    protected void renameFileInCommand(final VirtualFile file, final String newName) {
      renameFileInCommand(myProject, file, newName);
    }
  
    public static void renameFileInCommand(final Project project, final VirtualFile file, final String newName) {
      new WriteCommandAction.Simple(project) {
        @Override
        protected void run() throws Throwable {
          try {
            file.rename(this, newName);
          }
          catch (IOException e) {
            throw new RuntimeException(e);
          }
        }
      }.execute().throwException();
    }
  
    protected void deleteFileInCommand(final VirtualFile file) {
      deleteFileInCommand(myProject, file);
    }
  
    public static void deleteFileInCommand(final Project project, final VirtualFile file) {
      new WriteCommandAction.Simple(project) {
        @Override
        protected void run() throws Throwable {
          try {
            file.delete(this);
          }
          catch(IOException ex) {
            throw new RuntimeException(ex);
          }
        }
      }.execute();
    }
  
    protected void editFileInCommand(final VirtualFile file, final String newContent) {
      editFileInCommand(myProject, file, newContent);
    }
  
    public static void editFileInCommand(final Project project, final VirtualFile file, final String newContent) {
      assertTrue(file.isValid());
      file.getTimeStamp();
      new WriteCommandAction.Simple(project) {
        @Override
        protected void run() throws Throwable {
          try {
            final long newTs = Math.max(System.currentTimeMillis(), file.getTimeStamp() + 1100);
            file.setBinaryContent(newContent.getBytes(), -1, newTs);
            final File file1 = new File(file.getPath());
            FileUtil.writeToFile(file1, newContent.getBytes());
            file.refresh(false, false);
            assertTrue(file1 + " / " + newTs, file1.setLastModified(newTs));
          }
          catch(IOException ex) {
            throw new RuntimeException(ex);
          }
        }
      }.execute();
    }
  
    protected VirtualFile copyFileInCommand(final VirtualFile file, final String toName) {
      final AtomicReference<VirtualFile> res = new AtomicReference<VirtualFile>();
      new WriteCommandAction.Simple(myProject) {
        @Override
        protected void run() throws Throwable {
          try {
            res.set(file.copy(this, file.getParent(), toName));
          }
          catch (IOException e) {
            throw new RuntimeException(e);
          }
        }
      }.execute();
      return res.get();
    }
  
    protected VirtualFile copyFileInCommand(final VirtualFile file, final VirtualFile newParent) {
      return copyFileInCommand(myProject, file, newParent, file.getName());
    }
                                            
    public static VirtualFile copyFileInCommand(final Project project,
                                                final VirtualFile file,
                                                final VirtualFile newParent,
                                                final String newName) {
      return new WriteCommandAction<VirtualFile>(project) {
        @Override
        protected void run(Result<VirtualFile> result) throws Throwable {
          try {
            result.setResult(file.copy(this, newParent, newName));
          }
          catch (IOException e) {
            throw new RuntimeException(e);
          }
        }
      }.execute().getResultObject();
    }
  
    protected void moveFileInCommand(final VirtualFile file, final VirtualFile newParent) {
      moveFileInCommand(myProject, file, newParent);
    }
  
    public static void moveFileInCommand(final Project project, final VirtualFile file, final VirtualFile newParent) {
      new WriteCommandAction.Simple(project) {
        @Override
        protected void run() throws Throwable {
          try {
            file.move(this, newParent);
          }
          catch (IOException e) {
            throw new RuntimeException(e);
          }
        }
      }.execute();
    }
  
    protected void verifyChange(final Change c, final String beforePath, final String afterPath) {
      if (beforePath == null) {
        Assert.assertNull(c.getBeforeRevision());
      }
      else {
        verifyRevision(c.getBeforeRevision(), beforePath);
      }
      if (afterPath == null) {
        Assert.assertNull(c.getAfterRevision());
      }
      else {
        verifyRevision(c.getAfterRevision(), afterPath);
      }
    }
  
    public VirtualFile getWorkingCopyDir() {
      return myWorkingCopyDir;
    }
  
    private void verifyRevision(final ContentRevision beforeRevision, final String beforePath) {
      File beforeFile = new File(myWorkingCopyDir.getPath(), beforePath);
      String beforeFullPath = FileUtil.toSystemIndependentName(beforeFile.getPath());
      final String beforeRevPath = FileUtil.toSystemIndependentName(beforeRevision.getFile().getPath());
      Assert.assertTrue(beforeFullPath + "!=" + beforeRevPath,  beforeFullPath.equalsIgnoreCase(beforeRevPath));
    }
  
    public static void sortChanges(final List<Change> changes) {
      Collections.sort(changes, new Comparator<Change>() {
        @Override
        public int compare(final Change o1, final Change o2) {
          final String p1 = FileUtil.toSystemIndependentName(ChangesUtil.getFilePath(o1).getPath());
          final String p2 = FileUtil.toSystemIndependentName(ChangesUtil.getFilePath(o2).getPath());
          return p1.compareTo(p2);
        }
      });
    }
  }
  