blob: 364906460fe63d8de7ab55d7b9a4d86fa9301993 [file] [log] [blame]
/*
* Copyright 2000-2014 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 org.jetbrains.idea;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.ide.startup.impl.StartupManagerImpl;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.PluginPathManager;
import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.TestDialog;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsConfiguration;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.VcsShowConfirmationOption;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx;
import com.intellij.openapi.vcs.update.CommonUpdateProjectAction;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
import com.intellij.testFramework.PlatformTestCase;
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory;
import com.intellij.testFramework.fixtures.TempDirTestFixture;
import com.intellij.testFramework.vcs.AbstractJunitVcsTestCase;
import com.intellij.testFramework.vcs.MockChangeListManagerGate;
import com.intellij.testFramework.vcs.MockChangelistBuilder;
import com.intellij.testFramework.vcs.TestClientRunner;
import com.intellij.util.Processor;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.io.ZipUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.SvnApplicationSettings;
import org.jetbrains.idea.svn.SvnConfiguration;
import org.jetbrains.idea.svn.SvnFileUrlMappingImpl;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.actions.CreateExternalAction;
import org.junit.After;
import org.junit.Before;
import java.io.File;
import java.io.IOException;
import java.util.*;
import static org.junit.Assert.*;
/**
* @author yole
*/
public abstract class SvnTestCase extends AbstractJunitVcsTestCase {
public static String ourGlobalTestDataDir;
public static Boolean ourGlobalUseNativeAcceleration;
protected TempDirTestFixture myTempDirFixture;
protected String myRepoUrl;
protected TestClientRunner myRunner;
protected String myWcRootName;
// TODO: Change this to explicitly run either with native acceleration or not.
// properties set through run configurations or different runners (like Suite) could be used
private boolean myUseNativeAcceleration = new GregorianCalendar().get(Calendar.HOUR_OF_DAY) % 2 == 0;
private String myTestDataDir;
private File myRepoRoot;
private File myWcRoot;
private ChangeListManagerGate myGate;
protected String myAnotherRepoUrl;
protected File myPluginRoot;
protected SvnTestCase(@NotNull String testDataDir) {
PlatformTestCase.initPlatformLangPrefix();
myTestDataDir = testDataDir;
myWcRootName = "wcroot";
}
public static void imitateEvent(VirtualFile dir) {
final VirtualFile child = dir.findChild(".svn");
assertNotNull(child);
final VirtualFile wcdb = child.findChild("wc.db");
assertNotNull(wcdb);
final BulkFileListener listener = ApplicationManager.getApplication().getMessageBus().syncPublisher(VirtualFileManager.VFS_CHANGES);
final VFileContentChangeEvent event =
new VFileContentChangeEvent(null, wcdb, wcdb.getModificationStamp() - 1, wcdb.getModificationStamp(), true);
final List<VFileContentChangeEvent> events = Collections.singletonList(event);
listener.before(events);
listener.after(events);
}
@Override
protected String getPluginName() {
return "Subversion";
}
@Before
public void setUp() throws Exception {
System.out.println("Native client for status: " + isUseNativeAcceleration());
String property = System.getProperty("svn.test.data.directory");
if (!StringUtil.isEmpty(property)) {
myTestDataDir = property;
}
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
try {
final IdeaTestFixtureFactory fixtureFactory = IdeaTestFixtureFactory.getFixtureFactory();
myTempDirFixture = fixtureFactory.createTempDirTestFixture();
myTempDirFixture.setUp();
myRepoRoot = new File(myTempDirFixture.getTempDirPath(), "svnroot");
assert myRepoRoot.mkdir() || myRepoRoot.isDirectory() : myRepoRoot;
myPluginRoot = new File(PluginPathManager.getPluginHomePath("svn4idea"));
if (!myPluginRoot.isDirectory()) {
// try standalone mode
Class aClass = SvnTestCase.class;
String rootPath = PathManager.getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class");
myPluginRoot = new File(rootPath).getParentFile().getParentFile().getParentFile();
}
File svnBinDir = new File(myPluginRoot, getTestDataDir() + "/svn/bin");
File svnExecutable = null;
if (SystemInfo.isWindows) {
svnExecutable = new File(svnBinDir, "windows/svn.exe");
}
else if (SystemInfo.isLinux) {
svnExecutable = new File(svnBinDir, "linux/svn");
}
else if (SystemInfo.isMac) {
svnExecutable = new File(svnBinDir, "mac/svn");
}
assertTrue("No Subversion executable was found: " + svnExecutable + ", " + SystemInfo.OS_NAME,
svnExecutable != null && svnExecutable.canExecute());
myClientBinaryPath = svnExecutable.getParentFile();
myRunner = SystemInfo.isMac
? createClientRunner(Collections.singletonMap("DYLD_LIBRARY_PATH", myClientBinaryPath.getPath()))
: createClientRunner();
ZipUtil.extract(new File(myPluginRoot, getTestDataDir() + "/svn/newrepo.zip"), myRepoRoot, null);
myWcRoot = new File(myTempDirFixture.getTempDirPath(), myWcRootName);
assert myWcRoot.mkdir() || myWcRoot.isDirectory() : myWcRoot;
myRepoUrl = (SystemInfo.isWindows ? "file:///" : "file://") + FileUtil.toSystemIndependentName(myRepoRoot.getPath());
verify(runSvn("co", myRepoUrl, myWcRoot.getPath()));
initProject(myWcRoot, SvnTestCase.this.getTestName());
activateVCS(SvnVcs.VCS_NAME);
myGate = new MockChangeListManagerGate(ChangeListManager.getInstance(myProject));
((StartupManagerImpl) StartupManager.getInstance(myProject)).runPostStartupActivities();
refreshSvnMappingsSynchronously();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
});
// there should be kind-a waiting for after change list manager finds all changes and runs inner refresh of copies in the above method
if (myInitChangeListManager) {
ChangeListManager changeListManager = ChangeListManager.getInstance(myProject);
VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty();
changeListManager.ensureUpToDate(false);
}
}
protected void refreshSvnMappingsSynchronously() {
final SvnVcs vcs = SvnVcs.getInstance(myProject);
if (! myInitChangeListManager) {
return;
}
final Semaphore semaphore = new Semaphore();
semaphore.down();
((SvnFileUrlMappingImpl) vcs.getSvnFileUrlMapping()).realRefresh(new Runnable() {
@Override
public void run() {
semaphore.up();
}
});
semaphore.waitFor();
}
@Override
protected void projectCreated() {
if (isUseNativeAcceleration()) {
SvnConfiguration.getInstance(myProject).setUseAcceleration(SvnConfiguration.UseAcceleration.commandLine);
SvnApplicationSettings.getInstance().setCommandLinePath(myClientBinaryPath + File.separator + "svn");
}
}
@After
public void tearDown() throws Exception {
((ChangeListManagerImpl) ChangeListManager.getInstance(myProject)).stopEveryThingIfInTestMode();
sleep(100);
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
try {
tearDownProject();
if (myWcRoot != null && myWcRoot.exists()) {
FileUtil.delete(myWcRoot);
}
if (myRepoRoot != null && myRepoRoot.exists()) {
FileUtil.delete(myRepoRoot);
}
if (myTempDirFixture != null) {
myTempDirFixture.tearDown();
myTempDirFixture = null;
}
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
protected ProcessOutput runSvn(String... commandLine) throws IOException {
return myRunner.runClient("svn", null, myWcRoot, commandLine);
}
protected void enableSilentOperation(final VcsConfiguration.StandardConfirmation op) {
setStandardConfirmation(SvnVcs.VCS_NAME, op, VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY);
}
protected void disableSilentOperation(final VcsConfiguration.StandardConfirmation op) {
setStandardConfirmation(SvnVcs.VCS_NAME, op, VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY);
}
protected void checkin() throws IOException {
runInAndVerifyIgnoreOutput("ci", "-m", "test");
}
protected void update() throws IOException {
runInAndVerifyIgnoreOutput("up");
}
protected List<Change> getChangesInScope(final VcsDirtyScope dirtyScope) throws VcsException {
ChangeProvider changeProvider = SvnVcs.getInstance(myProject).getChangeProvider();
MockChangelistBuilder builder = new MockChangelistBuilder();
changeProvider.getChanges(dirtyScope, builder, new EmptyProgressIndicator(), myGate);
return builder.getChanges();
}
protected void undo() {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
final TestDialog oldTestDialog = Messages.setTestDialog(TestDialog.OK);
try {
UndoManager.getInstance(myProject).undo(null);
}
finally {
Messages.setTestDialog(oldTestDialog);
}
}
});
}
protected void prepareInnerCopy(final boolean anotherRepository) throws Exception {
final String mainUrl = myRepoUrl + "/root/source";
final String externalURL;
if (anotherRepository) {
createAnotherRepo();
externalURL = myAnotherRepoUrl + "/root/target";
} else {
externalURL = myRepoUrl + "/root/target";
}
final ChangeListManagerImpl clManager = (ChangeListManagerImpl)ChangeListManager.getInstance(myProject);
final SubTree subTree = new SubTree(myWorkingCopyDir);
checkin();
clManager.stopEveryThingIfInTestMode();
sleep(100);
final File rootFile = new File(subTree.myRootDir.getPath());
FileUtil.delete(rootFile);
FileUtil.delete(new File(myWorkingCopyDir.getPath() + File.separator + ".svn"));
assertTrue(!rootFile.exists());
sleep(200);
myWorkingCopyDir.refresh(false, true);
runInAndVerifyIgnoreOutput("co", mainUrl);
final File sourceDir = new File(myWorkingCopyDir.getPath(), "source");
final File innerDir = new File(sourceDir, "inner1/inner2/inner");
runInAndVerifyIgnoreOutput("co", externalURL, innerDir.getPath());
sleep(100);
myWorkingCopyDir.refresh(false, true);
// above is preparation
// start change list manager again
clManager.forceGoInTestMode();
refreshSvnMappingsSynchronously();
//clManager.ensureUpToDate(false);
//clManager.ensureUpToDate(false);
}
public String getTestDataDir() {
return StringUtil.isEmpty(ourGlobalTestDataDir) ? myTestDataDir : ourGlobalTestDataDir;
}
public void setTestDataDir(String testDataDir) {
myTestDataDir = testDataDir;
}
public boolean isUseNativeAcceleration() {
return ourGlobalUseNativeAcceleration != null ? ourGlobalUseNativeAcceleration : myUseNativeAcceleration;
}
public void setUseNativeAcceleration(boolean useNativeAcceleration) {
myUseNativeAcceleration = useNativeAcceleration;
}
protected class SubTree {
public VirtualFile myRootDir;
public VirtualFile mySourceDir;
public VirtualFile myTargetDir;
public VirtualFile myS1File;
public VirtualFile myS2File;
public final List<VirtualFile> myTargetFiles;
public static final String ourS1Contents = "123";
public static final String ourS2Contents = "abc";
private VirtualFile findOrCreateChild(final VirtualFile parent, final String name, final String content) {
final VirtualFile result = parent.findChild(name);
if (result != null) return result;
if (content == null) {
return createDirInCommand(parent, name);
} else {
return createFileInCommand(parent, name, content);
}
}
public SubTree(final VirtualFile base) throws Exception {
myRootDir = findOrCreateChild(base, "root", null);
mySourceDir = findOrCreateChild(myRootDir, "source", null);
myS1File = findOrCreateChild(mySourceDir, "s1.txt", ourS1Contents);
myS2File = findOrCreateChild(mySourceDir, "s2.txt", ourS2Contents);
myTargetDir = findOrCreateChild(myRootDir, "target", null);
myTargetFiles = new ArrayList<VirtualFile>();
for (int i = 0; i < 10; i++) {
myTargetFiles.add(findOrCreateChild(myTargetDir, "t" + (i + 10) + ".txt", ourS1Contents));
}
}
}
protected static void sleep(final int millis) {
try {
Thread.sleep(millis);
}
catch (InterruptedException ignore) { }
}
public String prepareBranchesStructure() throws Exception {
final SvnVcs vcs = SvnVcs.getInstance(myProject);
final String mainUrl = myRepoUrl + "/trunk";
runInAndVerifyIgnoreOutput("mkdir", "-m", "mkdir", mainUrl);
runInAndVerifyIgnoreOutput("mkdir", "-m", "mkdir", myRepoUrl + "/branches");
runInAndVerifyIgnoreOutput("mkdir", "-m", "mkdir", myRepoUrl + "/tags");
final ChangeListManagerImpl clManager = (ChangeListManagerImpl)ChangeListManager.getInstance(myProject);
clManager.stopEveryThingIfInTestMode();
sleep(100);
boolean deleted = false;
for (int i = 0; i < 5; i++) {
deleted = FileUtil.delete(new File(myWorkingCopyDir.getPath() + File.separator + ".svn"));
if (deleted) break;
sleep(200);
}
assertTrue(deleted);
sleep(200);
myWorkingCopyDir.refresh(false, true);
runInAndVerifyIgnoreOutput("co", mainUrl, myWorkingCopyDir.getPath());
enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD);
final SubTree tree = new SubTree(myWorkingCopyDir);
checkin();
final String branchUrl = myRepoUrl + "/branches/b1";
runInAndVerifyIgnoreOutput("copy", "-q", "-m", "coppy", mainUrl, branchUrl);
clManager.forceGoInTestMode();
refreshSvnMappingsSynchronously();
//clManager.ensureUpToDate(false);
//clManager.ensureUpToDate(false);
return branchUrl;
}
public void prepareExternal() throws Exception {
prepareExternal(true, true, false);
}
public void prepareExternal(final boolean commitExternalDefinition, final boolean updateExternal,
final boolean anotherRepository) throws Exception {
final ChangeListManagerImpl clManager = (ChangeListManagerImpl)ChangeListManager.getInstance(myProject);
final SvnVcs vcs = SvnVcs.getInstance(myProject);
final String mainUrl = myRepoUrl + "/root/source";
final String externalURL;
if (anotherRepository) {
createAnotherRepo();
externalURL = myAnotherRepoUrl + "/root/target";
} else {
externalURL = myRepoUrl + "/root/target";
}
final SubTree subTree = new SubTree(myWorkingCopyDir);
checkin();
clManager.stopEveryThingIfInTestMode();
sleep(100);
final File rootFile = new File(subTree.myRootDir.getPath());
FileUtil.delete(rootFile);
FileUtil.delete(new File(myWorkingCopyDir.getPath() + File.separator + ".svn"));
assertTrue(!rootFile.exists());
sleep(200);
myWorkingCopyDir.refresh(false, true);
final File sourceDir = new File(myWorkingCopyDir.getPath(), "source");
runInAndVerifyIgnoreOutput("co", mainUrl, sourceDir.getPath());
CreateExternalAction.addToExternalProperty(vcs, sourceDir, "external", externalURL);
sleep(100);
if (updateExternal) {
runInAndVerifyIgnoreOutput("up", sourceDir.getPath());
}
if (commitExternalDefinition) {
runInAndVerifyIgnoreOutput("ci", "-m", "test", sourceDir.getPath());
}
sleep(100);
if (updateExternal) {
myWorkingCopyDir.refresh(false, true);
assertTrue(new File(sourceDir, "external").exists());
}
// above is preparation
// start change list manager again
clManager.forceGoInTestMode();
refreshSvnMappingsSynchronously();
//clManager.ensureUpToDate(false);
//clManager.ensureUpToDate(false);
}
protected void createAnotherRepo() throws Exception {
final File repo = FileUtil.createTempDirectory("anotherRepo", "");
FileUtil.delete(repo);
FileUtil.copyDir(myRepoRoot, repo);
myAnotherRepoUrl = (SystemInfo.isWindows ? "file:///" : "file://") + FileUtil.toSystemIndependentName(repo.getPath());
final File tmpWc = FileUtil.createTempDirectory("hhh", "");
runInAndVerifyIgnoreOutput("co", myAnotherRepoUrl, tmpWc.getPath());
final VirtualFile tmpWcVf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tmpWc);
assertNotNull(tmpWcVf);
final SubTree tree = new SubTree(tmpWcVf);
runInAndVerifyIgnoreOutput(tmpWc, "add", "root");
runInAndVerifyIgnoreOutput(tmpWc, "ci", "-m", "fff");
FileUtil.delete(tmpWc);
}
protected static void imitUpdate(final Project project) {
ProjectLevelVcsManagerEx.getInstanceEx(project).getOptions(VcsConfiguration.StandardOption.UPDATE).setValue(false);
final CommonUpdateProjectAction action = new CommonUpdateProjectAction();
action.getTemplatePresentation().setText("1");
action.actionPerformed(new AnActionEvent(null,
new DataContext() {
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (CommonDataKeys.PROJECT.is(dataId)) {
return project;
}
return null;
}
}, "test", new Presentation(), ActionManager.getInstance(), 0));
final ChangeListManager clManager = ChangeListManager.getInstance(project);
clManager.ensureUpToDate(false);
clManager.ensureUpToDate(false); // wait for after-events like annotations recalculation
sleep(100); // zipper updater
}
protected void runAndVerifyStatusSorted(final String... stdoutLines) throws IOException {
runStatusAcrossLocks(myWcRoot, true, stdoutLines);
}
protected void runAndVerifyStatus(final String... stdoutLines) throws IOException {
runStatusAcrossLocks(myWcRoot, false, stdoutLines);
}
private void runStatusAcrossLocks(@Nullable File workingDir, final boolean sorted, final String... stdoutLines) throws IOException {
final Processor<ProcessOutput> primitiveVerifier = new Processor<ProcessOutput>() {
@Override
public boolean process(ProcessOutput output) {
if (sorted) {
verifySorted(output, stdoutLines); // will assert if err not empty
} else {
verify(output, stdoutLines); // will assert if err not empty
}
return false;
}
};
runAndVerifyAcrossLocks(workingDir, new String[]{"status"}, new Processor<ProcessOutput>() {
@Override
public boolean process(ProcessOutput output) {
final List<String> lines = output.getStdoutLines();
for (String line : lines) {
if (line.trim().startsWith("L")) {
return true; // i.e. continue tries
}
}
primitiveVerifier.process(output);
return false;
}
}, primitiveVerifier);
}
protected void runInAndVerifyIgnoreOutput(final String... inLines) throws IOException {
final Processor<ProcessOutput> verifier = createPrimitiveExitCodeVerifier();
runAndVerifyAcrossLocks(myWcRoot, myRunner, inLines, verifier, verifier);
}
private static Processor<ProcessOutput> createPrimitiveExitCodeVerifier() {
return new Processor<ProcessOutput>() {
@Override
public boolean process(ProcessOutput output) {
assertEquals(output.getStderr(), 0, output.getExitCode());
return false;
}
};
}
public static void runInAndVerifyIgnoreOutput(File workingDir, final TestClientRunner runner, final String[] input, final String... stdoutLines) throws IOException {
final Processor<ProcessOutput> verifier = createPrimitiveExitCodeVerifier();
runAndVerifyAcrossLocks(workingDir, runner, input, verifier, verifier);
}
protected void runInAndVerifyIgnoreOutput(final File root, final String... inLines) throws IOException {
final Processor<ProcessOutput> verifier = createPrimitiveExitCodeVerifier();
runAndVerifyAcrossLocks(root, myRunner, inLines, verifier, verifier);
}
private void runAndVerifyAcrossLocks(@Nullable File workingDir, final String[] input, final Processor<ProcessOutput> verifier,
final Processor<ProcessOutput> primitiveVerifier) throws IOException {
workingDir = workingDir == null ? myWcRoot : workingDir;
runAndVerifyAcrossLocks(workingDir, myRunner, input, verifier, primitiveVerifier);
}
/**
* @param verifier - if returns true, try again
*/
public static void runAndVerifyAcrossLocks(File workingDir, final TestClientRunner runner, final String[] input,
final Processor<ProcessOutput> verifier, final Processor<ProcessOutput> primitiveVerifier) throws IOException {
for (int i = 0; i < 5; i++) {
final ProcessOutput output = runner.runClient("svn", null, workingDir, input);
if (output.getExitCode() == 0) {
if (verifier.process(output)) {
continue;
}
return;
}
if (! StringUtil.isEmptyOrSpaces(output.getStderr())) {
final String stderr = output.getStderr();
/*svn: E155004: Working copy '' locked.
svn: E155004: '' is already locked.
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)*/
if (stderr.contains("E155004") && stderr.contains("is already locked")) {
continue;
}
}
// will throw assertion
if (verifier.process(output)) {
continue;
}
return;
}
final ProcessOutput output = runner.runClient("svn", null, workingDir, input);
primitiveVerifier.process(output);
}
protected void setNativeAcceleration(final boolean value) {
System.out.println("Set native acceleration to " + value);
SvnConfiguration.getInstance(myProject).setUseAcceleration(
value ? SvnConfiguration.UseAcceleration.commandLine : SvnConfiguration.UseAcceleration.nothing);
SvnApplicationSettings.getInstance().setCommandLinePath(myClientBinaryPath + File.separator + "svn");
}
}