| /******************************************************************************* |
| * 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; |
| } |
| } |
| } |