Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimeon Andreev2022-03-18 09:14:38 +0000
committerAndrey Loskutov2022-03-24 17:28:23 +0000
commit532fc55835c8c161a751c2c9ff63a5cc82dd79ce (patch)
treeedcf57ec95ecf6b6f97ecdae977273242b575006
parent14aef90c3aa857209e13af8ef3c48a51e9c564c2 (diff)
downloadeclipse.platform.resources-532fc55835c8c161a751c2c9ff63a5cc82dd79ce.tar.gz
eclipse.platform.resources-532fc55835c8c161a751c2c9ff63a5cc82dd79ce.tar.xz
eclipse.platform.resources-532fc55835c8c161a751c2c9ff63a5cc82dd79ce.zip
Bug 479451 - Warning for projects without explicit encodingHEADmaster
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>
-rw-r--r--bundles/org.eclipse.core.resources/plugin.properties1
-rw-r--r--bundles/org.eclipse.core.resources/plugin.xml11
-rw-r--r--bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetDeltaJob.java20
-rw-r--r--bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java5
-rw-r--r--bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java5
-rw-r--r--bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ValidateProjectEncoding.java177
-rw-r--r--bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/Messages.java3
-rw-r--r--bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/messages.properties3
-rw-r--r--tests/org.eclipse.core.tests.resources/pom.xml1
-rw-r--r--tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/RelaxedSchedRuleBuilderTest.java23
-rw-r--r--tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/CharsetTest.java55
-rw-r--r--tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/FreezeMonitor.java81
-rw-r--r--tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/MarkerTest.java5
-rw-r--r--tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ResourceTest.java27
-rw-r--r--tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/TestUtil.java58
15 files changed, 451 insertions, 24 deletions
diff --git a/bundles/org.eclipse.core.resources/plugin.properties b/bundles/org.eclipse.core.resources/plugin.properties
index 6d8e1b943..09190635c 100644
--- a/bundles/org.eclipse.core.resources/plugin.properties
+++ b/bundles/org.eclipse.core.resources/plugin.properties
@@ -38,3 +38,4 @@ regexFilterProvider.name = Regular Expression
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 25f1c4eb4..74b386a10 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 e4de6e20b..62bc4415b 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 @@ public class CharsetDeltaJob extends Job implements IContentTypeManager.IContent
*/
IPath getRoot();
+ IProject getProject();
+
/**
* Returns whether the corresponding resource is affected by this change.
*/
@@ -106,6 +108,12 @@ public class CharsetDeltaJob extends Job implements IContentTypeManager.IContent
// 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 @@ public class CharsetDeltaJob extends Job implements IContentTypeManager.IContent
return false;
return event.getContentType().isAssociatedWith(requestor.requestName());
}
+
+ @Override
+ public IProject getProject() {
+ return null;
+ }
};
addToQueue(filter);
}
@@ -150,8 +163,13 @@ public class CharsetDeltaJob extends Job implements IContentTypeManager.IContent
};
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 617b187d0..f2e5d5f35 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 @@ public class CharsetManager implements IManager {
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 @@ public class CharsetManager implements IManager {
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 bbac6b859..8bbdeedcd 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 @@ public class Project extends Container implements IProject {
@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 @@ public class Project extends Container implements IProject {
// 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 @@ public class Project extends Container implements IProject {
} 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 000000000..db72e143e
--- /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 a12291f44..583a7e42d 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 class Messages extends NLS {
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 797a3d21b..90ed8a2cb 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_workspaceOpen = The workspace is already open.
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 0cf3da4ce..ec6346e84 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 66d764b11..db5d925c7 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 @@ package org.eclipse.core.tests.internal.builders;
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 @@ public class RelaxedSchedRuleBuilderTest extends AbstractBuilderTest {
// 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 @@ public class RelaxedSchedRuleBuilderTest extends AbstractBuilderTest {
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 @@ public class RelaxedSchedRuleBuilderTest extends AbstractBuilderTest {
};
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 @@ public class RelaxedSchedRuleBuilderTest extends AbstractBuilderTest {
}
};
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 31751c0ea..ed588c3be 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 @@ package org.eclipse.core.tests.resources;
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 @@ public class CharsetTest extends ResourceTest {
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 @@ public class CharsetTest extends ResourceTest {
// 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 @@ public class CharsetTest extends ResourceTest {
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 @@ public class CharsetTest extends ResourceTest {
}
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 000000000..207292e3a
--- /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 e278bbb84..bcc3da02b 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 @@ public class MarkerTest extends ResourceTest {
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 @@ public class MarkerTest extends ResourceTest {
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 @@ public class MarkerTest extends ResourceTest {
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 60daabd2b..b7dd2fa3a 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.Set;
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 @@ public abstract class ResourceTest extends CoreTest {
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 @@ public abstract class ResourceTest extends CoreTest {
*/
@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 @@ public abstract class ResourceTest extends CoreTest {
}
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 1ebc4c2f7..5cd55e75d 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 @@ public class TestUtil {
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) {

Back to the top