[Git] Rework GitChangeProviderTest
authorKirill Likhodedov <kirill.likhodedov@jetbrains.com>
Wed, 18 May 2011 14:50:10 +0000 (18:50 +0400)
committerKirill Likhodedov <kirill.likhodedov@jetbrains.com>
Thu, 19 May 2011 07:42:30 +0000 (11:42 +0400)
+use MockDirtyScope,
* add to dirty scope correctly,
+testMoveNewFile
+tos()-methods for more descriptive output.
*add random string when creating files to avoid Git rename detection.

platform/testFramework/src/com/intellij/testFramework/vcs/MockDirtyScope.java [new file with mode: 0644]
plugins/git4idea/tests/git4idea/tests/GitChangeProviderTest.java
plugins/git4idea/tests/git4idea/tests/GitTest.java
plugins/git4idea/tests/git4idea/tests/GitTestUtil.java

diff --git a/platform/testFramework/src/com/intellij/testFramework/vcs/MockDirtyScope.java b/platform/testFramework/src/com/intellij/testFramework/vcs/MockDirtyScope.java
new file mode 100644 (file)
index 0000000..cb37d8d
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2000-2011 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.openapi.project.Project;
+import com.intellij.openapi.vcs.AbstractVcs;
+import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vcs.ProjectLevelVcsManager;
+import com.intellij.openapi.vcs.changes.VcsModifiableDirtyScope;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Processor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Mock implementation of {@link com.intellij.openapi.vcs.changes.VcsDirtyScope}.
+ * Stores files and dirs separately without any check.
+ * Not all operations may be supported.
+ *
+ * @author Kirill Likhodedov
+ */
+public class MockDirtyScope extends VcsModifiableDirtyScope {
+
+  private final Project myProject;
+  private final AbstractVcs myVcs;
+  private final ProjectLevelVcsManager myVcsManager;
+
+  private final Set<FilePath> myDirtyFiles = new HashSet<FilePath>();
+  private final Set<FilePath> myDirtyDirs = new HashSet<FilePath>();
+  private final Set<VirtualFile> myContentRoots = new HashSet<VirtualFile>();
+
+  public MockDirtyScope(@NotNull Project project, @NotNull AbstractVcs vcs) {
+    myProject = project;
+    myVcs = vcs;
+    myVcsManager = ProjectLevelVcsManager.getInstance(myProject);
+  }
+
+  @Override
+  public void addDirtyFile(@Nullable FilePath newcomer) {
+    myDirtyFiles.add(newcomer);
+    myContentRoots.add(myVcsManager.getVcsRootFor(newcomer.getVirtualFile()));
+  }
+
+  @Override
+  public void addDirtyDirRecursively(@Nullable FilePath newcomer) {
+    myDirtyDirs.add(newcomer);
+    myContentRoots.add(myVcsManager.getVcsRootFor(newcomer.getVirtualFile()));
+  }
+
+  @Override
+  @NotNull
+  public Collection<VirtualFile> getAffectedContentRoots() {
+    return myContentRoots;
+  }
+
+  @Override
+  @NotNull
+  public Project getProject() {
+    return myProject;
+  }
+
+  @Override
+  @NotNull
+  public AbstractVcs getVcs() {
+    return myVcs;
+  }
+
+  @Override
+  @NotNull
+  public Set<FilePath> getDirtyFiles() {
+    return myDirtyFiles;
+  }
+
+  @Override
+  @NotNull
+  public Set<FilePath> getDirtyFilesNoExpand() {
+    return myDirtyFiles;
+  }
+
+  @Override
+  @NotNull
+  public Set<FilePath> getRecursivelyDirtyDirectories() {
+    return myDirtyDirs;
+  }
+
+  @Override
+  public boolean isRecursivelyDirty(VirtualFile vf) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void iterate(Processor<FilePath> iterator) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return myDirtyFiles.isEmpty();
+  }
+
+  @Override
+  public boolean belongsTo(FilePath path) {
+    throw new UnsupportedOperationException();
+  }
+}
index e89f1b3ba74649777ba5142f82d8f5cb1e577a09..6320c4fe552d83734ce14c4057ba23d89b0aad3d 100644 (file)
@@ -19,17 +19,20 @@ import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.progress.EmptyProgressIndicator;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vcs.*;
-import com.intellij.openapi.vcs.changes.*;
+import com.intellij.openapi.vcs.changes.Change;
+import com.intellij.openapi.vcs.changes.ChangeListManager;
+import com.intellij.openapi.vcs.changes.VcsModifiableDirtyScope;
 import com.intellij.openapi.vcs.changes.pending.MockChangeListManagerGate;
+import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.testFramework.vcs.MockChangelistBuilder;
+import com.intellij.testFramework.vcs.MockDirtyScope;
 import com.intellij.ui.GuiUtils;
 import git4idea.GitVcs;
 import git4idea.changes.GitChangeProvider;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import java.io.File;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -41,7 +44,7 @@ import static org.testng.Assert.*;
 /**
  * Tests GitChangeProvider functionality. Scenario is the same for all tests:
  * 1. Modifies files on disk (creates, edits, deletes, etc.)
- * 2. Manually adds them to a dirty scope (better to use VcsDirtyScopeManagerImpl, but it's too asynchronous - couldn't overcome this for now.
+ * 2. Manually adds them to a dirty scope.
  * 3. Calls ChangeProvider.getChanges() and checks that the changes are there.
  * @author Kirill Likhodedov
  */
@@ -51,41 +54,46 @@ public class GitChangeProviderTest extends GitSingleUserTest {
   private VcsModifiableDirtyScope myDirtyScope;
   private Map<String, VirtualFile> myFiles;
   private VirtualFile afile;
+  private VirtualFile myRootDir;
 
   @BeforeMethod
   @Override
   protected void setUp() throws Exception {
     super.setUp();
     myChangeProvider = (GitChangeProvider) GitVcs.getInstance(myProject).getChangeProvider();
-    myDirtyScope = new VcsDirtyScopeImpl(GitVcs.getInstance(myProject), myProject);
 
     myFiles = GitTestUtil.createFileStructure(myProject, myRepo, "a.txt", "b.txt", "dir/c.txt", "dir/subdir/d.txt");
+    myRepo.addCommit();
+    myRepo.refresh();
+
     afile = myFiles.get("a.txt"); // the file is commonly used, so save it in a field.
-    myRepo.commit();
+    myRootDir = myRepo.getDir();
+
+    myDirtyScope = new MockDirtyScope(myProject, GitVcs.getInstance(myProject));
   }
 
   @Test
   public void testCreateFile() throws Exception {
-    VirtualFile bfile = myRepo.createFile("new.txt");
-    assertChanges(bfile, ADDED);
+    VirtualFile file = create(myRootDir, "new.txt");
+    assertChanges(file, ADDED);
   }
 
   @Test
   public void testCreateFileInDir() throws Exception {
-    VirtualFile dir = createDirInCommand(myRepo.getDir(), "newdir");
-    VirtualFile bfile = createFileInCommand(dir, "new.txt", "initial b");
+    VirtualFile dir = createDir(myRootDir, "newdir");
+    VirtualFile bfile = create(dir, "new.txt");
     assertChanges(new VirtualFile[] {bfile, dir}, new FileStatus[] { ADDED, null} );
   }
 
   @Test
   public void testEditFile() throws Exception {
-    editFileInCommand(afile, "new content");
+    edit(afile, "new content");
     assertChanges(afile, MODIFIED);
   }
 
   @Test
   public void testDeleteFile() throws Exception {
-    deleteFileInCommand(afile);
+    delete(afile);
     assertChanges(afile, DELETED);
   }
 
@@ -97,7 +105,9 @@ public class GitChangeProviderTest extends GitSingleUserTest {
         ApplicationManager.getApplication().runWriteAction(new Runnable() {
           @Override
           public void run() {
-            FileUtil.delete(new File(myRepo.getDir().getPath(), "dir"));
+            final VirtualFile dir= myRepo.getDir().findChild("dir");
+            myDirtyScope.addDirtyDirRecursively(new FilePathImpl(dir));
+            FileUtil.delete(VfsUtil.virtualToIoFile(dir));
           }
         });
       }
@@ -105,15 +115,29 @@ public class GitChangeProviderTest extends GitSingleUserTest {
     assertChanges(new VirtualFile[] { myFiles.get("dir/c.txt"), myFiles.get("dir/subdir/d.txt") }, new FileStatus[] { DELETED, DELETED });
   }
 
+  @Test
+  public void testMoveNewFile() throws Exception {
+    // IDEA-59587
+    // Reproducibility of the bug (in the original roots cause) depends on the order of new and old paths in the dirty scope.
+    // MockDirtyScope shouldn't preserve the order of items added there - a Set is returned from getDirtyFiles().
+    // But the order is likely preserved if it meets the natural order of the items inserted into the dirty scope.
+    // That's why the test moves from .../repo/dir/new.txt to .../repo/new.txt - to make the old path appear later than the new one.
+    // This is not consistent though.
+    final VirtualFile dir= myRepo.getDir().findChild("dir");
+    final VirtualFile file = create(dir, "new.txt");
+    move(file, myRootDir);
+    assertChanges(file, ADDED);
+  }
+
   @Test
   public void testSimultaneousOperationsOnMultipleFiles() throws Exception {
     VirtualFile dfile = myFiles.get("dir/subdir/d.txt");
     VirtualFile cfile = myFiles.get("dir/c.txt");
 
-    editFileInCommand(afile, "new content");
-    editFileInCommand(cfile, "new content");
-    deleteFileInCommand(dfile);
-    VirtualFile newfile = createFileInCommand("newfile.txt", "new content");
+    edit(afile, "new afile content");
+    edit(cfile, "new cfile content");
+    delete(dfile);
+    VirtualFile newfile = create(myRootDir, "newfile.txt");
 
     assertChanges(new VirtualFile[] {afile, cfile, dfile, newfile}, new FileStatus[] {MODIFIED, MODIFIED, DELETED, ADDED});
   }
@@ -194,19 +218,24 @@ public class GitChangeProviderTest extends GitSingleUserTest {
     VirtualFile file = myRepo.getDir().findChild(filename);
     switch (action) {
       case CREATE:
-        createFileInCommand(filename, "initial content in branch " + branchName);
+        final VirtualFile createdFile = createFileInCommand(filename, "initial content in branch " + branchName);
+        dirty(createdFile);
         myRepo.add(filename);
         break;
       case MODIFY:
         editFileInCommand(file, "new content in branch " + branchName);
+        dirty(file);
         myRepo.add(filename);
         break;
       case DELETE:
+        dirty(file);
         myRepo.rm(filename);
         break;
       case RENAME:
-        String name = filename + "_" + branchName.replaceAll("\\s", "_") + "_new";
-        myRepo.mv(filename, name);
+        String newName = filename + "_" + branchName.replaceAll("\\s", "_") + "_new";
+        dirty(file);
+        myRepo.mv(filename, newName);
+        dirty(myRootDir.findChild(newName));
         break;
       default:
         break;
@@ -223,11 +252,11 @@ public class GitChangeProviderTest extends GitSingleUserTest {
       FilePath fp = new FilePathImpl(virtualFiles[i]);
       FileStatus status = fileStatuses[i];
       if (status == null) {
-        assertFalse(result.containsKey(fp), "File [" + fp + " shouldn't be in the change list, but it was.");
+        assertFalse(result.containsKey(fp), "File [" + tos(fp) + " shouldn't be in the change list, but it was.");
         continue;
       }
-      assertTrue(result.containsKey(fp), "File [" + fp + "] didn't change. Changes: " + result);
-      assertEquals(result.get(fp).getFileStatus(), status, "File statuses don't match for file [" + fp + "]");
+      assertTrue(result.containsKey(fp), "File [" + tos(fp) + "] didn't change. Changes: " + tos(result));
+      assertEquals(result.get(fp).getFileStatus(), status, "File statuses don't match for file [" + tos(fp) + "]");
     }
   }
 
@@ -237,18 +266,11 @@ public class GitChangeProviderTest extends GitSingleUserTest {
 
   /**
    * Marks the given files dirty in myDirtyScope, gets changes from myChangeProvider and groups the changes in the map.
-   * Assumes that only one change for a file happened.
+   * Assumes that only one change for a file has happened.
    */
   private Map<FilePath, Change> getChanges(VirtualFile... changedFiles) throws VcsException {
     final List<FilePath> changedPaths = ObjectsConvertor.vf2fp(Arrays.asList(changedFiles));
 
-    // populate dirty scope
-    //for (FilePath path : changedPaths) {
-    //  myDirtyScope.addDirtyFile(path);
-    //}
-    VcsDirtyScopeManagerImpl.getInstance(myProject).markEverythingDirty();
-    myDirtyScope.addDirtyDirRecursively(new FilePathImpl(myRepo.getDir()));
-
     // get changes
     MockChangelistBuilder builder = new MockChangelistBuilder();
     myChangeProvider.getChanges(myDirtyScope, builder, new EmptyProgressIndicator(), new MockChangeListManagerGate(ChangeListManager.getInstance(myProject)));
@@ -275,4 +297,37 @@ public class GitChangeProviderTest extends GitSingleUserTest {
     return result;
   }
 
+  private VirtualFile create(VirtualFile parent, String name) {
+    return create(parent, name, false);
+  }
+
+  private VirtualFile createDir(VirtualFile parent, String name) {
+    return create(parent, name, true);
+  }
+
+  private VirtualFile create(VirtualFile parent, String name, boolean dir) {
+    final VirtualFile file = dir ? createDirInCommand(parent, name) : createFileInCommand(parent, name, "content" + Math.random());
+    dirty(file);
+    return file;
+  }
+
+  private void edit(VirtualFile file, String content) {
+    editFileInCommand(file, content);
+    dirty(file);
+  }
+
+  private void move(VirtualFile file, VirtualFile newParent) {
+    dirty(file);
+    moveFileInCommand(file, newParent);
+    dirty(file);
+  }
+
+  private void delete(VirtualFile file) {
+    dirty(file);
+    deleteFileInCommand(file);
+  }
+
+  private void dirty(VirtualFile file) {
+    myDirtyScope.addDirtyFile(new FilePathImpl(file));
+  }
 }
index 8c1e6f081b93103be4d90843f7b0246a1ad36c5e..8565cecf603e9c774434e3b67c9890720f3fd561 100644 (file)
@@ -18,8 +18,12 @@ package git4idea.tests;
 import com.intellij.execution.process.ProcessOutput;
 import com.intellij.openapi.application.PluginPathManager;
 import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vcs.VcsConfiguration;
 import com.intellij.openapi.vcs.VcsShowConfirmationOption;
+import com.intellij.openapi.vcs.changes.Change;
+import com.intellij.openapi.vcs.changes.ContentRevision;
 import com.intellij.testFramework.AbstractVcsTestCase;
 import com.intellij.ui.GuiUtils;
 import com.intellij.util.ui.UIUtil;
@@ -31,6 +35,7 @@ import org.testng.annotations.BeforeMethod;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Map;
 
 /**
  * The common ancestor for git test cases which need git executable.
@@ -119,12 +124,31 @@ public abstract class GitTest extends AbstractVcsTestCase {
     setStandardConfirmation(GitVcs.NAME, op, VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY);
   }
 
-  protected void doNothingSilently(final VcsConfiguration.StandardConfirmation op) {
-    setStandardConfirmation(GitVcs.NAME, op, VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY);
+  protected String tos(FilePath fp) {
+    return FileUtil.getRelativePath(new File(myMainRepo.getDir().getPath()), fp.getIOFile());
   }
 
-  protected void showConfirmation(final VcsConfiguration.StandardConfirmation op) {
-    setStandardConfirmation(GitVcs.NAME, op, VcsShowConfirmationOption.Value.SHOW_CONFIRMATION);
+  protected String tos(Change change) {
+    switch (change.getType()) {
+      case NEW: return "A: " + tos(change.getAfterRevision());
+      case DELETED: return "D: " + tos(change.getBeforeRevision());
+      case MOVED: return "M: " + tos(change.getBeforeRevision()) + " -> " + tos(change.getAfterRevision());
+      case MODIFICATION: return "M: " + tos(change.getAfterRevision());
+      default: return "~: " +  tos(change.getBeforeRevision()) + " -> " + tos(change.getAfterRevision());
+    }
+  }
+
+  protected String tos(ContentRevision revision) {
+    return tos(revision.getFile());
+  }
+
+  protected String tos(Map<FilePath, Change> changes) {
+    StringBuilder stringBuilder = new StringBuilder("[");
+    for (Change change : changes.values()) {
+      stringBuilder.append(tos(change)).append(", ");
+    }
+    stringBuilder.append("]");
+    return stringBuilder.toString();
   }
 
 }
index b67dbb4ab807979f6a2e391b151e786f360f4d77..07e94904b5e43006f9ba0381bc4c0392d5c5ef18 100644 (file)
@@ -54,7 +54,7 @@ public class GitTestUtil {
       }
 
       String lastElement = pathElements[pathElements.length-1];
-      currentParent = lastIsDir ? createDir(project, currentParent, lastElement) : createFile(project, currentParent, lastElement, "initial content");
+      currentParent = lastIsDir ? createDir(project, currentParent, lastElement) : createFile(project, currentParent, lastElement, "content" + Math.random());
       result.put(path, currentParent);
     }
     return result;