blob: 0c058bd8df10ef627369ff5334d14260382ab69f [file] [log] [blame]
/*******************************************************************************
* 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
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.debug.tests.core;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
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.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;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleManager;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.TextConsole;
import org.eclipse.ui.internal.console.IOConsolePartitioner;
/**
* Tests console lifecycle and output handling.
*/
public class ConsoleTests extends AbstractDebugTest {
public ConsoleTests(String name) {
super(name);
}
class TestConsole extends MessageConsole {
public boolean fInit = false;
public boolean fDispose = false;
public TestConsole(boolean autoLifecycle) {
super("Life's like that", null, autoLifecycle);
}
@Override
protected void init() {
super.init();
fInit = true;
}
@Override
protected void dispose() {
super.dispose();
fDispose = true;
}
}
/**
* Test that when a process is removed from a launch, the associated
* console is closed.
*
* @throws Exception
*/
public void testRemoveProcess() throws Exception {
String typeName = "Breakpoints";
IJavaDebugTarget target = null;
try {
final IJavaDebugTarget otherTarget = launchAndTerminate(typeName);
target = otherTarget;
IProcess process = target.getProcess();
assertNotNull("Missing VM process", process);
ILaunch launch = target.getLaunch();
// make sure the console exists
DebugUIPlugin.getStandardDisplay().syncExec(new Runnable() {
@Override
public void run() {
IConsole console = DebugUITools.getConsole(otherTarget);
assertNotNull("Missing console", console);
}
});
launch.removeProcess(process);
// make sure the console is gone
DebugUIPlugin.getStandardDisplay().syncExec(new Runnable() {
@Override
public void run() {
IConsole console = DebugUITools.getConsole(otherTarget);
assertNull("Console should no longer exist", console);
}
});
} finally {
terminateAndRemove(target);
}
}
public void testAutoLifecycle() {
TestConsole console = new TestConsole(true);
IConsoleManager consoleManager = ConsolePlugin.getDefault().getConsoleManager();
consoleManager.addConsoles(new IConsole[]{console});
consoleManager.removeConsoles(new IConsole[]{console});
assertTrue("Console was not initialized", console.fInit);
assertTrue("Console was not disposed", console.fDispose);
}
public void testManualLifecycle() {
TestConsole console = new TestConsole(false);
IConsoleManager consoleManager = ConsolePlugin.getDefault().getConsoleManager();
consoleManager.addConsoles(new IConsole[]{console});
consoleManager.removeConsoles(new IConsole[]{console});
assertTrue("Console was initialized", !console.fInit);
assertTrue("Console was disposed", !console.fDispose);
console.dispose();
}
/**
* Test synchronization of standard and error output stream of started process.
* <p>
* This variant tests output on multiple lines.
* </p>
*
* @throws Exception
* if test failed
*/
public void testConsoleOutputSynchronization() throws Exception {
String typeName = "OutSync";
ILaunch launch = null;
try {
ILaunchConfiguration launchConfig = getLaunchConfiguration(typeName);
ILaunchConfigurationWorkingCopy launchCopy = launchConfig.getWorkingCopy();
launchCopy.setAttribute(DebugPlugin.ATTR_MERGE_OUTPUT, true);
launch = launchCopy.launch(ILaunchManager.RUN_MODE, null);
TestUtil.waitForJobs(getName(), 0, DEFAULT_TIMEOUT);
String content = getConsoleContent(launch.getProcesses()[0]);
// normalize new lines to unix style
content = content.replace("\r\n", "\n").replace('\r', '\n');
String expectedOutput = String.join("", Collections.nCopies(content.length() / 4, "o\ne\n"));
assertEquals("Received wrong output. Probably not synchronized.", expectedOutput, content);
} finally {
if (launch != null) {
getLaunchManager().removeLaunch(launch);
}
}
}
/**
* Test synchronization of standard and error output stream of started process.
* <p>
* This variant tests output on single line.
* </p>
*
* @throws Exception
* if test failed
*/
public void testConsoleOutputSynchronization2() throws Exception {
String typeName = "OutSync2";
ILaunch launch = null;
try {
ILaunchConfiguration launchConfig = getLaunchConfiguration(typeName);
ILaunchConfigurationWorkingCopy launchCopy = launchConfig.getWorkingCopy();
launchCopy.setAttribute(DebugPlugin.ATTR_MERGE_OUTPUT, true);
launch = launchCopy.launch(ILaunchManager.RUN_MODE, null);
TestUtil.waitForJobs(getName(), 0, DEFAULT_TIMEOUT);
String content = getConsoleContent(launch.getProcesses()[0]);
String expectedOutput = String.join("", Collections.nCopies(content.length() / 2, "oe"));
assertEquals("Received wrong output. Probably not synchronized.", expectedOutput, content);
} finally {
if (launch != null) {
getLaunchManager().removeLaunch(launch);
}
}
}
/**
* Test if process error output has another color in console than standard output.
*
* @throws Exception
* if test failed
*/
public void testConsoleErrorColoring() throws Exception {
String typeName = "OutSync";
IJavaDebugTarget target = null;
try {
target = launchAndTerminate(typeName);
final IProcess process = target.getProcess();
assertNotNull("Missing VM process.", process);
final IConsole console = DebugUITools.getConsole(process);
assertTrue("Console is not a TextConsole.", console instanceof TextConsole);
final TextConsole textConsole = (TextConsole) console;
final IDocumentPartitioner partitioner = textConsole.getDocument().getDocumentPartitioner();
assertTrue("Partitioner is not a IOConsolePartitioner.", partitioner instanceof IOConsolePartitioner);
final IOConsolePartitioner ioPartitioner = (IOConsolePartitioner) partitioner;
TestUtil.waitForJobs(getName(), 100, DEFAULT_TIMEOUT); // wait for output appending
final long numStyleTypes = Arrays.stream(ioPartitioner.getStyleRanges(0, textConsole.getDocument().getLength())).map((s) -> s.foreground).distinct().count();
assertTrue("Console partitioner did not distinct standard and error output.", numStyleTypes > 1);
} finally {
if (target != null) {
terminateAndRemove(target);
}
}
}
/**
* Try to get the console content associated with given process.
*
* @param process
* the process on whose output we are interested
* @return the raw content in console probably written by given process. Note: result may be affected by user input and console trimming.
* @throws Exception
* if content retrieval failed
*/
private String getConsoleContent(IProcess process) throws Exception {
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();
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());
ILaunch launch = null;
try {
launch = launchCopy.launch(ILaunchManager.RUN_MODE, null);
final IProcess process = launch.getProcesses()[0];
assertNotNull("Missing VM process", process);
TestUtil.waitForJobs(getName(), 25, DEFAULT_TIMEOUT); // wait for console creation
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();
final int expectedLength = (numAscii + numUmlaut + 2) * repetitions;
if (consoleDocument.getLength() > expectedLength) {
// debug code for test failures
int offset = expectedLength - 20;
String trail = consoleDocument.get(offset, consoleDocument.getLength() - offset);
System.out.println(trail);
}
assertEquals("Wrong number of characters in console.", expectedLength, consoleDocument.getLength());
} finally {
debugPrefStore.setValue(IDebugPreferenceConstants.CONSOLE_LIMIT_CONSOLE_OUTPUT, true);
debugPrefStore.setValue(IDebugPreferenceConstants.CONSOLE_WRAP, false);
if (launch != null) {
if (launch.getProcesses() != null) {
for (IProcess process : launch.getProcesses()) {
process.terminate();
}
}
getLaunchManager().removeLaunch(launch);
}
}
}
}