/******************************************************************************* | |
* Copyright (c) 2010, 2011 Obeo. | |
* 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 | |
* | |
* Contributors: | |
* Obeo - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.mylyn.docs.intent.client.ui.test.util; | |
import com.google.common.collect.Sets; | |
import com.google.common.collect.Sets.SetView; | |
import java.io.File; | |
import java.io.IOException; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Modifier; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.List; | |
import java.util.Set; | |
import junit.framework.AssertionFailedError; | |
import junit.framework.TestCase; | |
import org.eclipse.core.resources.IFolder; | |
import org.eclipse.core.resources.IProject; | |
import org.eclipse.core.resources.IResource; | |
import org.eclipse.core.resources.IWorkspace; | |
import org.eclipse.core.resources.IWorkspaceDescription; | |
import org.eclipse.core.resources.IWorkspaceRunnable; | |
import org.eclipse.core.resources.ResourcesPlugin; | |
import org.eclipse.core.runtime.CoreException; | |
import org.eclipse.core.runtime.ILogListener; | |
import org.eclipse.core.runtime.IProgressMonitor; | |
import org.eclipse.core.runtime.IStatus; | |
import org.eclipse.core.runtime.NullProgressMonitor; | |
import org.eclipse.emf.common.util.URI; | |
import org.eclipse.emf.ecore.EObject; | |
import org.eclipse.emf.ecore.resource.Resource; | |
import org.eclipse.emf.ecore.resource.ResourceSet; | |
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; | |
import org.eclipse.mylyn.docs.intent.client.ui.IntentEditorActivator; | |
import org.eclipse.mylyn.docs.intent.client.ui.editor.IntentEditor; | |
import org.eclipse.mylyn.docs.intent.client.ui.ide.builder.IntentNature; | |
import org.eclipse.mylyn.docs.intent.client.ui.ide.builder.ToggleNatureAction; | |
import org.eclipse.mylyn.docs.intent.client.ui.ide.launcher.IDEApplicationManager; | |
import org.eclipse.mylyn.docs.intent.client.ui.utils.IntentEditorOpener; | |
import org.eclipse.mylyn.docs.intent.collab.common.IntentRepositoryManager; | |
import org.eclipse.mylyn.docs.intent.collab.common.location.IntentLocations; | |
import org.eclipse.mylyn.docs.intent.collab.handlers.RepositoryObjectHandler; | |
import org.eclipse.mylyn.docs.intent.collab.handlers.adapters.ReadOnlyException; | |
import org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter; | |
import org.eclipse.mylyn.docs.intent.collab.handlers.impl.ReadWriteRepositoryObjectHandlerImpl; | |
import org.eclipse.mylyn.docs.intent.collab.handlers.impl.notification.elementList.ElementListAdapter; | |
import org.eclipse.mylyn.docs.intent.collab.handlers.impl.notification.elementList.ElementListNotificator; | |
import org.eclipse.mylyn.docs.intent.collab.handlers.impl.notification.typeListener.TypeNotificator; | |
import org.eclipse.mylyn.docs.intent.collab.handlers.notification.Notificator; | |
import org.eclipse.mylyn.docs.intent.collab.repository.Repository; | |
import org.eclipse.mylyn.docs.intent.collab.repository.RepositoryConnectionException; | |
import org.eclipse.mylyn.docs.intent.core.compiler.CompilerPackage; | |
import org.eclipse.mylyn.docs.intent.core.document.IntentChapter; | |
import org.eclipse.mylyn.docs.intent.core.document.IntentDocument; | |
import org.eclipse.mylyn.docs.intent.core.document.IntentSection; | |
import org.eclipse.mylyn.docs.intent.core.document.IntentStructuredElement; | |
import org.eclipse.mylyn.docs.intent.parser.modelingunit.test.utils.FileToStringConverter; | |
import org.eclipse.ui.PlatformUI; | |
/** | |
* An abstract test class providing API for manage an Intent IDE projects and editors. | |
* | |
* @author <a href="mailto:alex.lagarde@obeo.fr">Alex Lagarde</a> | |
*/ | |
public abstract class AbstractIntentUITest extends TestCase implements ILogListener { | |
public static final String INTENT_NEW_PROJECT_WIZARD_ID = "org.eclipse.mylyn.docs.intent.client.ui.ide.wizards.NewIntentProjectWizard"; | |
protected IProject intentProject; | |
protected Repository repository; | |
protected RepositoryAdapter repositoryAdapter; | |
protected RepositoryListenerForTests repositoryListener; | |
private IntentDocument intentDocument; | |
private List<IntentEditor> openedEditors; | |
/** | |
* {@inheritDoc} | |
* | |
* @see junit.framework.TestCase#setUp() | |
*/ | |
@Override | |
protected void setUp() throws Exception { | |
initClassLoader(); | |
// Step 1 : printing testclass name (make hudson debugs easier) | |
for (int i = 0; i < getClass().getName().length() - 1; i++) { | |
System.out.print("="); | |
} | |
System.out.println("="); | |
System.out.println(getClass().getName()); | |
super.setUp(); | |
// Step 2 : deactivating the automatic build of the workspace (if needed) | |
IWorkspace workspace = ResourcesPlugin.getWorkspace(); | |
IWorkspaceDescription description = workspace.getDescription(); | |
if (description.isAutoBuilding()) { | |
description.setAutoBuilding(false); | |
workspace.setDescription(description); | |
} | |
// Step 3 : clean workspace, close welcome page | |
WorkspaceUtils.cleanWorkspace(); | |
WorkspaceUtils.closeWelcomePage(); | |
waitForAllOperationsInUIThread(); | |
IntentEditorActivator.getDefault().getLog().addLogListener(this); | |
System.out.println("-- SETTED UP.;"); | |
traceHeapSize(); | |
openedEditors = new ArrayList<IntentEditor>(); | |
} | |
/** | |
* Forces OSGI to load ui.ide plugin first. Otherwise, IntentRepositoryManager.INSTANCE.getRepository is | |
* called twice at the same time (?). Happens only in the test suite. | |
*/ | |
private void initClassLoader() { | |
assertNotNull(IntentNature.class); | |
} | |
private void traceHeapSize() { | |
long maxHeapSize = Runtime.getRuntime().maxMemory(); | |
long allocatedHeapSize = Runtime.getRuntime().totalMemory(); | |
long usedHeap = allocatedHeapSize - Runtime.getRuntime().freeMemory(); | |
System.out.println(" Heap size : " + usedHeap + "/" + allocatedHeapSize + "(" | |
+ Math.ceil(usedHeap * 100 / allocatedHeapSize) + "% - " | |
+ Math.ceil(usedHeap * 100 / maxHeapSize) + "% of max heap size)"); | |
} | |
/** | |
* {@inheritDoc} | |
* | |
* @see junit.framework.TestCase#tearDown() | |
*/ | |
@Override | |
protected void tearDown() throws Exception { | |
waitForAllOperationsInUIThread(); | |
// Step 1 : close editors | |
for (IntentEditor editor : openedEditors) { | |
editor.close(false); | |
} | |
// Step 2 : clean workspace | |
if (intentProject != null) { | |
IntentRepositoryManager.INSTANCE.deleteRepository(intentProject.getName()); | |
waitForAllOperationsInUIThread(); | |
intentProject.delete(true, true, new NullProgressMonitor()); | |
} | |
IntentEditorActivator.getDefault().getLog().removeLogListener(this); | |
WorkspaceUtils.cleanWorkspace(); | |
// Step 3 : setting all fields to null (to avoid memory leaks) | |
setAllFieldsToNull(); | |
super.tearDown(); | |
long totalHeapSize = Runtime.getRuntime().totalMemory(); | |
long usedHeap = totalHeapSize - Runtime.getRuntime().freeMemory(); | |
System.out.println("-- TEARED DOWN."); | |
traceHeapSize(); | |
} | |
/** | |
* Creates and register an new {@link org.eclipse.mylyn.docs.intent.collab.handlers.RepositoryClient } in | |
* charge of detecting that events happened on the repository. | |
*/ | |
protected void registerRepositoryListener() { | |
// Step 1 : creating the handler | |
// we listen for all modifications made on the traceability index | |
RepositoryObjectHandler handler = new ReadWriteRepositoryObjectHandlerImpl(repositoryAdapter); | |
if (getIntentDocument() == null) { | |
fail("Cannot register a repository listener without having setted up an Intent Document"); | |
} | |
Set<EObject> listenedElements = Sets.newLinkedHashSet(); | |
listenedElements.add(getIntentDocument()); | |
listenedElements.add(handler.getRepositoryAdapter() | |
.getResource(IntentLocations.TRACEABILITY_INFOS_INDEX_PATH).getContents().iterator().next()); | |
Notificator elementNotificator = new ElementListNotificator(listenedElements, | |
new ElementListAdapter(), repositoryAdapter); | |
Notificator compilationStatusNotificator = new TypeNotificator( | |
Sets.newLinkedHashSet(CompilerPackage.eINSTANCE.getCompilationStatusManager() | |
.getEAllStructuralFeatures())); | |
handler.addNotificator(elementNotificator); | |
handler.addNotificator(compilationStatusNotificator); | |
// Step 2 : creating the client | |
this.repositoryListener = new RepositoryListenerForTests(); | |
repositoryListener.addRepositoryObjectHandler(handler); | |
} | |
/** | |
* Creates a new Intent project using the intent document located at the given path. | |
* | |
* @param projectName | |
* the intent project name | |
* @param intentDocumentPath | |
* the path of the intent document to use (relative to the | |
* org.eclipse.mylyn.docs.intent.client.ui.test project). | |
*/ | |
protected void setUpIntentProject(final String projectName, String intentDocumentPath) { | |
setUpIntentProject(projectName, intentDocumentPath, false); | |
} | |
/** | |
* Creates a new Intent project using the intent document located at the given path. | |
* | |
* @param projectName | |
* the intent project name | |
* @param intentDocumentPath | |
* the path of the intent document to use (relative to the | |
* org.eclipse.mylyn.docs.intent.client.ui.test project) | |
* @param listenForRepository | |
* indicates whether a repository listener should be registered. If you want to determine if a | |
* client has done a job (by calling {@link AbstractIntentUITest#waitForIndexer() for example} | |
* ), this must be true. | |
*/ | |
protected void setUpIntentProject(final String projectName, String intentDocumentPath, | |
boolean listenForRepository) { | |
try { | |
// Step 1 : getting the content of the intent document located at the given path. | |
File file = new File(intentDocumentPath); | |
final String intentDocumentContent = FileToStringConverter.getFileAsString(file); | |
// Step 2 : creating the intent project | |
IWorkspaceRunnable create = new IWorkspaceRunnable() { | |
public void run(IProgressMonitor monitor) throws CoreException { | |
IProject project = WorkspaceUtils.createProject(projectName, monitor); | |
ToggleNatureAction.toggleNature(project); | |
IDEApplicationManager.initializeContent(project, intentDocumentContent); | |
// Step 3 : initializing all useful informations | |
intentProject = project; | |
setUpRepository(project); | |
} | |
}; | |
ResourcesPlugin.getWorkspace().run(create, null); | |
while (repositoryAdapter == null | |
// && (repository == null || ((WorkspaceRepository)repository).getEditingDomain() | |
// .getCommandStack() == null) | |
) { | |
Thread.sleep(10); | |
} | |
// Step 3 : registering the repository listener | |
registerRepositoryListener(); | |
} catch (CoreException e) { | |
AssertionFailedError error = new AssertionFailedError("Failed to create Intent project"); | |
error.setStackTrace(e.getStackTrace()); | |
throw error; | |
} catch (IOException e) { | |
AssertionFailedError error = new AssertionFailedError( | |
"Failed to get content of intent document '" + intentDocumentPath + "'"); | |
error.setStackTrace(e.getStackTrace()); | |
throw error; | |
} catch (InterruptedException e) { | |
AssertionFailedError error = new AssertionFailedError("Failed to create Intent project"); | |
error.setStackTrace(e.getStackTrace()); | |
throw error; | |
} | |
} | |
/** | |
* Set up the repository for the given project. | |
* | |
* @param project | |
* the project | |
*/ | |
protected void setUpRepository(IProject project) { | |
assertNotNull(project); | |
try { | |
repository = IntentRepositoryManager.INSTANCE.getRepository(project.getName()); | |
assertNotNull(repository); | |
repositoryAdapter = repository.createRepositoryAdapter(); | |
} catch (RepositoryConnectionException e) { | |
AssertionFailedError error = new AssertionFailedError( | |
"Cannot connect to the created IntentRepository"); | |
error.setStackTrace(e.getStackTrace()); | |
throw error; | |
} catch (CoreException e) { | |
AssertionFailedError error = new AssertionFailedError( | |
"Cannot retrieve the correct IntentRepository type"); | |
error.setStackTrace(e.getStackTrace()); | |
throw error; | |
} | |
// wait for initialization completed | |
waitForAllOperationsInUIThread(); | |
} | |
/** | |
* Loads the {@link IntentStructuredElement} located at the given path. If it contains an IntentDocument, | |
* also updates the intentDocument field. | |
* | |
* @param intentDocumentModelPath | |
* the path of the Intent document model (from | |
* org.eclipse.mylyn.docs.intent.client.ui.test/data) | |
* @return the loaded {@link IntentStructuredElement} | |
*/ | |
protected IntentStructuredElement loadIntentDocumentFromTests(String intentDocumentModelPath) { | |
ResourceSet rs = new ResourceSetImpl(); | |
URI documentURI = URI.createURI("platform:/plugin/org.eclipse.mylyn.docs.intent.client.ui.test/data/" | |
+ intentDocumentModelPath); | |
Resource documentResource = rs.getResource(documentURI, true); | |
if (documentResource != null && documentResource.getContents().iterator().hasNext() | |
&& documentResource.getContents().iterator().next() instanceof IntentStructuredElement) { | |
if (documentResource.getContents().iterator().next() instanceof IntentDocument) { | |
intentDocument = (IntentDocument)documentResource.getContents().iterator().next(); | |
} | |
return (IntentStructuredElement)documentResource.getContents().iterator().next(); | |
} | |
throw new AssertionFailedError("Could not load Intent model at " + intentDocumentModelPath); | |
} | |
/** | |
* Returns the intentDocument associated to the current Intent project. | |
* | |
* @return the intentDocument associated to the current Intent project | |
*/ | |
protected IntentDocument getIntentDocument() { | |
if (intentDocument == null) { | |
try { | |
Resource documentResource = repositoryAdapter | |
.getOrCreateResource(IntentLocations.INTENT_INDEX); | |
assertTrue("Invalid content of resource '" + IntentLocations.INTENT_INDEX + "'", | |
documentResource.getContents().iterator().next() instanceof IntentDocument); | |
intentDocument = (IntentDocument)documentResource.getContents().iterator().next(); | |
} catch (ReadOnlyException e) { | |
// Cannot happen in the test context : no readonly access | |
} | |
} | |
return intentDocument; | |
} | |
/** | |
* Return the chapter at the given number. | |
* | |
* @param number | |
* the number of the chapter | |
* @return the chapter | |
*/ | |
protected IntentChapter getIntentChapter(int number) { | |
return getIntentDocument().getChapters().get(number - 1); | |
} | |
/** | |
* Return the section at the given number. | |
* | |
* @param number | |
* the number of the section | |
* @return the section | |
*/ | |
protected IntentSection getIntentSection(int... number) { | |
IntentSection section = getIntentChapter(number[0]).getSubSections().get(number[1] - 1); | |
if (number.length > 2) { | |
for (int i = 2; i < number.length; i++) { | |
section = section.getSubSections().get(number[i] - 1); | |
} | |
} | |
return section; | |
} | |
/** | |
* Opens an editor on the Document contained in the intent project. | |
* | |
* @return the opened editor | |
*/ | |
protected IntentEditor openIntentEditor() { | |
return openIntentEditor(getIntentDocument()); | |
} | |
/** | |
* Opens an editor on the given {@link IntentStructuredElement}. | |
* | |
* @param element | |
* the {@link IntentStructuredElement} to open an editor on | |
* @return the opened editor | |
*/ | |
protected IntentEditor openIntentEditor(IntentStructuredElement element) { | |
IntentEditorOpener.openIntentEditor(repository, element, true, null, true); | |
waitForAllOperationsInUIThread(); | |
IntentEditor editor = IntentEditorOpener.getAlreadyOpenedEditor(element); | |
assertNotNull(editor); | |
openedEditors.add(editor); | |
return editor; | |
} | |
/** | |
* {@inheritDoc} | |
* | |
* @see org.eclipse.core.runtime.ILogListener#logging(org.eclipse.core.runtime.IStatus, java.lang.String) | |
*/ | |
public void logging(IStatus status, String plugin) { | |
if (status.getSeverity() == IStatus.ERROR) { | |
fail(status.getMessage()); | |
} | |
} | |
/** | |
* Wait until the end of all asynchronous operations launched in the UI Thread. | |
*/ | |
protected static void waitForAllOperationsInUIThread() { | |
while (PlatformUI.getWorkbench().getDisplay().readAndDispatch()) { | |
try { | |
Thread.sleep(10); | |
} catch (InterruptedException e) { | |
// Nothing to do | |
} | |
} | |
} | |
/** | |
* Wait for synchronizer to complete work. | |
*/ | |
protected void waitForSynchronizer() { | |
waitForSynchronizer(true); | |
} | |
/** | |
* Wait for repository to complete work. | |
*/ | |
protected void waitForIndexer() { | |
waitForIndexer(true); | |
} | |
/** | |
* Wait for compiler to complete work. | |
*/ | |
protected void waitForCompiler() { | |
waitForCompiler(true); | |
} | |
/** | |
* Ensures that the synchronizer has been launched or not, according to the given boolean. | |
* | |
* @param compilerShouldBeNotified | |
* indicates whether the synchronizer should be notified or not | |
*/ | |
protected void waitForSynchronizer(boolean synchronizerShouldBeNotified) { | |
waitForAllOperationsInUIThread(); | |
assertNotNull( | |
"Cannot wait for synchronizer : you need to initialize a repository listener by calling the registerRepositoryListener() method", | |
repositoryListener); | |
if (synchronizerShouldBeNotified) { | |
assertTrue("Time out : synchronizer should have handle changes but did not", | |
repositoryListener.waitForModificationOn(IntentLocations.COMPILATION_STATUS_INDEX_PATH)); | |
} else { | |
assertFalse("Synchonizer should not have been notifed", | |
repositoryListener.waitForModificationOn(IntentLocations.COMPILATION_STATUS_INDEX_PATH)); | |
} | |
waitForAllOperationsInUIThread(); | |
} | |
/** | |
* Ensures that the indexer has been launched or not, according to the given boolean. | |
* | |
* @param compilerShouldBeNotified | |
* indicates whether the indexer should be notified or not | |
*/ | |
protected void waitForIndexer(boolean indexerShouldBeNotified) { | |
waitForAllOperationsInUIThread(); | |
assertNotNull( | |
"Cannot wait for Indexer : you need to initialize a repository listener by calling the registerRepositoryListener() method", | |
repositoryListener); | |
if (indexerShouldBeNotified) { | |
assertTrue("Time out : indexer should have handle changes but did not", | |
repositoryListener.waitForModificationOn(IntentLocations.GENERAL_INDEX_PATH)); | |
} else { | |
assertFalse("Indexer should not have been notifed", | |
repositoryListener.waitForModificationOn(IntentLocations.GENERAL_INDEX_PATH)); | |
} | |
waitForAllOperationsInUIThread(); | |
} | |
/** | |
* Ensures that the compiler has been launched or not, according to the given boolean. | |
* | |
* @param compilerShouldBeNotified | |
* indicates whether the compiler should be notified or not | |
*/ | |
protected void waitForCompiler(boolean compilerShouldBeNotified) { | |
waitForAllOperationsInUIThread(); | |
assertNotNull( | |
"Cannot wait for compiler : you need to initialize a repository listener by calling the registerRepositoryListener() method", | |
repositoryListener); | |
if (compilerShouldBeNotified) { | |
assertTrue("Time out : compiler should have handle changes but did not", | |
repositoryListener.waitForModificationOn(IntentLocations.TRACEABILITY_INFOS_INDEX_PATH)); | |
} else { | |
assertFalse("Compiler should not have been notifed", | |
repositoryListener.waitForModificationOn(IntentLocations.TRACEABILITY_INFOS_INDEX_PATH)); | |
} | |
waitForAllOperationsInUIThread(); | |
} | |
/** | |
* Sets all fields of the current test case to null (to avoid memory leaks). | |
*/ | |
private void setAllFieldsToNull() { | |
// For all fields defined in the current test and all its superclasses | |
for (Class<?> clazz = this.getClass(); clazz != TestCase.class; clazz = clazz.getSuperclass()) { | |
for (Field field : clazz.getDeclaredFields()) { | |
boolean isReference = !field.getType().isPrimitive(); | |
try { | |
field.setAccessible(true); | |
boolean isSet = field.get(this) != null; | |
// We do not clean non set references | |
if (isReference && isSet) { | |
boolean isFinal = Modifier.isFinal(field.getModifiers()); | |
// We do not clean final fields | |
if (!isFinal) { | |
// Setting the field to null | |
field.set(this, null); | |
} | |
} | |
} catch (IllegalArgumentException e) { | |
// Do nothing | |
} catch (IllegalAccessException e) { | |
// Do nothing | |
} | |
} | |
} | |
} | |
/** | |
* Checks that the structure of the repository is conform to the given specifications. | |
* | |
* @param expectedChapterNames | |
* the list of chapters resources that should be in the repository | |
* @param expectedSectionNames | |
* the list of section resources that should be in the repository | |
* @param expectedMUNames | |
* the list of Modeling unit resources that should be in the repository | |
*/ | |
protected void checkRepositoryStructure(Collection<String> expectedChapterNames, | |
Collection<String> expectedSectionNames, Collection<String> expectedMUNames) { | |
IFolder documentFolder = intentProject.getFolder(".repository/" + IntentLocations.INTENT_FOLDER); | |
try { | |
documentFolder.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); | |
IFolder chapterFolder = documentFolder.getFolder("IntentChapter"); | |
IFolder sectionFolder = documentFolder.getFolder("IntentSection"); | |
IFolder MUFolder = documentFolder.getFolder("ModelingUnit"); | |
checkFolderStructure(chapterFolder, Sets.newLinkedHashSet(expectedChapterNames)); | |
checkFolderStructure(sectionFolder, Sets.newLinkedHashSet(expectedSectionNames)); | |
checkFolderStructure(MUFolder, Sets.newLinkedHashSet(expectedMUNames)); | |
} catch (CoreException e) { | |
fail(e.getMessage()); | |
} | |
} | |
/** | |
* Ensures that the given folder contains exactly the given expected resources. | |
* | |
* @param folder | |
* the folder to check | |
* @param expectedResourcesName | |
* the expected resources | |
*/ | |
protected void checkFolderStructure(IFolder folder, Set<String> expectedResourcesName) { | |
Set<String> folderContent = Sets.newLinkedHashSet(); | |
try { | |
if (folder.exists()) { | |
// Collecting the resources contained by the given folder | |
for (IResource resource : folder.members()) { | |
folderContent.add(resource.getName().replace("." + resource.getFileExtension(), "")); | |
} | |
// Checking that the folder does not contain more resource that the expected ones | |
SetView<String> folderDifferences = Sets.difference(folderContent, expectedResourcesName); | |
assertTrue("The " + folder.getName() + " folder contains too many " + folder.getName() | |
+ "(s) (" + folderDifferences.size() + "): " + folderDifferences.toString(), | |
folderDifferences.isEmpty()); | |
// Checking that the folder does contain all expected ones | |
folderDifferences = Sets.difference(expectedResourcesName, folderContent); | |
assertTrue( | |
"The " + folder.getName() + " folder should contain the following " | |
+ folder.getName() + "(s): " + folderDifferences.toString(), | |
folderDifferences.isEmpty()); | |
} else { | |
assertEquals("The folder " + folder.getName() + " does not contain any content ", | |
Sets.<String> newLinkedHashSet(), expectedResourcesName); | |
} | |
} catch (CoreException e) { | |
fail(e.getMessage()); | |
} | |
} | |
} |