blob: 472d3650315afb07e5de4f7330f5c7c670861aef [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2004, 2014 QNX Software Systems Ltd and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* QNX Software Systems Ltd - initial API and implementation
* Anton Leherbauer (Wind River Systems)
* James Blackburn (Broadcom Corp.)
* Marc-Andre Laperle (Ericsson)
***********************************************************************/
package org.eclipse.cdt.core.cdescriptor.tests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.CDescriptorEvent;
import org.eclipse.cdt.core.CProjectNature;
import org.eclipse.cdt.core.ICDescriptor;
import org.eclipse.cdt.core.ICDescriptorListener;
import org.eclipse.cdt.core.ICDescriptorOperation;
import org.eclipse.cdt.core.ICExtensionReference;
import org.eclipse.cdt.core.ICOwnerInfo;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICStorageElement;
import org.eclipse.cdt.core.testplugin.CTestPlugin;
import org.eclipse.cdt.core.testplugin.util.BaseTestCase5;
import org.eclipse.cdt.internal.core.pdom.PDOMManager;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag(BaseTestCase5.FLAKY_TEST_TAG)
public class CDescriptorTests extends BaseTestCase5 {
static String projectId = CTestPlugin.PLUGIN_ID + ".TestProject";
static IProject fProject;
static CDescriptorListener listener = new CDescriptorListener();
static volatile CDescriptorEvent fLastEvent;
@BeforeEach
protected void setUpLocal() throws Exception {
CTestPlugin.getWorkspace().run(new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
IWorkspaceRoot root = CTestPlugin.getWorkspace().getRoot();
IProject project = root.getProject("testDescriptorProject");
if (!project.exists()) {
project.create(null);
} else {
project.refreshLocal(IResource.DEPTH_INFINITE, null);
}
if (!project.isOpen()) {
project.open(null);
}
CCorePlugin.getDefault().getCDescriptorManager().addDescriptorListener(listener);
if (!project.hasNature(CProjectNature.C_NATURE_ID)) {
addNatureToProject(project, CProjectNature.C_NATURE_ID, null);
}
fProject = project;
CTestPlugin.getWorkspace().run(new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
CCorePlugin.getDefault().mapCProjectOwner(fProject, projectId, false);
}
}, null);
}
}, null);
}
@AfterEach
protected void tearDownLocal() throws Exception {
fProject.delete(true, true, null);
}
private static void addNatureToProject(IProject proj, String natureId, IProgressMonitor monitor)
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, monitor);
}
static public class CDescriptorListener implements ICDescriptorListener {
@Override
public void descriptorChanged(CDescriptorEvent event) {
fLastEvent = event;
}
}
@Test
public void testDescriptorCreation() throws Exception {
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
assertNotNull(fLastEvent);
assertEquals(fLastEvent.getDescriptor(), desc);
assertEquals(fLastEvent.getType(), CDescriptorEvent.CDTPROJECT_ADDED);
assertEquals(fLastEvent.getFlags(), 0);
fLastEvent = null;
assertEquals(fProject, desc.getProject());
assertEquals("*", desc.getPlatform());
}
@Test
public void testDescriptorOwner() throws Exception {
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
ICOwnerInfo owner = desc.getProjectOwner();
assertEquals(projectId, owner.getID());
assertEquals("*", owner.getPlatform());
assertEquals("C/C++ Test Project", owner.getName());
}
// Disabled this test because it fails every now and then and it tests deprecated API
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=340123
// @Test
public void _testConcurrentDescriptorCreation() throws Exception {
for (int i = 0; i < 100; i++) {
fProject.close(null);
fProject.open(null);
Thread t = new Thread() {
@Override
public void run() {
try {
CCorePlugin.getDefault().getCProjectDescription(fProject, true);
} catch (CoreException exc) {
fail();
}
}
};
t.start();
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
try {
t.join();
} catch (InterruptedException e) {
}
ICStorageElement data = desc.getProjectStorageElement("testElement0");
data.createChild("test");
desc.saveProjectData();
fLastEvent = null;
}
}
/*
* This tests concurrent descriptor modification inside of a ICDescriptor operation run
* with
* CConfigBasedDescriptorManager.runDescriptorOperation(IProject project, ICDescriptorOperation op, IProgressMonitor monitor)
*/
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=185930
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=193503
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=196118
@Test
public void testConcurrentDescriptorModification() throws Exception {
int lastLength = 0;
for (int i = 0; i < 100; ++i) {
final int indexi = i;
PDOMManager pdomMgr = (PDOMManager) CCorePlugin.getIndexManager();
pdomMgr.shutdown();
fProject.close(null);
fProject.open(null);
pdomMgr.startup().schedule();
final ICDescriptor fdesc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
if (lastLength == 0)
lastLength = fdesc.getProjectStorageElement("testElement").getChildren().length;
final Throwable[] exception = new Throwable[10];
Thread[] threads = new Thread[10];
for (int j = 0; j < 10; j++) {
final int indexj = j;
Thread t = new Thread() {
@Override
public void run() {
try {
ICDescriptorOperation operation = new ICDescriptorOperation() {
@Override
public void execute(ICDescriptor descriptor, IProgressMonitor monitor)
throws CoreException {
// assertFalse(descriptor.getConfigurationDescription().isReadOnly());
ICStorageElement data = fdesc.getProjectStorageElement("testElement");
String test = "test" + (indexi * 10 + indexj);
data.createChild(test);
// assertFalse(descriptor.getConfigurationDescription().isReadOnly());
descriptor.saveProjectData();
// System.out.println("Saved " + test);
}
};
CCorePlugin.getDefault().getCDescriptorManager().runDescriptorOperation(fProject, operation,
null);
} catch (Throwable exc) {
exception[indexj] = exc;
exc.printStackTrace();
}
}
};
t.start();
threads[j] = t;
}
for (int j = 0; j < threads.length; j++) {
if (threads[j] != null) {
try {
threads[j].join();
} catch (InterruptedException e) {
}
}
assertNull(exception[j], "Exception occurred: " + exception[j]);
}
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
int lengthAfter = desc.getProjectStorageElement("testElement").getChildren().length;
lastLength += threads.length; // Update last lengths to what we expect
assertEquals(lastLength, lengthAfter, "Iteration count: " + i);
fLastEvent = null;
}
}
/*
* This test should pass as two threads, operating on the different storage elements
* (outside of an operation) should be safe.
*/
@Test
public void testConcurrentDifferentStorageElementModification() throws Exception {
for (int i = 0; i < 100; ++i) {
Thread t = new Thread() {
@Override
public void run() {
try {
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
ICStorageElement data = desc.getProjectStorageElement("testElement4");
data.createChild("test");
desc.saveProjectData();
} catch (CoreException exc) {
fail(exc.getMessage());
}
}
};
t.start();
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
ICStorageElement data = desc.getProjectStorageElement("testElement5");
data.createChild("test");
desc.saveProjectData();
try {
t.join();
} catch (InterruptedException e) {
}
fLastEvent = null;
}
assertEquals(100, CCorePlugin.getDefault().getCProjectDescription(fProject, false)
.getProjectStorageElement("testElement4").getChildren().length);
assertEquals(100, CCorePlugin.getDefault().getCProjectDescription(fProject, false)
.getProjectStorageElement("testElement5").getChildren().length);
}
/*
* Tests that (non-structural) changes to the storage element tree work as expected.
*/
@Test
public void testConcurrentSameStorageElementModification() throws Exception {
for (int i = 0; i < 100; ++i) {
Thread t = new Thread() {
@Override
public void run() {
try {
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
ICStorageElement data = desc.getProjectStorageElement("testElement6");
data.createChild("test");
desc.saveProjectData();
} catch (CoreException exc) {
fail(exc.getMessage());
}
}
};
t.start();
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
ICStorageElement data = desc.getProjectStorageElement("testElement6");
data.createChild("test");
desc.saveProjectData();
try {
t.join();
} catch (InterruptedException e) {
}
fLastEvent = null;
}
assertEquals(200, CCorePlugin.getDefault().getCProjectDescription(fProject, false)
.getProjectStorageElement("testElement6").getChildren().length);
}
/*
* Tests deadlock when accessing c project description concurrently from two threads
*/
@Test
public void testDeadlockDuringProjectCreation() throws Exception {
for (int i = 0; i < 10; ++i) {
tearDownLocal();
setUpLocal();
Thread t = new Thread() {
@Override
public void run() {
try {
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
ICStorageElement data = desc.getProjectStorageElement("testElement0");
data.createChild("test");
desc.saveProjectData();
} catch (Exception exc) {
}
}
};
t.start();
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
ICStorageElement data = desc.getProjectStorageElement("testElement0");
data.createChild("test");
desc.saveProjectData();
try {
t.join();
} catch (InterruptedException e) {
}
fLastEvent = null;
}
}
@Test
public void testDescriptorConversion() {
}
@Test
public void testExtensionCreation() throws Exception {
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
ICExtensionReference extRef = desc.create("org.eclipse.cdt.testextension", "org.eclipse.cdt.testextensionID");
assertNotNull(fLastEvent);
assertEquals(fLastEvent.getDescriptor(), desc);
assertEquals(fLastEvent.getType(), CDescriptorEvent.CDTPROJECT_CHANGED);
assertEquals(fLastEvent.getFlags(), CDescriptorEvent.EXTENSION_CHANGED);
fLastEvent = null;
assertEquals("org.eclipse.cdt.testextension", extRef.getExtension());
assertEquals("org.eclipse.cdt.testextensionID", extRef.getID());
}
@Test
public void testExtensionGet() throws Exception {
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
desc.create("org.eclipse.cdt.testextension", "org.eclipse.cdt.testextensionID");
ICExtensionReference extRef[] = desc.get("org.eclipse.cdt.testextension");
assertEquals("org.eclipse.cdt.testextension", extRef[0].getExtension());
assertEquals("org.eclipse.cdt.testextensionID", extRef[0].getID());
}
@Test
public void testExtensionData() throws Exception {
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
desc.create("org.eclipse.cdt.testextension", "org.eclipse.cdt.testextensionID");
ICExtensionReference extRef[] = desc.get("org.eclipse.cdt.testextension");
extRef[0].setExtensionData("testKey", "testValue");
assertNotNull(fLastEvent);
assertEquals(fLastEvent.getDescriptor(), desc);
assertEquals(fLastEvent.getType(), CDescriptorEvent.CDTPROJECT_CHANGED);
assertEquals(fLastEvent.getFlags(), 0);
fLastEvent = null;
assertEquals("testValue", extRef[0].getExtensionData("testKey"));
extRef[0].setExtensionData("testKey", null);
assertEquals(null, extRef[0].getExtensionData("testKey"));
}
@Test
public void testExtensionRemove() throws Exception {
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
desc.create("org.eclipse.cdt.testextension", "org.eclipse.cdt.testextensionID");
ICExtensionReference extRef[] = desc.get("org.eclipse.cdt.testextension");
desc.remove(extRef[0]);
assertNotNull(fLastEvent);
assertEquals(fLastEvent.getDescriptor(), desc);
assertEquals(fLastEvent.getType(), CDescriptorEvent.CDTPROJECT_CHANGED);
assertEquals(fLastEvent.getFlags(), CDescriptorEvent.EXTENSION_CHANGED);
fLastEvent = null;
}
@Test
public void testProjectDataCreate() throws Exception {
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
ICStorageElement data = desc.getProjectStorageElement("testElement");
data.createChild("test");
desc.saveProjectData();
assertNotNull(fLastEvent);
assertEquals(fLastEvent.getDescriptor(), desc);
assertEquals(fLastEvent.getType(), CDescriptorEvent.CDTPROJECT_CHANGED);
assertEquals(fLastEvent.getFlags(), 0);
fLastEvent = null;
}
@Test
public void testProjectDataDelete() throws Exception {
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
ICStorageElement data = desc.getProjectStorageElement("testElement");
data.createChild("test");
ICStorageElement[] list = data.getChildrenByName("test");
assertEquals(1, list.length);
data.removeChild(list[0]);
desc.saveProjectData();
assertNotNull(fLastEvent);
assertEquals(fLastEvent.getDescriptor(), desc);
assertEquals(fLastEvent.getType(), CDescriptorEvent.CDTPROJECT_CHANGED);
assertEquals(fLastEvent.getFlags(), 0);
fLastEvent = null;
}
@Test
public void testCProjectDescriptionDescriptorInteraction() throws Exception {
for (int i = 1; i < 100; i++) {
// Create a descriptor with some test data
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
ICStorageElement data = desc.getProjectStorageElement("descDescInteraction");
data.createChild("dataItem1");
// Get the CProjectDescription
ICProjectDescription projDesc = CCorePlugin.getDefault().getProjectDescription(fProject);
data = desc.getProjectStorageElement("descDescInteraction");
data.createChild("dataItem2");
data = desc.getProjectStorageElement("descDescInteraction2");
data.createChild("dataItem3");
// save the descriptor
desc.saveProjectData();
// save the project description
CCorePlugin.getDefault().setProjectDescription(fProject, projDesc);
fProject.close(null);
assertTrue(CCorePlugin.getDefault().getCProjectDescription(fProject, false) == null);
fProject.open(null);
// Check that the descriptor added data is still there
desc = CCorePlugin.getDefault().getCProjectDescription(fProject, false);
data = desc.getProjectStorageElement("descDescInteraction");
assertEquals(2 * i, data.getChildren().length);
data = desc.getProjectStorageElement("descDescInteraction2");
assertEquals(1 * i, data.getChildren().length);
}
}
@Test
public void testAccumulatingBlankLinesInProjectData() throws Exception {
ICDescriptor desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
ICStorageElement data = desc.getProjectStorageElement("testElement");
data.createChild("test");
desc.saveProjectData();
fProject.close(null);
fProject.open(null);
String dotCProject1 = readDotCProjectFile(fProject);
long mtime1 = fProject.getFile(".cproject").getLocalTimeStamp();
desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
data = desc.getProjectStorageElement("testElement");
for (ICStorageElement child : data.getChildren()) {
data.removeChild(child);
}
data.createChild("test");
desc.saveProjectData();
String dotCProject2 = readDotCProjectFile(fProject);
long mtime2 = fProject.getFile(".cproject").getLocalTimeStamp();
assertEquals(dotCProject1, dotCProject2, "Difference in .cproject file");
assertTrue(mtime1 == mtime2, ".cproject file has been written");
// do it a second time - just to be sure
fProject.close(null);
fProject.open(null);
desc = CCorePlugin.getDefault().getCProjectDescription(fProject, true);
data = desc.getProjectStorageElement("testElement");
for (ICStorageElement child : data.getChildren()) {
data.removeChild(child);
}
data.createChild("test");
desc.saveProjectData();
String dotCProject3 = readDotCProjectFile(fProject);
long mtime3 = fProject.getFile(".cproject").getLocalTimeStamp();
assertEquals(dotCProject2, dotCProject3, "Difference in .cproject file");
assertTrue(mtime2 == mtime3, ".cproject file has been written");
}
/**
* Read .cproject file.
*
* @param project
* @return content of .cproject file
* @throws CoreException
* @throws IOException
*/
private static String readDotCProjectFile(IProject project) throws CoreException, IOException {
IFile cProjectFile = project.getFile(".cproject");
InputStream in = cProjectFile.getContents();
try {
Reader reader = new InputStreamReader(in, "UTF-8");
StringBuilder sb = new StringBuilder();
char[] b = new char[4096];
int n;
while ((n = reader.read(b)) > 0) {
sb.append(b, 0, n);
}
return sb.toString();
} finally {
in.close();
}
}
}