diff options
author | Paul Pazderski | 2020-02-22 20:23:28 +0000 |
---|---|---|
committer | Paul Pazderski | 2020-05-01 23:17:19 +0000 |
commit | 79c7833392ac1e85adf0f48e5808998c6f3a1caf (patch) | |
tree | 17817364c3e69c10f2424c6eab4237e024b016f4 | |
parent | 4296b4127e2e3b8549f5733c4c7d8f2f24a4b891 (diff) | |
download | eclipse.platform.debug-79c7833392ac1e85adf0f48e5808998c6f3a1caf.tar.gz eclipse.platform.debug-79c7833392ac1e85adf0f48e5808998c6f3a1caf.tar.xz eclipse.platform.debug-79c7833392ac1e85adf0f48e5808998c6f3a1caf.zip |
Bug 333239 - [console] Console redirection treats file name as regularI20200505-1800I20200504-1800I20200503-1800I20200502-1800
expression
Change-Id: I8b475d001a6e0a626540d9aa93285fa918d4b21e
Signed-off-by: Paul Pazderski <paul-eclipse@ppazderski.de>
3 files changed, 193 insertions, 19 deletions
diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/MockProcess.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/MockProcess.java index aa84545c0..135b54c7c 100644 --- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/MockProcess.java +++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/MockProcess.java @@ -17,13 +17,18 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import org.eclipse.core.runtime.CoreException; import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchConfigurationType; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.Launch; import org.eclipse.debug.core.model.RuntimeProcess; +import org.eclipse.debug.tests.launching.LaunchConfigurationTests; /** * A mockup process which can either simulate generation of output or wait for @@ -265,4 +270,25 @@ public class MockProcess extends Process { public RuntimeProcess toRuntimeProcess(String name) { return (RuntimeProcess) DebugPlugin.newProcess(new Launch(null, ILaunchManager.RUN_MODE, null), this, name); } + + /** + * Create a {@link RuntimeProcess} which wraps this {@link MockProcess}. + * <p> + * This method also attaches a + * {@link LaunchConfigurationTests#ID_TEST_LAUNCH_TYPE} launch configuration + * to the {@link RuntimeProcess}. + * </p> + * + * @param name name for the process and launch configuration + * @return the created {@link RuntimeProcess} + */ + public RuntimeProcess toRuntimeProcess(String name, Map<String, Object> launchConfigAttributes) throws CoreException { + ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); + ILaunchConfigurationType launchType = launchManager.getLaunchConfigurationType(LaunchConfigurationTests.ID_TEST_LAUNCH_TYPE); + ILaunchConfigurationWorkingCopy launchConfiguration = launchType.newInstance(null, name); + if (launchConfigAttributes != null) { + launchConfiguration.setAttributes(launchConfigAttributes); + } + return (RuntimeProcess) DebugPlugin.newProcess(new Launch(launchConfiguration, ILaunchManager.RUN_MODE, null), this, name); + } } diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java index 37e3737a6..7b274bcc2 100644 --- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java +++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/ProcessConsoleTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019 Paul Pazderski and others. + * Copyright (c) 2019, 2020 Paul Pazderski and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -13,12 +13,21 @@ *******************************************************************************/ package org.eclipse.debug.tests.console; +import static org.junit.Assert.assertArrayEquals; + +import java.io.ByteArrayInputStream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -41,10 +50,13 @@ import org.eclipse.debug.tests.TestUtil; import org.eclipse.debug.tests.launching.LaunchConfigurationTests; import org.eclipse.debug.ui.IDebugUIConstants; import org.eclipse.debug.ui.console.ConsoleColorProvider; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.ui.console.ConsolePlugin; import org.eclipse.ui.console.IConsole; import org.eclipse.ui.console.IConsoleConstants; import org.eclipse.ui.console.IConsoleManager; +import org.eclipse.ui.console.IOConsole; import org.eclipse.ui.console.IOConsoleInputStream; import org.junit.After; import org.junit.Before; @@ -62,11 +74,14 @@ public class ProcessConsoleTests extends AbstractDebugTest { /** Listener to count error messages in {@link ConsolePlugin} log. */ private final ILogListener errorLogListener = (status, plugin) -> { - if (status.matches(IStatus.ERROR)) { - loggedErrors.incrementAndGet(); - } + if (status.matches(IStatus.ERROR)) { + loggedErrors.incrementAndGet(); + } }; + /** Temporary test files created by a test. Will be deleted on teardown. */ + private final ArrayList<File> tmpFiles = new ArrayList<>(); + @Override @Before public void setUp() throws Exception { @@ -79,12 +94,34 @@ public class ProcessConsoleTests extends AbstractDebugTest { @After public void tearDown() throws Exception { Platform.removeLogListener(errorLogListener); + for (File tmpFile : tmpFiles) { + tmpFile.delete(); + } + tmpFiles.clear(); + super.tearDown(); assertEquals("Test triggered errors.", 0, loggedErrors.get()); } /** + * Create a new temporary file for testing. File will be deleted when test + * finishes. + * + * @param filename name of the temporary file + * @return the created temporary file + * @throws IOException if creating the file failed. Includes file already + * exists. + */ + private File createTmpFile(String filename) throws IOException { + File file = DebugUIPlugin.getDefault().getStateLocation().addTrailingSeparator().append(filename).toFile(); + boolean fileCreated = file.createNewFile(); + assertTrue("Failed to prepare temporary test file.", fileCreated); + tmpFiles.add(file); + return file; + } + + /** * Test if two byte UTF-8 characters get disrupted on there way from process * console to the runtime process. * <p> @@ -231,9 +268,9 @@ public class ProcessConsoleTests extends AbstractDebugTest { @SuppressWarnings("restriction") final org.eclipse.debug.internal.ui.views.console.ProcessConsole console = new org.eclipse.debug.internal.ui.views.console.ProcessConsole(process, new ConsoleColorProvider()); console.addPropertyChangeListener(event -> { - if (event.getSource() == console && IConsoleConstants.P_CONSOLE_OUTPUT_COMPLETE.equals(event.getProperty())) { - terminationSignaled.set(true); - } + if (event.getSource() == console && IConsoleConstants.P_CONSOLE_OUTPUT_COMPLETE.equals(event.getProperty())) { + terminationSignaled.set(true); + } }); final IConsoleManager consoleManager = ConsolePlugin.getDefault().getConsoleManager(); try { @@ -248,4 +285,124 @@ public class ProcessConsoleTests extends AbstractDebugTest { TestUtil.waitForJobs(name.getMethodName(), 0, 10000); } } + + /** + * Test simple redirect of console output into file. + */ + @Test + public void testRedirectOutputToFile() throws Exception { + final String testContent = "Hello World!"; + final File outFile = createTmpFile("test.out"); + Map<String, Object> launchConfigAttributes = new HashMap<>(); + launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_FILE, outFile.getCanonicalPath()); + launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, true); + doConsoleOutputTest(testContent.getBytes(), launchConfigAttributes); + assertArrayEquals("Wrong content redirected to file.", testContent.getBytes(), Files.readAllBytes(outFile.toPath())); + } + + /** + * Test appending of console output into existing file. + */ + @Test + public void testAppendOutputToFile() throws Exception { + final String testContent = "Hello World!"; + final File outFile = createTmpFile("test.out"); + Map<String, Object> launchConfigAttributes = new HashMap<>(); + launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_FILE, outFile.getCanonicalPath()); + launchConfigAttributes.put(IDebugUIConstants.ATTR_APPEND_TO_FILE, true); + launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, true); + doConsoleOutputTest(testContent.getBytes(), launchConfigAttributes); + assertArrayEquals("Wrong content redirected to file.", testContent.getBytes(), Files.readAllBytes(outFile.toPath())); + + String appendedContent = "append"; + doConsoleOutputTest(appendedContent.getBytes(), launchConfigAttributes); + assertArrayEquals("Wrong content redirected to file.", (testContent + appendedContent).getBytes(), Files.readAllBytes(outFile.toPath())); + } + + /** + * Test output redirect with a filename containing regular expression + * specific special characters. + * <p> + * Test a filename with special characters which is still a valid regular + * expression and a filename whose name is an invalid regular expression. + */ + @Test + public void testBug333239_regexSpecialCharactersInOutputFilename() throws Exception { + final String testContent = "1.\n2.\n3.\n"; + File outFile = createTmpFile("test.[out]"); + Map<String, Object> launchConfigAttributes = new HashMap<>(); + launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_FILE, outFile.getCanonicalPath()); + launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, false); + IOConsole console = doConsoleOutputTest(testContent.getBytes(), launchConfigAttributes); + assertArrayEquals("Wrong content redirected to file.", testContent.getBytes(), Files.readAllBytes(outFile.toPath())); + assertEquals("Output in console.", 2, console.getDocument().getNumberOfLines()); + + outFile = createTmpFile("exhaustive[128-32].out"); + launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_FILE, outFile.getCanonicalPath()); + console = doConsoleOutputTest(testContent.getBytes(), launchConfigAttributes); + assertArrayEquals("Wrong content redirected to file.", testContent.getBytes(), Files.readAllBytes(outFile.toPath())); + assertEquals("Output in console.", 2, console.getDocument().getNumberOfLines()); + + outFile = createTmpFile("ug(ly.out"); + launchConfigAttributes.put(IDebugUIConstants.ATTR_CAPTURE_IN_FILE, outFile.getCanonicalPath()); + console = doConsoleOutputTest(testContent.getBytes(), launchConfigAttributes); + assertArrayEquals("Wrong content redirected to file.", testContent.getBytes(), Files.readAllBytes(outFile.toPath())); + assertEquals("Output in console.", 2, console.getDocument().getNumberOfLines()); + } + + /** + * Shared test code for tests who want to write and verify content to + * console. Method will open a console for a mockup process, output the + * given content, terminate the process and close the console. If content is + * expected to be found in console it will be verified. If output is + * redirected to file the file path which should be printed to console is + * checked. + * + * @param testContent content to output in console + * @param launchConfigAttributes optional launch configuration attributes to + * specify behavior + * @return the console object after it has finished + */ + private IOConsole doConsoleOutputTest(byte[] testContent, Map<String, Object> launchConfigAttributes) throws Exception { + final MockProcess mockProcess = new MockProcess(new ByteArrayInputStream(testContent), null, 0); + final IProcess process = mockProcess.toRuntimeProcess("Output Redirect", launchConfigAttributes); + final String encoding = launchConfigAttributes != null ? (String) launchConfigAttributes.get(DebugPlugin.ATTR_CONSOLE_ENCODING) : null; + final AtomicBoolean consoleFinished = new AtomicBoolean(false); + @SuppressWarnings("restriction") + final org.eclipse.debug.internal.ui.views.console.ProcessConsole console = new org.eclipse.debug.internal.ui.views.console.ProcessConsole(process, new ConsoleColorProvider(), encoding); + console.addPropertyChangeListener((PropertyChangeEvent event) -> { + if (event.getSource() == console && IConsoleConstants.P_CONSOLE_OUTPUT_COMPLETE.equals(event.getProperty())) { + consoleFinished.set(true); + } + }); + final IConsoleManager consoleManager = ConsolePlugin.getDefault().getConsoleManager(); + try { + consoleManager.addConsoles(new IConsole[] { console }); + waitWhile(c -> !consoleFinished.get(), testTimeout, c -> "Console did not finished."); + + Object value = launchConfigAttributes != null ? launchConfigAttributes.get(IDebugUIConstants.ATTR_CAPTURE_IN_FILE) : null; + final File outFile = value != null ? new File((String) value) : null; + value = launchConfigAttributes != null ? launchConfigAttributes.get(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE) : null; + final boolean checkOutput = value != null ? (boolean) value : true; + final IDocument doc = console.getDocument(); + + if (outFile != null) { + @SuppressWarnings("restriction") + String expectedPathMsg = MessageFormat.format(org.eclipse.debug.internal.ui.views.console.ConsoleMessages.ProcessConsole_1, new Object[] { + outFile.getAbsolutePath() }); + assertEquals("No or wrong output of redirect file path in console.", expectedPathMsg, doc.get(doc.getLineOffset(0), doc.getLineLength(0))); + assertEquals("Expected redirect file path to be linked.", 1, console.getHyperlinks().length); + } + if (checkOutput) { + assertEquals("Output not found in console.", new String(testContent), doc.get(doc.getLineOffset(1), doc.getLineLength(1))); + } + return console; + } finally { + if (!process.isTerminated()) { + process.terminate(); + } + consoleManager.removeConsoles(new IConsole[] { console }); + TestUtil.waitForJobs(name.getMethodName(), 0, 1000); + } + } } diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java index be44bdc16..a759e5dbc 100644 --- a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java +++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/views/console/ProcessConsole.java @@ -34,6 +34,7 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IStorage; @@ -884,22 +885,12 @@ public class ProcessConsole extends IOConsole implements IConsole, IDebugEventSe String fFilePath; public ConsoleLogFilePatternMatcher(String filePath) { - fFilePath = escape(filePath); - } - - private String escape(String path) { - StringBuilder buffer = new StringBuilder(path); - int index = buffer.indexOf("\\"); //$NON-NLS-1$ - while (index >= 0) { - buffer.insert(index, '\\'); - index = buffer.indexOf("\\", index+2); //$NON-NLS-1$ - } - return buffer.toString(); + fFilePath = filePath; } @Override public String getPattern() { - return fFilePath; + return Pattern.quote(fFilePath); } @Override |