Bug 479451 - Warning for projects without explicit encoding
Add a warning marker to projects without explicit encoding. The warning
is meant to encourage setting an explicit encoding to all projects, so
that the workspace default encoding can change without breaking
projects.
Change-Id: Id112603856a334447d5d504adc874f769a1daa80
Signed-off-by: Simeon Andreev <simeon.danailov.andreev@gmail.com>
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.resources/+/192052
Tested-by: Platform Bot <platform-bot@eclipse.org>
Reviewed-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/bundles/org.eclipse.core.resources/plugin.properties b/bundles/org.eclipse.core.resources/plugin.properties
index 6d8e1b9..0919063 100644
--- a/bundles/org.eclipse.core.resources/plugin.properties
+++ b/bundles/org.eclipse.core.resources/plugin.properties
@@ -38,3 +38,4 @@
trace.component.label = Platform Core Resources
unknownNatureMarkerName=Unknown nature
+noExplicitEncodingMarkerName=No explicit project encoding
diff --git a/bundles/org.eclipse.core.resources/plugin.xml b/bundles/org.eclipse.core.resources/plugin.xml
index 25f1c4e..74b386a 100644
--- a/bundles/org.eclipse.core.resources/plugin.xml
+++ b/bundles/org.eclipse.core.resources/plugin.xml
@@ -274,4 +274,15 @@
name="natureId">
</attribute>
</extension>
+ <extension
+ id="noExplicitEncoding"
+ name="%noExplicitEncodingMarkerName"
+ point="org.eclipse.core.resources.markers">
+ <super
+ type="org.eclipse.core.resources.problemmarker">
+ </super>
+ <persistent
+ value="true">
+ </persistent>
+ </extension>
</plugin>
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java
index e4de6e2..62bc441 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java
@@ -44,6 +44,8 @@
*/
IPath getRoot();
+ IProject getProject();
+
/**
* Returns whether the corresponding resource is affected by this change.
*/
@@ -106,6 +108,12 @@
// for now, mark all resources in the project as potential encoding resource changes
return true;
}
+
+ @Override
+ public IProject getProject() {
+ return project;
+ }
+
};
addToQueue(filter);
}
@@ -129,6 +137,11 @@
return false;
return event.getContentType().isAssociatedWith(requestor.requestName());
}
+
+ @Override
+ public IProject getProject() {
+ return null;
+ }
};
addToQueue(filter);
}
@@ -150,8 +163,13 @@
};
try {
IPath root = filter.getRoot();
- if (root != null)
+ if (root != null) {
new ElementTreeIterator(workspace.getElementTree(), root).iterate(visitor);
+ }
+ IProject project = filter.getProject();
+ if (project != null) {
+ ValidateProjectEncoding.updateMissingEncodingMarker(project);
+ }
} catch (WrappedRuntimeException e) {
throw (CoreException) e.getTargetException();
}
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java
index 617b187..f2e5d5f 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java
@@ -445,6 +445,10 @@
else
encodingSettings.put(getKeyFor(resourcePath), newCharset);
flushPreferences(encodingSettings, true);
+ if (resource instanceof IProject) {
+ IProject project = (IProject) resource;
+ ValidateProjectEncoding.scheduleProjectValidation(project);
+ }
} catch (BackingStoreException e) {
IProject project = workspace.getRoot().getProject(resourcePath.segment(0));
String message = Messages.resources_savingEncoding;
@@ -503,5 +507,6 @@
workspace.addResourceChangeListener(resourceChangeListener, IResourceChangeEvent.POST_CHANGE);
charsetListener = new CharsetDeltaJob(workspace);
charsetListener.startup();
+ ValidateProjectEncoding.scheduleWorkspaceValidation();
}
}
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java
index bbac6b8..8bbdeed 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java
@@ -1010,6 +1010,7 @@
@Override
public void open(int updateFlags, IProgressMonitor monitor) throws CoreException {
monitor = Policy.monitorFor(monitor);
+ boolean encodingWritten = false;
try {
String msg = NLS.bind(Messages.resources_opening_1, getName());
monitor.beginTask(msg, Policy.totalWork);
@@ -1100,6 +1101,7 @@
// Project is new and does not have any content already (not imported)
if (!used && !unknownChildren) {
writeEncodingAfterOpen(monitor);
+ encodingWritten = true;
}
//creation of this project may affect overlapping resources
workspace.getAliasManager().updateAliases(this, getStore(), IResource.DEPTH_INFINITE, monitor);
@@ -1112,6 +1114,9 @@
} finally {
monitor.done();
}
+ if (!encodingWritten) {
+ ValidateProjectEncoding.scheduleProjectValidation(this);
+ }
}
/**
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ValidateProjectEncoding.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ValidateProjectEncoding.java
new file mode 100644
index 0000000..db72e14
--- /dev/null
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ValidateProjectEncoding.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright (c) 2022 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 - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.util.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Reports warning markers on projects without an explicit encoding setting.
+ */
+public class ValidateProjectEncoding extends WorkspaceJob {
+
+ public static final String MARKER_ID = "noExplicitEncoding"; //$NON-NLS-1$
+
+ public static final String MARKER_TYPE = ResourcesPlugin.getPlugin().getBundle().getSymbolicName() + "." //$NON-NLS-1$
+ + MARKER_ID;
+
+ public static void scheduleWorkspaceValidation() {
+ IWorkspaceRoot workspaceRoot = getWorkspaceRoot();
+ IProject[] projects = workspaceRoot.getProjects();
+ ValidateProjectEncoding validateProjectEncoding = new ValidateProjectEncoding(projects);
+ validateProjectEncoding.setRule(getWorkspaceRoot());
+ validateProjectEncoding.schedule();
+ }
+
+ public static void scheduleProjectValidation(IProject project) {
+ // schedule a job only if marker state would change
+ boolean shouldScheduleValidation = shouldScheduleValidation(project);
+ if (shouldScheduleValidation) {
+ ValidateProjectEncoding validateProjectEncoding = new ValidateProjectEncoding(project);
+ validateProjectEncoding.setRule(project);
+ validateProjectEncoding.schedule();
+ }
+ }
+
+ private final IProject[] projects;
+
+ private ValidateProjectEncoding(IProject... projects) {
+ super(Messages.resources_checkExplicitEncoding_jobName);
+ setSystem(true);
+ this.projects = projects;
+ }
+
+ @Override
+ public boolean belongsTo(Object family) {
+ return family == ValidateProjectEncoding.class;
+ }
+
+ @Override
+ public IStatus runInWorkspace(IProgressMonitor monitor) {
+ if (monitor.isCanceled()) {
+ return Status.CANCEL_STATUS;
+ }
+ SubMonitor subMonitor = SubMonitor.convert(monitor, projects.length);
+ for (IProject project : projects) {
+ subMonitor.checkCanceled();
+ subMonitor.setTaskName(NLS.bind(Messages.resources_checkExplicitEncoding_taskName, project.getName()));
+ updateMissingEncodingMarker(project);
+ subMonitor.worked(1);
+ }
+ return Status.OK_STATUS;
+ }
+
+ /**
+ * Must be called from a workspace job
+ *
+ * @param project non null
+ */
+ static void updateMissingEncodingMarker(IProject project) {
+ try {
+ if (project.isAccessible() && !project.isHidden()) {
+ String defaultCharset = getDefaultCharset(project);
+ if (defaultCharset == null) {
+ createEncodingMarker(project);
+ } else {
+ deleteEncodingMarkers(project);
+ }
+ }
+ } catch (CoreException e) {
+ logException(e);
+ }
+ }
+
+ private static boolean shouldScheduleValidation(IProject project) {
+ boolean shouldScheduleValidation = true;
+ try {
+ if (project.isHidden()) {
+ shouldScheduleValidation = false;
+ } else if (project.isAccessible()) {
+ String defaultCharset = getDefaultCharset(project);
+ boolean hasDefaultEncoding = defaultCharset != null;
+ IMarker[] encodingMarkers = getEncodingMarkers(project);
+ boolean hasEncodingMarkers = encodingMarkers != null && encodingMarkers.length > 0;
+ if (hasEncodingMarkers && !hasDefaultEncoding) {
+ // don't validate again if the project already has a marker and has no encoding
+ shouldScheduleValidation = false;
+ } else if (!hasEncodingMarkers && hasDefaultEncoding) {
+ // don't validate again if the project has no marker and has encoding
+ shouldScheduleValidation = false;
+ }
+ }
+ } catch (CoreException e) {
+ logException(e);
+ }
+ return shouldScheduleValidation;
+ }
+
+ private static String getDefaultCharset(IProject project) throws CoreException {
+ boolean checkImplicit = false;
+ String defaultCharset = project.getDefaultCharset(checkImplicit);
+ return defaultCharset;
+ }
+
+ private static void createEncodingMarker(IProject project) throws CoreException {
+ String message = NLS.bind(Messages.resources_checkExplicitEncoding_problemText, project.getName());
+
+ String[] attributeNames = { IMarker.MESSAGE, IMarker.SEVERITY, IMarker.LOCATION };
+ Object[] attributevalues = { message, IMarker.SEVERITY_WARNING, project.getFullPath().toString() };
+
+ IMarker[] existing = project.findMarkers(MARKER_TYPE, false, IResource.DEPTH_ONE);
+ for (IMarker marker : existing) {
+ Object[] markerValues = marker.getAttributes(attributeNames);
+ if (Arrays.equals(attributevalues, markerValues)) {
+ return;
+ }
+ }
+
+ Map<String, Object> attributes = new HashMap<>();
+ for (int i = 0; i < attributeNames.length; i++) {
+ attributes.put(attributeNames[i], attributevalues[i]);
+ }
+ project.createMarker(MARKER_TYPE, attributes);
+ }
+
+ private static void deleteEncodingMarkers(IProject project) throws CoreException {
+ IMarker[] existing = getEncodingMarkers(project);
+ for (IMarker marker : existing) {
+ marker.delete();
+ }
+ }
+
+ private static IMarker[] getEncodingMarkers(IProject project) throws CoreException {
+ IMarker[] existing = project.findMarkers(MARKER_TYPE, false, IResource.DEPTH_ONE);
+ return existing;
+ }
+
+ private static void logException(CoreException e) {
+ boolean logException = true;
+ if (e instanceof ResourceException) {
+ int code = e.getStatus().getCode();
+ if (code == IResourceStatus.RESOURCE_NOT_FOUND || code == IResourceStatus.PROJECT_NOT_OPEN) {
+ logException = false;
+ }
+ }
+ if (logException) {
+ ResourcesPlugin.getPlugin().getLog().log(e.getStatus());
+ }
+ }
+
+ private static IWorkspaceRoot getWorkspaceRoot() {
+ return ResourcesPlugin.getWorkspace().getRoot();
+ }
+}
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java
index a12291f..583a7e4 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java
@@ -299,6 +299,9 @@
public static String resources_writeMeta;
public static String resources_writeWorkspaceMeta;
public static String resources_errorResourceIsFiltered;
+ public static String resources_checkExplicitEncoding_jobName;
+ public static String resources_checkExplicitEncoding_taskName;
+ public static String resources_checkExplicitEncoding_problemText;
public static String synchronizer_partnerNotRegistered;
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties
index 797a3d2..90ed8a2 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties
@@ -300,6 +300,9 @@
resources_writeMeta = Could not write metadata for ''{0}''.
resources_writeWorkspaceMeta = Could not write workspace metadata ''{0}''.
resources_errorResourceIsFiltered=The resource will be filtered out by its parent resource filters
+resources_checkExplicitEncoding_jobName=Checking project encoding
+resources_checkExplicitEncoding_taskName=Checking encoding of project ''{0}''
+resources_checkExplicitEncoding_problemText=Project ''{0}'' has no explicit encoding set
synchronizer_partnerNotRegistered = Sync partner: {0} not registered with the synchronizer.
diff --git a/tests/org.eclipse.core.tests.resources/pom.xml b/tests/org.eclipse.core.tests.resources/pom.xml
index 0cf3da4..ec6346e 100644
--- a/tests/org.eclipse.core.tests.resources/pom.xml
+++ b/tests/org.eclipse.core.tests.resources/pom.xml
@@ -52,6 +52,7 @@
<configuration>
<useUIHarness>false</useUIHarness>
<useUIThread>false</useUIThread>
+ <appArgLine>-consoleLog</appArgLine>
<dependencies>
<dependency>
<artifactId>org.eclipse.core.tests.filesystem.feature</artifactId>
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/RelaxedSchedRuleBuilderTest.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/RelaxedSchedRuleBuilderTest.java
index 66d764b..db5d925 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/RelaxedSchedRuleBuilderTest.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/RelaxedSchedRuleBuilderTest.java
@@ -16,11 +16,13 @@
import java.io.ByteArrayInputStream;
import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.*;
import org.eclipse.core.tests.harness.TestBarrier;
import org.eclipse.core.tests.internal.builders.TestBuilder.BuilderRuleCallback;
+import org.eclipse.core.tests.resources.TestUtil;
/**
* This class tests extended functionality (since 3.6) which allows
@@ -288,13 +290,14 @@
// assert that the delta contains the file foo
IResourceDelta delta = getDelta(project);
assertNotNull("1.1", delta);
- assertTrue("1.2", delta.getAffectedChildren().length == 1);
- assertTrue("1.3", delta.getAffectedChildren()[0].getResource().equals(foo));
+ assertEquals("1.2.", 1, delta.getAffectedChildren().length);
+ assertEquals("1.3.", foo, delta.getAffectedChildren()[0].getResource());
tb1.setStatus(TestBarrier.STATUS_DONE);
return super.build(kind, args, monitor);
}
});
+ AtomicReference<Throwable> error = new AtomicReference<>();
// Run the incremental build
Job j = new Job("IProject.build()") {
@Override
@@ -302,6 +305,13 @@
try {
getWorkspace().build(new IBuildConfiguration[] {project.getActiveBuildConfig()}, IncrementalProjectBuilder.INCREMENTAL_BUILD, true, monitor);
} catch (CoreException e) {
+ IStatus status = e.getStatus();
+ IStatus[] children = status.getChildren();
+ if (children.length > 0) {
+ error.set(children[0].getException());
+ } else {
+ error.set(e);
+ }
fail();
}
return Status.OK_STATUS;
@@ -309,6 +319,11 @@
};
j.schedule();
+ TestUtil.waitForJobs(getName(), 100, 1000);
+ if (error.get() != null) {
+ fail("Error observed", error.get());
+ }
+
// Wait for the build to transition to getRule
tb1.waitForStatus(TestBarrier.STATUS_START);
// Modify a file in the project
@@ -321,6 +336,10 @@
}
};
j.schedule();
+ TestUtil.waitForJobs(getName(), 1000, 5000);
+ if (error.get() != null) {
+ fail("Error observed", error.get());
+ }
tb1.waitForStatus(TestBarrier.STATUS_DONE);
}
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/CharsetTest.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/CharsetTest.java
index 31751c0..ed588c3 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/CharsetTest.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/CharsetTest.java
@@ -17,11 +17,11 @@
import java.io.*;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.util.*;
import org.eclipse.core.internal.events.NotificationManager;
import org.eclipse.core.internal.preferences.EclipsePreferences;
-import org.eclipse.core.internal.resources.CharsetDeltaJob;
-import org.eclipse.core.internal.resources.CharsetManager;
+import org.eclipse.core.internal.resources.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.content.*;
@@ -913,19 +913,38 @@
ensureExistsInWorkspace(new IResource[] { project }, true);
assertEquals(ResourcesPlugin.getEncoding(), project.getDefaultCharset(false));
+
+ IMarker[] markers = project.findMarkers(ValidateProjectEncoding.MARKER_TYPE, false, IResource.DEPTH_ONE);
+ assertEquals("No missing encoding marker should be set", 0, markers.length);
+
project.setDefaultCharset(null, getMonitor());
assertEquals(null, project.getDefaultCharset(false));
+ waitForProjectEncodingValidation();
+
+ markers = project.findMarkers(ValidateProjectEncoding.MARKER_TYPE, false, IResource.DEPTH_ONE);
+ assertEquals("Missing encoding marker should be set", 1, markers.length);
+
ensureExistsInWorkspace(new IResource[] {file1, file2, file3}, true);
// project and children should be using the workspace's default now
assertCharsetIs("1.0", ResourcesPlugin.getEncoding(), new IResource[] {workspace.getRoot(), project, file1, folder1, file2, folder2, file3}, true);
assertCharsetIs("1.1", null, new IResource[] {project, file1, folder1, file2, folder2, file3}, false);
+
// sets workspace default charset
workspace.getRoot().setDefaultCharset("FOO", getMonitor());
+ markers = project.findMarkers(ValidateProjectEncoding.MARKER_TYPE, false, IResource.DEPTH_ONE);
+ assertEquals("Missing encoding marker should be still set", 1, markers.length);
+
assertCharsetIs("2.0", "FOO", new IResource[] {workspace.getRoot(), project, file1, folder1, file2, folder2, file3}, true);
assertCharsetIs("2.1", null, new IResource[] {project, file1, folder1, file2, folder2, file3}, false);
+
// sets project default charset
project.setDefaultCharset("BAR", getMonitor());
+ waitForProjectEncodingValidation();
+
+ markers = project.findMarkers(ValidateProjectEncoding.MARKER_TYPE, false, IResource.DEPTH_ONE);
+ assertEquals("No missing encoding marker should be set", 0, markers.length);
+
assertCharsetIs("3.0", "BAR", new IResource[] {project, file1, folder1, file2, folder2, file3}, true);
assertCharsetIs("3.1", null, new IResource[] {file1, folder1, file2, folder2, file3}, false);
assertCharsetIs("3.2", "FOO", new IResource[] {workspace.getRoot()}, true);
@@ -1057,7 +1076,7 @@
// check preference change events are reflected in the charset settings
// temporarily disabled
- public void testDeltaOnPreferenceChanges() {
+ public void testDeltaOnPreferenceChanges() throws Exception {
CharsetVerifier backgroundVerifier = new CharsetVerifier(CharsetVerifier.IGNORE_CREATION_THREAD);
getWorkspace().addResourceChangeListener(backgroundVerifier, IResourceChangeEvent.POST_CHANGE);
@@ -1070,36 +1089,54 @@
IFile resourcesPrefs = getResourcesPreferenceFile(project, false);
assertTrue("0.9", resourcesPrefs.exists());
-
+ String prefsContent = Files.readString(resourcesPrefs.getLocation().toFile().toPath());
+ assertTrue(prefsContent.contains(ResourcesPlugin.getEncoding()));
try {
file1.setCharset("CHARSET1", getMonitor());
} catch (CoreException e) {
fail("1.0", e);
}
assertTrue("1.1", resourcesPrefs.exists());
+ waitForCharsetManagerJob();
+
+ prefsContent = Files.readString(resourcesPrefs.getLocation().toFile().toPath());
+ assertTrue(prefsContent.contains(ResourcesPlugin.getEncoding()));
backgroundVerifier.reset();
backgroundVerifier.addExpectedChange(new IResource[] {project, folder1, file1, file2, resourcesPrefs, resourcesPrefs.getParent()}, IResourceDelta.CHANGED, IResourceDelta.ENCODING);
// cause a resource change event without actually changing contents
+ InputStream contents = new ByteArrayInputStream(prefsContent.getBytes());
try {
- resourcesPrefs.setContents(resourcesPrefs.getContents(), 0, getMonitor());
+ resourcesPrefs.setContents(contents, 0, getMonitor());
} catch (CoreException e) {
fail("2.0", e);
}
assertTrue("2.1", backgroundVerifier.waitForEvent(10000));
assertTrue("2.2 " + backgroundVerifier.getMessage(), backgroundVerifier.isDeltaValid());
+ IMarker[] markers = project.findMarkers(ValidateProjectEncoding.MARKER_TYPE, false, IResource.DEPTH_ONE);
+ assertEquals("No missing encoding marker should be set", 0, markers.length);
+
backgroundVerifier.reset();
- backgroundVerifier.addExpectedChange(new IResource[] {project, folder1, file1, file2, resourcesPrefs.getParent()}, IResourceDelta.CHANGED, IResourceDelta.ENCODING);
+ backgroundVerifier.addExpectedChange(
+ new IResource[] { project }, IResourceDelta.CHANGED,
+ IResourceDelta.ENCODING | IResourceDelta.MARKERS);
+ backgroundVerifier.addExpectedChange(new IResource[] { folder1, file1, file2, resourcesPrefs.getParent() },
+ IResourceDelta.CHANGED, IResourceDelta.ENCODING);
try {
// delete the preferences file
resourcesPrefs.delete(true, getMonitor());
} catch (CoreException e) {
fail("3.0", e);
}
+ waitForCharsetManagerJob();
+ waitForProjectEncodingValidation();
+
assertTrue("3.1", backgroundVerifier.waitForEvent(10000));
assertTrue("3.2 " + backgroundVerifier.getMessage(), backgroundVerifier.isDeltaValid());
+ markers = project.findMarkers(ValidateProjectEncoding.MARKER_TYPE, false, IResource.DEPTH_ONE);
+ assertEquals("Missing encoding marker should be set", 1, markers.length);
} finally {
getWorkspace().removeResourceChangeListener(backgroundVerifier);
try {
@@ -1552,11 +1589,7 @@
}
private static void waitForJobFamily(Object family) {
- try {
- Job.getJobManager().join(family, null);
- } catch (Exception e) {
- fail("Exception occurred while waiting on jobs from family: " + family, e);
- }
+ TestUtil.waitForJobs("Waiting for " + family, 100, 10_000, family);
}
private class CharsetVerifierWithExtraInfo extends CharsetVerifier {
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/FreezeMonitor.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/FreezeMonitor.java
new file mode 100644
index 0000000..207292e
--- /dev/null
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/FreezeMonitor.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc 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:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+
+package org.eclipse.core.tests.resources;
+
+import java.lang.management.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+
+public class FreezeMonitor {
+
+ public static final long FROZEN_TEST_TIMEOUT_MS = 60_000;
+
+ private static /* @Nullable */ Job monitorJob;
+
+ /**
+ * Will dump JVM threads if test runs over one minute
+ */
+ public static void expectCompletionInAMinute() {
+ expectCompletionIn(FROZEN_TEST_TIMEOUT_MS);
+ }
+
+ /**
+ * Will dump JVM threads if test runs over given time
+ */
+ public static void expectCompletionIn(final long timeout) {
+ done();
+ monitorJob = new Job("FreezeMonitor") {
+ @Override
+ public IStatus run(IProgressMonitor monitor) {
+ if (monitor.isCanceled()) {
+ return Status.CANCEL_STATUS;
+ }
+ StringBuilder result = new StringBuilder();
+ result.append("Possible frozen test case\n");
+ ThreadMXBean threadStuff = ManagementFactory.getThreadMXBean();
+ ThreadInfo[] allThreads = threadStuff.getThreadInfo(threadStuff.getAllThreadIds(), 200);
+ for (ThreadInfo threadInfo : allThreads) {
+ result.append("\"");
+ result.append(threadInfo.getThreadName());
+ result.append("\": ");
+ result.append(threadInfo.getThreadState());
+ result.append("\n");
+ final StackTraceElement[] elements = threadInfo.getStackTrace();
+ for (StackTraceElement element : elements) {
+ result.append(" ");
+ result.append(element);
+ result.append("\n");
+ }
+ result.append("\n");
+ }
+ System.out.println(result.toString());
+ return Status.OK_STATUS;
+ }
+
+ @Override
+ public boolean belongsTo(Object family) {
+ return FreezeMonitor.class == family;
+ }
+ };
+ monitorJob.schedule(timeout);
+ }
+
+ public static void done() {
+ if (monitorJob != null) {
+ monitorJob.cancel();
+ monitorJob = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/MarkerTest.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/MarkerTest.java
index e278bbb..bcc3da0 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/MarkerTest.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/MarkerTest.java
@@ -786,8 +786,7 @@
try {
IProject project = getWorkspace().getRoot().getProject("MyProject");
- project.create(null);
- project.open(null);
+ create(project, false);
IFile file = project.getFile("foo.txt");
file.create(getRandomContents(), true, null);
file.createMarker(IMarker.PROBLEM);
@@ -1344,6 +1343,7 @@
IFile file = project.getFile("file.txt");
IFile subFile = folder.getFile("subFile.txt");
ensureExistsInWorkspace(new IResource[] {project, folder, file, subFile}, true);
+ waitForProjectEncodingValidation();
IFolder destFolder = project.getFolder("myOtherFolder");
IFile destSubFile = destFolder.getFile(subFile.getName());
IMarker folderMarker = null;
@@ -1412,6 +1412,7 @@
IFile file = project.getFile("file.txt");
IFile subFile = folder.getFile("subFile.txt");
ensureExistsInWorkspace(new IResource[] {project, folder, file, subFile}, true);
+ waitForProjectEncodingValidation();
IFile destFile = folder.getFile(file.getName());
IFile destSubFile = project.getFile(subFile.getName());
IMarker fileMarker = null;
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ResourceTest.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ResourceTest.java
index 60daabd..b7dd2fa 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ResourceTest.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ResourceTest.java
@@ -23,6 +23,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.core.filesystem.*;
import org.eclipse.core.internal.resources.Resource;
+import org.eclipse.core.internal.resources.ValidateProjectEncoding;
import org.eclipse.core.internal.utils.FileUtil;
import org.eclipse.core.internal.utils.UniversalUniqueIdentifier;
import org.eclipse.core.resources.*;
@@ -582,14 +583,15 @@
if (resource == null) {
return;
}
- IWorkspaceRunnable body = monitor -> {
- if (resource.exists()) {
- resource.setContents(contents, true, false, null);
- } else {
- ensureExistsInWorkspace(resource.getParent(), true);
+ IWorkspaceRunnable body;
+ if (resource.exists()) {
+ body = monitor -> resource.setContents(contents, true, false, null);
+ } else {
+ body = monitor -> {
+ create(resource.getParent(), true);
resource.create(contents, true, null);
- }
- };
+ };
+ }
try {
getWorkspace().run(body, null);
} catch (CoreException e) {
@@ -978,15 +980,20 @@
*/
@Override
protected void setUp() throws Exception {
+ super.setUp();
+ TestUtil.log(IStatus.INFO, getName(), "setUp");
+ FreezeMonitor.expectCompletionInAMinute();
assertNotNull("Workspace was not setup", getWorkspace());
}
@Override
protected void tearDown() throws Exception {
- super.tearDown();
+ TestUtil.log(IStatus.INFO, getName(), "tearDown");
// Ensure everything is in a clean state for next one.
// Session tests should overwrite it.
cleanup();
+ super.tearDown();
+ FreezeMonitor.done();
}
/**
@@ -1037,4 +1044,8 @@
}
return devices;
}
+
+ protected void waitForProjectEncodingValidation() {
+ TestUtil.waitForJobs(getName(), 10, 5_000, ValidateProjectEncoding.class);
+ }
}
\ No newline at end of file
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/TestUtil.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/TestUtil.java
index 1ebc4c2..5cd55e7 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/TestUtil.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/TestUtil.java
@@ -101,6 +101,64 @@
return false;
}
+ /**
+ * Utility for waiting until the execution of jobs of given family has finished
+ * or timeout is reached. If no jobs are running, the method waits given minimum
+ * wait time.
+ *
+ * @param owner
+ * name of the caller which will be logged as prefix if the wait
+ * times out
+ * @param minTimeMs
+ * minimum wait time in milliseconds
+ * @param maxTimeMs
+ * maximum wait time in milliseconds
+ * @param family
+ * job family to wait for
+ *
+ * @return true if the method timed out, false if all the jobs terminated before
+ * the timeout
+ */
+ public static boolean waitForJobs(String owner, long minTimeMs, long maxTimeMs, Object family) {
+ if (maxTimeMs < minTimeMs) {
+ throw new IllegalArgumentException("Max time is smaller as min time!");
+ }
+ final long start = System.currentTimeMillis();
+ while (System.currentTimeMillis() - start < minTimeMs) {
+ try {
+ Thread.sleep(Math.min(10, minTimeMs));
+ } catch (InterruptedException e) {
+ // Uninterruptable
+ }
+ }
+ while (!Job.getJobManager().isIdle()) {
+ List<Job> jobs = getRunningOrWaitingJobs(family);
+ if (jobs.isEmpty()) {
+ // only uninteresting jobs running
+ break;
+ }
+
+ if (!Collections.disjoint(runningJobs, jobs)) {
+ // There is a job which runs already quite some time, don't wait for it to avoid
+ // test timeouts
+ dumpRunningOrWaitingJobs(owner, jobs);
+ return true;
+ }
+
+ if (System.currentTimeMillis() - start >= maxTimeMs) {
+ dumpRunningOrWaitingJobs(owner, jobs);
+ return true;
+ }
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ // Uninterruptable
+ }
+ }
+ runningJobs.clear();
+ return false;
+ }
+
static Set<Job> runningJobs = new LinkedHashSet<>();
private static void dumpRunningOrWaitingJobs(String owner, List<Job> jobs) {