Files
openide/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnTestCase.java
Vladimir Krivosheev cf8cf718ce IJPL-158385 SvnFileUrlMappingImpl - pass scope to MergingUpdateQueue instead of Disposable
GitOrigin-RevId: c3e6fa4c42cd57c99fedad533410af4e4b7f66a4
2024-07-21 22:18:56 +00:00

520 lines
20 KiB
Java

// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.svn;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.ui.TestDialog;
import com.intellij.openapi.ui.TestDialogManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
import com.intellij.openapi.vcs.rollback.RollbackProgressListener;
import com.intellij.openapi.vcs.update.CommonUpdateProjectAction;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.ApplicationRule;
import com.intellij.testFramework.RunAll;
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.ThrowableRunnable;
import com.intellij.util.io.ZipUtil;
import com.intellij.util.system.CpuArch;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.actions.CreateExternalAction;
import org.jetbrains.idea.svn.api.Url;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.rules.ExternalResource;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import static com.intellij.openapi.application.PluginPathManager.getPluginHomePath;
import static com.intellij.openapi.util.io.FileUtil.*;
import static com.intellij.openapi.util.text.StringUtil.isEmptyOrSpaces;
import static com.intellij.openapi.vfs.VfsUtilCore.virtualToIoFile;
import static com.intellij.testFramework.EdtTestUtil.runInEdtAndWait;
import static com.intellij.testFramework.UsefulTestCase.*;
import static com.intellij.util.ObjectUtils.notNull;
import static com.intellij.util.containers.ContainerUtil.map2Array;
import static com.intellij.util.lang.CompoundRuntimeException.throwIfNotEmpty;
import static java.util.Collections.singletonMap;
import static org.jetbrains.idea.svn.SvnUtil.parseUrl;
import static org.junit.Assume.assumeTrue;
public abstract class SvnTestCase extends AbstractJunitVcsTestCase {
@ClassRule public static final ApplicationRule appRule = new ApplicationRule();
@ClassRule public static final ExternalResource ideaTempDirectoryRule = new ExternalResource() {
@Override
protected void before() throws Throwable {
ensureExists(new File(PathManager.getTempPath()));
}
};
private static final String ORIGINAL_TEMP_DIRECTORY = getTempDirectory();
protected TempDirTestFixture myTempDirFixture;
protected Url myRepositoryUrl;
protected String myRepoUrl;
protected TestClientRunner myRunner;
protected String myWcRootName;
private final String myTestDataDir;
private File myRepoRoot;
private File myWcRoot;
private ChangeListManagerGate myGate;
protected String myAnotherRepoUrl;
protected File myPluginRoot;
protected ProjectLevelVcsManagerImpl vcsManager;
protected ChangeListManagerImpl changeListManager;
protected VcsDirtyScopeManager dirtyScopeManager;
protected SvnVcs vcs;
protected SvnTestCase() {
this("testData");
}
protected SvnTestCase(@NotNull String testDataDir) {
myTestDataDir = testDataDir;
myWcRootName = "wcroot";
}
@NotNull
public static String getPluginHome() {
return getPluginHomePath("svn4idea");
}
@BeforeClass
public static void assumeSupportedTeamCityAgentArch() {
if (IS_UNDER_TEAMCITY) {
assumeTrue(SystemInfo.OS_NAME + '/' + CpuArch.CURRENT + " is not supported", !SystemInfo.isMac && CpuArch.isIntel64());
}
}
@Before
public void before() throws Exception {
myTempDirFixture = IdeaTestFixtureFactory.getFixtureFactory().createTempDirTestFixture();
myTempDirFixture.setUp();
resetCanonicalTempPathCache(myTempDirFixture.getTempDirPath());
myPluginRoot = new File(getPluginHome());
myClientBinaryPath = getSvnClientDirectory();
myRunner =
SystemInfo.isMac ? createClientRunner(singletonMap("DYLD_LIBRARY_PATH", myClientBinaryPath.getPath())) : createClientRunner();
myRepoRoot = virtualToIoFile(myTempDirFixture.findOrCreateDir("svnroot"));
ZipUtil.extract(new File(myPluginRoot, getTestDataDir() + "/svn/newrepo.zip"), myRepoRoot, null);
myWcRoot = virtualToIoFile(myTempDirFixture.findOrCreateDir(myWcRootName));
myRepoUrl = (SystemInfo.isWindows ? "file:///" : "file://") + toSystemIndependentName(myRepoRoot.getPath());
myRepositoryUrl = parseUrl(myRepoUrl);
verify(runSvn("co", myRepoUrl, myWcRoot.getPath()));
initProject(myWcRoot, this.getTestName());
activateVCS(SvnVcs.VCS_NAME);
vcsManager = (ProjectLevelVcsManagerImpl)ProjectLevelVcsManager.getInstance(myProject);
changeListManager = ChangeListManagerImpl.getInstanceImpl(myProject);
dirtyScopeManager = VcsDirtyScopeManager.getInstance(myProject);
vcs = SvnVcs.getInstance(myProject);
myGate = new MockChangeListManagerGate(changeListManager);
VfsUtil.markDirtyAndRefresh(false, true, true, myRepoRoot);
refreshSvnMappingsSynchronously();
refreshChanges();
}
@NotNull
private File getSvnClientDirectory() {
File svnBinDir = new File(myPluginRoot, getTestDataDir() + "/svn/bin");
String executablePath =
SystemInfo.isWindows ? "windows/svn.exe" : SystemInfo.isLinux ? "linux/svn" : SystemInfo.isMac ? "mac/svn" : null;
assertNotNull("No Subversion executable was found " + SystemInfo.OS_NAME, executablePath);
File svnExecutable = new File(svnBinDir, executablePath);
assertTrue(svnExecutable + " is not executable", svnExecutable.canExecute());
return svnExecutable.getParentFile();
}
protected void refreshSvnMappingsSynchronously() throws TimeoutException {
vcs.getSvnFileUrlMappingImpl().scheduleRefresh();
vcs.getSvnFileUrlMappingImpl().waitForRefresh();
}
protected void refreshChanges() {
dirtyScopeManager.markEverythingDirty();
changeListManager.ensureUpToDate();
}
protected void waitChangesAndAnnotations() {
changeListManager.ensureUpToDate();
((VcsAnnotationLocalChangesListenerImpl)vcsManager.getAnnotationLocalChangesListener()).calmDown();
}
@NotNull
protected Set<String> commit(@NotNull List<Change> changes, @NotNull String message) {
Set<String> feedback = new HashSet<>();
throwIfNotEmpty(vcs.getCheckinEnvironment().commit(changes, message, new CommitContext(), feedback));
return feedback;
}
protected void rollback(@NotNull List<Change> changes) {
List<VcsException> exceptions = new ArrayList<>();
vcs.createRollbackEnvironment().rollbackChanges(changes, exceptions, RollbackProgressListener.EMPTY);
throwIfNotEmpty(exceptions);
}
@Override
protected void projectCreated() {
SvnApplicationSettings.getInstance().setCommandLinePath(myClientBinaryPath + File.separator + "svn");
}
@After
public void after() throws Exception {
RunAll.runAll(
this::tearDownChangeListManager,
() -> runInEdtAndWait(this::tearDownProject),
this::tearDownTempDirectoryFixture,
() -> resetCanonicalTempPathCache(ORIGINAL_TEMP_DIRECTORY)
);
}
private void tearDownChangeListManager() {
if (changeListManager != null) {
changeListManager.waitEverythingDoneInTestMode();
}
}
private void tearDownTempDirectoryFixture() throws Exception {
if (myTempDirFixture != null) {
myTempDirFixture.tearDown();
myTempDirFixture = null;
}
}
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 {
MockChangelistBuilder builder = new MockChangelistBuilder();
vcs.getChangeProvider().getChanges(dirtyScope, builder, new EmptyProgressIndicator(), myGate);
return builder.getChanges();
}
protected void undoFileMove() {
undo("VcsTestUtil MoveFile");
}
protected void undoFileRename() {
undo("VcsTestUtil RenameFile");
}
protected void undo() {
undo(null);
}
/**
* @param expectedCommandName Typically - command name from {@link VcsTestUtil}, be wary of ellipsis
*/
private void undo(@Nullable String expectedCommandName) {
runInEdtAndWait(() -> {
final TestDialog oldTestDialog = TestDialogManager.setTestDialog(TestDialog.OK);
try {
UndoManager undoManager = UndoManager.getInstance(myProject);
assertTrue("undo is not available", undoManager.isUndoAvailable(null));
if (expectedCommandName != null) {
String undoText = undoManager.getUndoActionNameAndDescription(null).first;
assumeTrue("Unexpected VFS command on undo stack, suspecting IDEA-182560. Test aborted. Undo on stack: " + undoText,
undoText != null && undoText.contains(expectedCommandName));
}
undoManager.undo(null);
}
finally {
TestDialogManager.setTestDialog(oldTestDialog);
}
});
}
protected void prepareInnerCopy(final boolean anotherRepository) throws Exception {
if (anotherRepository) {
createAnotherRepo();
}
final String mainUrl = myRepoUrl + "/root/source";
final String externalURL = (anotherRepository ? myAnotherRepoUrl : myRepoUrl) + "/root/target";
final SubTree subTree = new SubTree(myWorkingCopyDir);
checkin();
withDisabledChangeListManager(() -> {
final File rootFile = virtualToIoFile(subTree.myRootDir);
delete(rootFile);
delete(new File(myWorkingCopyDir.getPath() + File.separator + ".svn"));
assertDoesntExist(rootFile);
refreshVfs();
File sourceDir = new File(myWorkingCopyDir.getPath(), "source");
File innerDir = new File(sourceDir, "inner1/inner2/inner");
runInAndVerifyIgnoreOutput("co", mainUrl, sourceDir.getPath());
runInAndVerifyIgnoreOutput("co", externalURL, innerDir.getPath());
refreshVfs();
setVcsMappings(createDirectoryMapping(sourceDir));
});
}
public String getTestDataDir() {
return myTestDataDir;
}
protected class SubTree {
public final VirtualFile myBase;
public VirtualFile myRootDir;
public VirtualFile mySourceDir;
public VirtualFile myTargetDir;
public VirtualFile myS1File;
public VirtualFile myS2File;
public final List<VirtualFile> myTargetFiles = new ArrayList<>();
public static final String ourS1Contents = "123";
public static final String ourS2Contents = "abc";
private VirtualFile findChild(final VirtualFile parent, final String name, final String content, boolean create) {
final VirtualFile result = parent.findChild(name);
if (result != null || !create) return result;
return content == null ? createDirInCommand(parent, name) : createFileInCommand(parent, name, content);
}
public SubTree(@NotNull VirtualFile base) {
myBase = base;
refresh(true);
}
public void refresh(boolean create) {
myRootDir = findChild(myBase, "root", null, create);
mySourceDir = findChild(myRootDir, "source", null, create);
myS1File = findChild(mySourceDir, "s1.txt", ourS1Contents, create);
myS2File = findChild(mySourceDir, "s2.txt", ourS2Contents, create);
myTargetDir = findChild(myRootDir, "target", null, create);
myTargetFiles.clear();
for (int i = 0; i < 3; i++) {
myTargetFiles.add(findChild(myTargetDir, "t" + (i + 10) + ".txt", ourS1Contents, create));
}
}
}
public String prepareBranchesStructure() throws Exception {
final String mainUrl = myRepoUrl + "/trunk";
runInAndVerifyIgnoreOutput("mkdir", "-m", "mkdir", mainUrl);
runInAndVerifyIgnoreOutput("mkdir", "-m", "mkdir", myRepoUrl + "/branches");
runInAndVerifyIgnoreOutput("mkdir", "-m", "mkdir", myRepoUrl + "/tags");
String branchUrl = myRepoUrl + "/branches/b1";
withDisabledChangeListManager(() -> {
deleteRecursively(new File(myWorkingCopyDir.getPath() + File.separator + ".svn").toPath());
refreshVfs();
runInAndVerifyIgnoreOutput("co", mainUrl, myWorkingCopyDir.getPath());
enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD);
new SubTree(myWorkingCopyDir);
checkin();
runInAndVerifyIgnoreOutput("copy", "-q", "-m", "coppy", mainUrl, branchUrl);
});
return branchUrl;
}
public void prepareExternal() throws Exception {
prepareExternal(true, true, false);
}
public void prepareExternal(boolean commitExternalDefinition, boolean updateExternal, boolean anotherRepository) throws Exception {
if (anotherRepository) {
createAnotherRepo();
}
final String mainUrl = myRepoUrl + "/root/source";
final String externalURL = (anotherRepository ? myAnotherRepoUrl : myRepoUrl) + "/root/target";
final SubTree subTree = new SubTree(myWorkingCopyDir);
checkin();
withDisabledChangeListManager(() -> {
final File rootFile = virtualToIoFile(subTree.myRootDir);
delete(rootFile);
delete(new File(myWorkingCopyDir.getPath() + File.separator + ".svn"));
assertDoesntExist(rootFile);
refreshVfs();
final File sourceDir = new File(myWorkingCopyDir.getPath(), "source");
runInAndVerifyIgnoreOutput("co", mainUrl, sourceDir.getPath());
CreateExternalAction.addToExternalProperty(vcs, sourceDir, "external", externalURL);
if (updateExternal) {
runInAndVerifyIgnoreOutput("up", sourceDir.getPath());
}
if (commitExternalDefinition) {
runInAndVerifyIgnoreOutput("ci", "-m", "test", sourceDir.getPath());
}
refreshVfs();
setVcsMappings(createDirectoryMapping(sourceDir));
if (updateExternal) {
assertExists(new File(sourceDir, "external"));
}
});
}
private void withDisabledChangeListManager(@NotNull ThrowableRunnable<? extends Exception> action) throws Exception {
changeListManager.waitUntilRefreshed();
changeListManager.forceStopInTestMode();
action.run();
changeListManager.forceGoInTestMode();
refreshSvnMappingsSynchronously();
}
@NotNull
private VcsDirectoryMapping createDirectoryMapping(@NotNull File directory) {
return new VcsDirectoryMapping(toSystemIndependentName(directory.getPath()), vcs.getName());
}
private void createAnotherRepo() throws Exception {
File repo = virtualToIoFile(myTempDirFixture.findOrCreateDir("anotherRepo"));
copyDir(myRepoRoot, repo);
myAnotherRepoUrl = (SystemInfo.isWindows ? "file:///" : "file://") + toSystemIndependentName(repo.getPath());
VirtualFile tmpWcVf = myTempDirFixture.findOrCreateDir("anotherRepoWc");
File tmpWc = virtualToIoFile(tmpWcVf);
runInAndVerifyIgnoreOutput("co", myAnotherRepoUrl, tmpWc.getPath());
new SubTree(tmpWcVf);
runInAndVerifyIgnoreOutput(tmpWc, "add", "root");
runInAndVerifyIgnoreOutput(tmpWc, "ci", "-m", "fff");
delete(tmpWc);
}
protected void imitUpdate() {
vcsManager.getOptions(VcsConfiguration.StandardOption.UPDATE).setValue(false);
final CommonUpdateProjectAction action = new CommonUpdateProjectAction();
action.getTemplatePresentation().setText("1");
action
.actionPerformed(new AnActionEvent(null, SimpleDataContext.getProjectContext(myProject), "test", new Presentation(), ActionManager.getInstance(), 0));
waitChangesAndAnnotations();
}
protected void runAndVerifyStatusSorted(final String... stdoutLines) throws IOException {
runStatusAcrossLocks(myWcRoot, true, map2Array(stdoutLines, String.class, it -> toSystemDependentName(it)));
}
protected void runAndVerifyStatus(final String... stdoutLines) throws IOException {
runStatusAcrossLocks(myWcRoot, false, map2Array(stdoutLines, String.class, it -> toSystemDependentName(it)));
}
private void runStatusAcrossLocks(@Nullable File workingDir, final boolean sorted, final String... stdoutLines) throws IOException {
final Processor<ProcessOutput> primitiveVerifier = output -> {
if (sorted) {
verifySorted(output, stdoutLines);
} else {
verify(output, stdoutLines);
}
return false;
};
runAndVerifyAcrossLocks(workingDir, new String[]{"status"}, 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 output -> {
assertEquals(output.getStderr(), 0, output.getExitCode());
return false;
};
}
public static void runInAndVerifyIgnoreOutput(File workingDir, final TestClientRunner runner, final String[] input) 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 = notNull(workingDir, myWcRoot);
runAndVerifyAcrossLocks(workingDir, myRunner, input, verifier, primitiveVerifier);
}
private 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 && !isEmptyOrSpaces(output.getStderr())) {
final String stderr = output.getStderr();
if (stderr.contains("E155004") && stderr.contains("is already locked")) continue;
}
if (verifier.process(output)) continue;
return;
}
primitiveVerifier.process(runner.runClient("svn", null, workingDir, input));
}
}