blob: ad2652156168afb259f467f7abf8072b353b4fc6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 Simeon Andreev 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:
* Simeon Andreev <simeon.danailov.andreev@gmail.com> - initial API and implementation
*******************************************************************************/
package org.eclipse.core.tests.internal.events;
import java.util.*;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.eclipse.core.internal.events.BuildCommand;
import org.eclipse.core.internal.resources.Project;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.tests.internal.builders.ConfigurationBuilder;
import org.eclipse.core.tests.resources.AutomatedTests;
import org.eclipse.core.tests.resources.ResourceTest;
import org.eclipse.core.tests.resources.regression.SimpleBuilder;
/**
* Tests that triggering a project build from multiple jobs does not cause assertion failures,
* e.g. due to adding builders to the {@link BuildCommand} in parallel.
*
* @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=517411">Eclipse bug 517411</a>
*/
public class BuildProjectFromMultipleJobsTest extends ResourceTest {
private static final String TEST_PROJECT_NAME = "ProjectForBuildCommandTest";
private final ErrorLogListener logListener = new ErrorLogListener();
private boolean wasAutoBuildOn;
public static Test suite() {
return new TestSuite(BuildProjectFromMultipleJobsTest.class);
}
public BuildProjectFromMultipleJobsTest(String name) {
super(name);
}
@Override
protected void setUp() throws Exception {
super.setUp();
// auto-build makes reproducing the problem harder,
// since it may build before we trigger parallel builds from the test
wasAutoBuildOn = setWorkspaceAutoBuild(false);
Platform.addLogListener(logListener);
}
@Override
protected void tearDown() throws Exception {
Job.getJobManager().cancel(BuildTestProject.class);
Platform.removeLogListener(logListener);
logListener.clear();
try {
IProject testProject = getTestProject();
if (testProject.exists()) {
testProject.delete(true, null);
}
} finally {
setWorkspaceAutoBuild(wasAutoBuildOn);
}
super.tearDown();
}
/**
* Creates a project with no contents and a builder, and triggers a project build from multiple jobs.
* Checks that no {@link AssertionFailedException} were logged during the builds.
*
* Repeats this several times, to ensure that no exceptions are thrown due to the build from parallel threads.
*
* @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=517411">Eclipse bug 517411</a>
*/
public void test10IterationsWithBuildsFrom8Jobs() throws Exception {
IProgressMonitor monitor = new NullProgressMonitor();
int iterations = 10;
int jobs = 8;
for (int i = 0; i < iterations; ++i) {
IProject project = createTestProject(SimpleBuilder.BUILDER_ID, monitor);
List<BuildTestProject> buildJobs = new ArrayList<>();
for (int j = 0; j < jobs; ++j) {
BuildTestProject buildTestProject = new BuildTestProject(project, j);
buildJobs.add(buildTestProject);
}
for (BuildTestProject buildJob : buildJobs) {
buildJob.schedule();
}
for (BuildTestProject buildJob : buildJobs) {
buildJob.join();
}
project.delete(true, monitor);
String errorMessage = "Building in parallel encountered an exception in iteration " + i;
logListener.assertNoExceptionsWereLogged(errorMessage);
}
}
/**
* Tests that modifying {@link BuildCommand#getBuilders()} map does not allow to modify internal state of the command.
*/
@SuppressWarnings("rawtypes")
public void testBuildersAreNotModifiable() throws Exception {
Project project = (Project) createTestProject(ConfigurationBuilder.BUILDER_NAME, null);
project.build(IncrementalProjectBuilder.FULL_BUILD, null);
// Get a non-cloned version of the project desc build spec
BuildCommand buildCommand = (BuildCommand) project.internalGetDescription().getBuildSpec(false)[0];
Map buildersMap = (Map) buildCommand.getBuilders();
assertEquals(1, buildersMap.size());
// Try to change the internal data
buildersMap.clear();
assertEquals(0, buildersMap.size());
// Should still be OK
buildersMap = (Map) buildCommand.getBuilders();
assertEquals("BuildCommand state was changed!", 1, buildersMap.size());
}
private IProject createTestProject(String builderId, IProgressMonitor monitor) throws CoreException {
IProject project = getTestProject();
assertFalse("Expected test project to not exist at beginning of test", project.exists());
ensureExistsInWorkspace(project, true);
assertTrue("Expected test project to be open after creation", project.isOpen());
// add some builder to the project, so that we can run into the concurrency problem
IProjectDescription projectDescription = project.getDescription();
ICommand[] buildSpec = projectDescription.getBuildSpec();
ICommand command = projectDescription.newCommand();
command.setBuilderName(builderId);
Collection<ICommand> builders = new ArrayList<>(Arrays.asList(buildSpec));
builders.add(command);
projectDescription.setBuildSpec(builders.toArray(new ICommand[] {}));
project.setDescription(projectDescription, monitor);
return project;
}
private static IProject getTestProject() {
IWorkspaceRoot workspaceRoot = getWorkspace().getRoot();
IProject project = workspaceRoot.getProject(TEST_PROJECT_NAME);
return project;
}
private static boolean setWorkspaceAutoBuild(boolean autobuildOn) throws CoreException {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
final IWorkspaceDescription description = workspace.getDescription();
boolean oldAutoBuildingState = description.isAutoBuilding();
if (oldAutoBuildingState != autobuildOn) {
description.setAutoBuilding(autobuildOn);
workspace.setDescription(description);
}
return oldAutoBuildingState;
}
private static class ErrorLogListener implements ILogListener {
private final List<Throwable> loggedExceptions;
public ErrorLogListener() {
loggedExceptions = new ArrayList<>();
}
@Override
public void logging(IStatus status, String plugin) {
Throwable statusException = status.getException();
loggedExceptions.add(statusException);
}
void assertNoExceptionsWereLogged(String errorMessage) {
for (Throwable loggedException : loggedExceptions) {
throw new AssertionError(errorMessage, loggedException);
}
}
void clear() {
loggedExceptions.clear();
}
}
private static class BuildTestProject extends Job {
private final IProject project;
public BuildTestProject(IProject project, int number) {
super("build test project " + number);
this.project = project;
}
@Override
protected IStatus run(IProgressMonitor jobMonitor) {
try {
if (!jobMonitor.isCanceled()) {
project.build(IncrementalProjectBuilder.FULL_BUILD, jobMonitor);
}
} catch (CoreException e) {
return e.getStatus();
}
return new Status(IStatus.OK, AutomatedTests.PI_RESOURCES_TESTS, getName() + " finished");
}
@Override
public boolean belongsTo(Object family) {
return BuildTestProject.class == family;
}
}
}