Bug 545769 - [tests] Test for possible UTF-8 corruption from/to process
The ProcessConsole input forwarding may corrupt two byte UTF-8
characters. The same problem may exist for reading the process output.
Change-Id: I7bb60282d7d174de16ec9d7be318f80cfc733693
Signed-off-by: Paul Pazderski <paul-eclipse@ppazderski.de>
diff --git a/org.eclipse.jdt.debug.tests/testprograms/ConsoleOutputUmlaut.java b/org.eclipse.jdt.debug.tests/testprograms/ConsoleOutputUmlaut.java
new file mode 100644
index 0000000..01edfaa
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testprograms/ConsoleOutputUmlaut.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Paul Pazderski 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:
+ * Paul Pazderski - initial API and implementation
+ *******************************************************************************/
+
+import java.nio.charset.Charset;
+
+/**
+ * This snippet prints a configurable number of one byte characters followed by a configurable number of two byte characters.
+ */
+public class ConsoleOutputUmlaut {
+ public static void main(String[] args) {
+ if (!"utf-8".equalsIgnoreCase(Charset.defaultCharset().name()) && !"utf8".equalsIgnoreCase(Charset.defaultCharset().name())) {
+ System.err.println("The programm's output must be UTF-8 encoded.");
+ System.exit(2);
+ }
+
+ int numAscii = 1;
+ int numUmlaut = 4200;
+ int repetitions = 1;
+
+ if (args.length > 0) {
+ try {
+ numAscii = Integer.parseInt(args[0]);
+ } catch (NumberFormatException e) {
+ }
+ }
+ if (args.length > 1) {
+ try {
+ numUmlaut = Integer.parseInt(args[1]);
+ } catch (NumberFormatException e) {
+ }
+ }
+ if (args.length > 2) {
+ try {
+ repetitions = Integer.parseInt(args[2]);
+ } catch (NumberFormatException e) {
+ }
+ }
+
+ StringBuilder sb = new StringBuilder(numAscii + numUmlaut + 2);
+ for (int i = 0; i < numAscii; i++) {
+ sb.append('0');
+ }
+ for (int i = 0; i < numUmlaut; i++) {
+ sb.append('\u00FC'); // ü
+ }
+ sb.append("\r\n");
+
+ String testString = sb.toString();
+ for (int i = 0; i < repetitions; i++) {
+ System.out.print(testString);
+ }
+ }
+}
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
index 57e9c4c..a5444e1 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java
@@ -207,7 +207,7 @@
"org.eclipse.debug.tests.targets.HcrClass5", "org.eclipse.debug.tests.targets.HcrClass6", "org.eclipse.debug.tests.targets.HcrClass7", "org.eclipse.debug.tests.targets.HcrClass8",
"org.eclipse.debug.tests.targets.HcrClass9", "TestContributedStepFilterClass", "TerminateAll_01", "TerminateAll_02", "StepResult1",
"StepResult2", "StepResult3", "StepUncaught", "TriggerPoint_01", "BulkThreadCreationTest", "MethodExitAndException",
- "Bug534319earlyStart", "Bug534319lateStart", "Bug534319singleThread", "Bug534319startBetwen", "MethodCall", "Bug538303", "Bug540243", "OutSync", "OutSync2" };
+ "Bug534319earlyStart", "Bug534319lateStart", "Bug534319singleThread", "Bug534319startBetwen", "MethodCall", "Bug538303", "Bug540243", "OutSync", "OutSync2", "ConsoleOutputUmlaut" };
/**
* the default timeout
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ConsoleInputTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ConsoleInputTests.java
index 3a3f5df..37377bc 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ConsoleInputTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ConsoleInputTests.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -13,12 +13,16 @@
*******************************************************************************/
package org.eclipse.jdt.debug.tests.core;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import org.eclipse.core.runtime.Platform;
+import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IStreamsProxy;
import org.eclipse.debug.core.model.IStreamsProxy2;
@@ -57,7 +61,9 @@
return new OrderedTestSuite(ConsoleInputTests.class, new String[] {
"testMultiLineInput",
"testEOF",
- "testDeleteAllEnteredText"
+ "testDeleteAllEnteredText",
+ "testBug545769_UTF8InEven",
+ "testBug545769_UTF8InOdd",
});
}
@@ -323,6 +329,68 @@
}
}
+ /**
+ * Test if two byte UTF-8 characters get disrupted on there way to the running process input.
+ * <p>
+ * This test starts every two byte character on an even byte offset.
+ * </p>
+ *
+ * @throws Exception
+ * if the test gets in trouble
+ */
+ public void testBug545769_UTF8InEven() throws Exception {
+ // 4200 characters result in 8400 bytes which should be more than most common buffer sizes.
+ utf8InputTest("", 4200);
+ }
+
+ /**
+ * Test if two byte UTF-8 characters get disrupted on there way to the running process input.
+ * <p>
+ * This test starts every two byte character on an odd byte offset.
+ * </p>
+ *
+ * @throws Exception
+ * if the test gets in trouble
+ */
+ public void testBug545769_UTF8InOdd() throws Exception {
+ // 4200 characters result in 8400 bytes which should be more than most common buffer sizes.
+ utf8InputTest(">", 4200);
+ }
+
+ /**
+ * Shared code for the UTF-8 input tests.
+ * <p>
+ * Send some two byte UTF-8 characters to process and read the echo back.
+ * </p>
+ *
+ * @param prefix
+ * an arbitrary prefix inserted before the two byte UTF-8 characters. Used to move the other characters to specific offsets e.g. a
+ * prefix of one byte will produce an input string where every two byte character starts at an odd offset.
+ * @param numTwoByteCharacters
+ * number of two byte UTF-8 characters to send to process
+ * @throws Exception
+ * if the test gets in trouble
+ */
+ private void utf8InputTest(String prefix, int numTwoByteCharacters) throws Exception {
+ ConsoleLineTracker.setDelegate(this);
+ ILaunchConfiguration configuration = getLaunchConfiguration("ConsoleInput");
+ ILaunchConfigurationWorkingCopy configurationCopy = configuration.getWorkingCopy();
+ configurationCopy.setAttribute(DebugPlugin.ATTR_CONSOLE_ENCODING, StandardCharsets.UTF_8.name());
+ ILaunch launch = null;
+ try {
+ launch = configurationCopy.launch(ILaunchManager.RUN_MODE, null);
+ String input = prefix + String.join("", Collections.nCopies(numTwoByteCharacters, "\u00F8"));
+ waitStarted();
+ String[] list = appendAndGet(fConsole, input + "\n", 2);
+ verifyOutput(new String[] { input, input }, list);
+
+ } finally {
+ ConsoleLineTracker.setDelegate(null);
+ launch.getProcesses()[0].terminate();
+ getLaunchManager().removeLaunch(launch);
+ }
+ }
+
private void spinEventLoop() {
final Display display= DebugUIPlugin.getStandardDisplay();
Runnable runnable= new Runnable() {
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ConsoleTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ConsoleTests.java
index 7f5b3ac..39eb1e0 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ConsoleTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ConsoleTests.java
@@ -13,6 +13,7 @@
*******************************************************************************/
package org.eclipse.jdt.debug.tests.core;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
@@ -23,10 +24,13 @@
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
+import org.eclipse.debug.internal.ui.preferences.IDebugPreferenceConstants;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.tests.AbstractDebugTest;
import org.eclipse.jdt.debug.tests.TestUtil;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.ui.console.ConsolePlugin;
@@ -37,7 +41,7 @@
import org.eclipse.ui.internal.console.IOConsolePartitioner;
/**
- * Tests console line tracker.
+ * Tests console lifecycle and output handling.
*/
public class ConsoleTests extends AbstractDebugTest {
@@ -231,4 +235,71 @@
final IDocument consoleDocument = textConsole.getDocument();
return consoleDocument.get();
}
+ /**
+ * Test console receiving UTF-8 output from process where two-byte UTF-8 characters start at even offsets.
+ *
+ * @throws Exception
+ * if the test gets in trouble
+ */
+ public void testBug545769_UTF8OutEven() throws Exception {
+ // 4200 umlaute results in 8400 byte of output which should be more than most common buffer sizes.
+ utf8OutputTest(0, 4200, 5);
+ }
+
+ /**
+ * Test console receiving UTF-8 output from process where two-byte UTF-8 characters start at odd offsets.
+ *
+ * @throws Exception
+ * if the test gets in trouble
+ */
+ public void testBug545769_UTF8OutOdd() throws Exception {
+ // 4200 umlaute results in 8400 byte of output which should be more than most common buffer sizes.
+ utf8OutputTest(1, 4200, 5);
+ }
+
+ /**
+ * Shared test code for possible UTF-8 process output corruption.
+ *
+ * @param numAscii
+ * number of one byte UTF-8 characters the process prints first
+ * @param numUmlaut
+ * number of two byte UTF-8 character the process prints second
+ * @param repetitions
+ * number of output repetitions. This test requires the process can write its output faster than the console can read it.
+ * @throws Exception
+ * if the test gets in trouble
+ */
+ private void utf8OutputTest(int numAscii, int numUmlaut, int repetitions) throws Exception {
+ final String typeName = "ConsoleOutputUmlaut";
+
+ final IPreferenceStore debugPrefStore = DebugUIPlugin.getDefault().getPreferenceStore();
+ debugPrefStore.setValue(IDebugPreferenceConstants.CONSOLE_LIMIT_CONSOLE_OUTPUT, false);
+ debugPrefStore.setValue(IDebugPreferenceConstants.CONSOLE_WRAP, true);
+ debugPrefStore.setValue(IDebugPreferenceConstants.CONSOLE_WIDTH, 100);
+
+ final ILaunchConfiguration launchConfig = getLaunchConfiguration(typeName);
+ final ILaunchConfigurationWorkingCopy launchCopy = launchConfig.getWorkingCopy();
+ String arg = String.join(" ", Integer.toString(numAscii), Integer.toString(numUmlaut), Integer.toString(repetitions));
+ launchCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, arg);
+ launchCopy.setAttribute(DebugPlugin.ATTR_CONSOLE_ENCODING, StandardCharsets.UTF_8.name());
+
+ IJavaDebugTarget target = null;
+ try {
+ target = launchAndTerminate(launchCopy.doSave(), DEFAULT_TIMEOUT);
+ final IProcess process = target.getProcess();
+ assertNotNull("Missing VM process", process);
+ final IConsole console = DebugUITools.getConsole(process);
+ assertNotNull("Missing console", console);
+ assertTrue("Console is not a TextConsole", console instanceof TextConsole);
+ final TextConsole textConsole = (TextConsole) console;
+ TestUtil.waitForJobs(getName(), 100, DEFAULT_TIMEOUT); // wait for output appending
+ assertEquals("Test program failed with error.", 0, process.getExitValue());
+ final IDocument consoleDocument = textConsole.getDocument();
+ assertEquals("Wrong number of characters in console.", (numAscii + numUmlaut + 2) * repetitions, consoleDocument.getLength());
+ } finally {
+ terminateAndRemove(target);
+ debugPrefStore.setValue(IDebugPreferenceConstants.CONSOLE_LIMIT_CONSOLE_OUTPUT, true);
+ debugPrefStore.setValue(IDebugPreferenceConstants.CONSOLE_WRAP, false);
+ }
+ }
}