/*******************************************************************************
* Copyright (C) 2015, Thomas Wolf <thomas.wolf@paranor.ch>
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.egit.ui.test.team.actions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceDescription;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.internal.indexdiff.IndexDiffCache;
import org.eclipse.egit.core.op.CommitOperation;
import org.eclipse.egit.core.op.ConnectProviderOperation;
import org.eclipse.egit.ui.common.LocalRepositoryTestCase;
import org.eclipse.egit.ui.internal.actions.ShowBlameActionHandler;
import org.eclipse.egit.ui.test.TestUtil;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jgit.lib.Repository;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
/**
* JUnit plugin test (non-SWTBot) to verify that {@link ShowBlameActionHandler}
* is enabled not only for {@link IResource} but also for other things that
* might be visible in the package explorer and that adapt to {@link IResource}.
* We verify by setting up a Java project with a simple test class and then
* checking for {@link IFile}, {@link ICompilationUnit}, and {@link IType}.
*
* @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=401156">Bug
* 401156</a>
*/
@RunWith(MockitoJUnitRunner.class)
public class ShowBlameActionHandlerTest extends LocalRepositoryTestCase {
private static final String JAVA_PROJECT_NAME = "javatestProject";
private static final String SRC_FOLDER_NAME = "src";
private static final String BIN_FOLDER_NAME = "bin";
private static final String PACKAGE_NAME = "p";
private static final String JAVA_CLASS_NAME = "A";
private static final String JAVA_FILE_NAME = JAVA_CLASS_NAME + ".java";
private static final int MAX_DELETE_RETRY = 5;
private static final int DELETE_RETRY_DELAY = 1000; // ms
private static boolean initialAutobuild;
private IJavaProject javaProject = null;
@BeforeClass
public static void setupAutobuildOff() throws CoreException {
// Switch off autobuild -- we don't need it, and a build job might
// interfere with our removing the Java project at the end.
initialAutobuild = setAutobuild(false);
}
@AfterClass
public static void teardownAutobuildReset() throws CoreException {
setAutobuild(initialAutobuild);
}
@Before
public void setup() throws Exception {
javaProject = createJavaProjectAndCommitToRepository();
}
@After
public void teardown() throws CoreException {
removeJavaProject();
}
@Test
public void testShowAnnotationsFromProjectExplorer() throws Exception {
IProject project = javaProject.getProject();
// Find the file
IFile file = project.getFolder(SRC_FOLDER_NAME).getFolder(PACKAGE_NAME)
.getFile(JAVA_FILE_NAME);
assertBlameEnabled(file, true);
// Now repeat the same with the ICompilationUnit.
IJavaElement element = JavaCore.create(file, javaProject);
assertTrue("Expected an ICompilationUnit",
element instanceof ICompilationUnit);
assertBlameEnabled(element, true);
// And with IType...
IType type = javaProject.findType(PACKAGE_NAME, JAVA_CLASS_NAME);
assertBlameEnabled(type, true);
// ... and finally with something that doesn't adapt to IResource:
assertBlameEnabled(this, false);
}
@SuppressWarnings("boxing")
private void assertBlameEnabled(Object selected, boolean expected) {
assertNotNull("Nothing selected", selected);
IStructuredSelection selection = mock(IStructuredSelection.class);
when(selection.getFirstElement()).thenReturn(selected);
when(selection.size()).thenReturn(1);
ShowBlameActionHandler blame = new ShowBlameActionHandler();
blame.setSelection(selection);
assertEquals("Unexpected enablement of blame action", expected,
blame.isEnabled());
}
// Java stuff below
private static boolean setAutobuild(boolean value) throws CoreException {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceDescription desc = workspace.getDescription();
boolean isAutoBuilding = desc.isAutoBuilding();
if (isAutoBuilding != value) {
desc.setAutoBuilding(value);
workspace.setDescription(desc);
}
return isAutoBuilding;
}
private IJavaProject createJavaProjectAndCommitToRepository()
throws Exception {
Repository myRepository = createLocalTestRepository(REPO1);
File gitDir = myRepository.getDirectory();
IJavaProject jProject = createJavaProject(myRepository,
JAVA_PROJECT_NAME);
IProject project = jProject.getProject();
try {
new ConnectProviderOperation(project, gitDir).execute(null);
} catch (Exception e) {
Activator.logError("Failed to connect project to repository", e);
}
assertConnected(project);
// Check in at least the java file
IFolder folder = project.getFolder(SRC_FOLDER_NAME)
.getFolder(PACKAGE_NAME);
IFile file = folder.getFile(JAVA_FILE_NAME);
IFile[] commitables = new IFile[] { file };
ArrayList<IFile> untracked = new ArrayList<IFile>();
untracked.addAll(Arrays.asList(commitables));
// commit to master
CommitOperation op = new CommitOperation(commitables, untracked,
TestUtil.TESTAUTHOR, TestUtil.TESTCOMMITTER, "Initial commit");
op.execute(null);
// Make sure cache entry is already listening for changes
IndexDiffCache cache = Activator.getDefault().getIndexDiffCache();
cache.getIndexDiffCacheEntry(lookupRepository(gitDir));
return jProject;
}
private IJavaProject createJavaProject(final Repository repository,
final String projectName) throws Exception {
final IJavaProject[] jProjectHolder = new IJavaProject[] { null };
IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IProject project = root.getProject(projectName);
if (project.exists()) {
project.delete(true, null);
TestUtil.waitForJobs(100, 5000);
}
IProjectDescription desc = ResourcesPlugin.getWorkspace()
.newProjectDescription(projectName);
desc.setLocation(
new Path(new File(repository.getWorkTree(), projectName)
.getPath()));
project.create(desc, null);
project.open(null);
TestUtil.waitForJobs(50, 5000);
// Create a "bin" folder
IFolder bin = project.getFolder(BIN_FOLDER_NAME);
if (!bin.exists()) {
bin.create(IResource.FORCE | IResource.DERIVED, true, null);
}
IPath outputLocation = bin.getFullPath();
// Create a "src" folder
IFolder src = project.getFolder(SRC_FOLDER_NAME);
if (!src.exists()) {
src.create(IResource.FORCE, true, null);
}
addNatureToProject(project, JavaCore.NATURE_ID);
// Set up the IJavaProject
IJavaProject jProject = JavaCore.create(project);
IPackageFragmentRoot srcContainer = jProject
.getPackageFragmentRoot(src);
IClasspathEntry srcEntry = JavaCore
.newSourceEntry(srcContainer.getPath());
// Create a JRE classpath entry using the default JRE
IClasspathEntry jreEntry = JavaRuntime
.getDefaultJREContainerEntry();
jProject.setRawClasspath(
new IClasspathEntry[] { srcEntry, jreEntry },
outputLocation, true, null);
// Create a package with a single test class
IPackageFragment javaPackage = srcContainer
.createPackageFragment(PACKAGE_NAME, true, null);
javaPackage
.createCompilationUnit(JAVA_FILE_NAME,
"package " + PACKAGE_NAME + ";\nclass "
+ JAVA_CLASS_NAME + " {\n\n}",
true, null);
jProjectHolder[0] = jProject;
}
};
ResourcesPlugin.getWorkspace().run(runnable, null);
return jProjectHolder[0];
}
private void addNatureToProject(IProject proj, String natureId)
throws CoreException {
IProjectDescription description = proj.getDescription();
String[] prevNatures = description.getNatureIds();
String[] newNatures = new String[prevNatures.length + 1];
System.arraycopy(prevNatures, 0, newNatures, 0, prevNatures.length);
newNatures[prevNatures.length] = natureId;
description.setNatureIds(newNatures);
proj.setDescription(description, null);
}
private void removeJavaProject() throws CoreException {
if (javaProject == null) {
return;
}
final IProject project = javaProject.getProject();
IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
// Following code inspired by {@link
// org.eclipse.jdt.testplugin.JavaProjectHelper#delete(IResource)}.
// I don't like all this sleeping at all, but apparently it's
// needed because the Java indexer might still run and hold on
// to some resources.
for (int i = 0; i < MAX_DELETE_RETRY; i++) {
try {
project.delete(
IResource.FORCE
| IResource.ALWAYS_DELETE_PROJECT_CONTENT,
null);
break;
} catch (CoreException e) {
if (i == MAX_DELETE_RETRY - 1) {
throw e;
}
try {
Activator.logInfo(
"Sleep before retrying to delete project "
+ project.getLocationURI());
// Give other threads the time to close and release
// the resource.
Thread.sleep(DELETE_RETRY_DELAY);
} catch (InterruptedException e1) {
// Ignore and retry to delete
}
}
}
}
};
ResourcesPlugin.getWorkspace().run(runnable, null);
}
}