From 12014fef8ed4050f84427028bed48dcac2e9ddfe Mon Sep 17 00:00:00 2001 From: Jonah Graham Date: Mon, 27 Oct 2025 10:39:40 -0400 Subject: [PATCH 1/5] Compare actual RGB values when comparing image datas with palette data For image datas without palette data (direct palettes) the code before worked ok because the getPixel's returned value was the direct color. But for palette images, the getPixel returns the index. When an index is used, you need to call getRGB to get the real data value. Part of https://github.com/eclipse-platform/eclipse.platform.swt/issues/2126 --- .../org/eclipse/swt/tests/graphics/ImageDataTestHelper.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/graphics/ImageDataTestHelper.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/graphics/ImageDataTestHelper.java index 98265d833cc..da83f012e05 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/graphics/ImageDataTestHelper.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/graphics/ImageDataTestHelper.java @@ -223,7 +223,9 @@ public static Comparator imageDataComparator() { .thenComparing((ImageData firstData, ImageData secondData) -> { for (int x = 0; x < firstData.width; x++) { for (int y = 0; y < firstData.height; y++) { - if (firstData.getPixel(x, y) != secondData.getPixel(x, y)) { + RGB first = firstData.palette.getRGB(firstData.getPixel(x, y)); + RGB second = secondData.palette.getRGB(secondData.getPixel(x, y)); + if (!first.equals(second)) { return -1; } if (firstData.getAlpha(x, y) != secondData.getAlpha(x, y)) { From db493490e49b8932e0c2d72498c5f77bfda9b508 Mon Sep 17 00:00:00 2001 From: Jonah Graham Date: Fri, 24 Oct 2025 19:19:04 -0400 Subject: [PATCH 2/5] Add option to debug ClipboardTest remote application Added RemoteClipboard.DEBUG_REMOTE so that ClipboardTest can be launched as a standalone java application to make it easier to debug the remote end of the tests. Part of https://github.com/eclipse-platform/eclipse.platform.swt/issues/2126 --- .../swt/tests/junit/RemoteClipboard.java | 97 +++++++++++-------- .../data/clipboard/ClipboardCommands.java | 4 + .../data/clipboard/ClipboardTest.java | 5 +- 3 files changed, 62 insertions(+), 44 deletions(-) diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/RemoteClipboard.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/RemoteClipboard.java index 6c2875e0931..2ee066bebd0 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/RemoteClipboard.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/RemoteClipboard.java @@ -35,12 +35,63 @@ import clipboard.ClipboardCommands; public class RemoteClipboard implements ClipboardCommands { + /** + * Set to true to manually launch the ClipboardTest. This is useful so you can + * debug the Swing side. + */ + private static boolean DEBUG_REMOTE = false; private ClipboardCommands remote; private Process remoteClipboardProcess; private Path remoteClipboardTempDir; public void start() throws Exception { assertNull(remote, "Create a new instance to restart"); + + int port = DEBUG_REMOTE ? ClipboardCommands.DEFAULT_PORT : launchRemote(); + try { + Registry reg = LocateRegistry.getRegistry("127.0.0.1", port); + long stopTime = System.currentTimeMillis() + 10000; + do { + try { + remote = (ClipboardCommands) reg.lookup(ClipboardCommands.ID); + break; + } catch (NotBoundException e) { + // try again because the remote app probably hasn't bound yet + } + } while (System.currentTimeMillis() < stopTime); + } catch (RemoteException e) { + + Integer exitValue = null; + boolean waitFor = false; + try { + waitFor = remoteClipboardProcess.waitFor(5, TimeUnit.SECONDS); + if (waitFor) { + exitValue = remoteClipboardProcess.exitValue(); + } + } catch (InterruptedException e1) { + Thread.interrupted(); + } + + String message = "Failed to get remote clipboards command, this seems to happen on macOS on I-build tests. Exception: " + + e.toString() + " waitFor: " + waitFor + " exitValue: " + exitValue; + + // Give some diagnostic information to help track down why this fails on build + // machine. We only hard error on Linux, for other platforms we allow test to + // just be skipped until we track down what is causing + // https://github.com/eclipse-platform/eclipse.platform.swt/issues/2553 + assumeTrue(SwtTestUtil.isGTK, message); + throw new RuntimeException(message, e); + } + assertNotNull(remote); + + // Run a no-op on the Swing event loop so that we know it is idle + // and we can continue startup + remote.waitUntilReady(); + remote.setFocus(); + remote.waitUntilReady(); + } + + private int launchRemote() throws IOException { /* * The below copy using getPath may be redundant (i.e. it may be possible to run * the class files from where they currently reside in the bin folder or the @@ -66,7 +117,7 @@ public void start() throws Exception { String javaExe = javaHome + "/bin/java" + (SwtTestUtil.isWindowsOS ? ".exe" : ""); assertTrue(Files.exists(Path.of(javaExe))); - ProcessBuilder pb = new ProcessBuilder(javaExe, "clipboard.ClipboardTest") + ProcessBuilder pb = new ProcessBuilder(javaExe, "clipboard.ClipboardTest", "-autoport") .directory(remoteClipboardTempDir.toFile()); pb.inheritIO(); pb.redirectOutput(Redirect.PIPE); @@ -85,47 +136,7 @@ public void start() throws Exception { throw new RuntimeException("Failed to get port"); }); assertNotEquals(0, port); - try { - Registry reg = LocateRegistry.getRegistry("127.0.0.1", port); - long stopTime = System.currentTimeMillis() + 10000; - do { - try { - remote = (ClipboardCommands) reg.lookup(ClipboardCommands.ID); - break; - } catch (NotBoundException e) { - // try again because the remote app probably hasn't bound yet - } - } while (System.currentTimeMillis() < stopTime); - } catch (RemoteException e) { - - Integer exitValue = null; - boolean waitFor = false; - try { - waitFor = remoteClipboardProcess.waitFor(5, TimeUnit.SECONDS); - if (waitFor) { - exitValue = remoteClipboardProcess.exitValue(); - } - } catch (InterruptedException e1) { - Thread.interrupted(); - } - - String message = "Failed to get remote clipboards command, this seems to happen on macOS on I-build tests. Exception: " - + e.toString() + " waitFor: " + waitFor + " exitValue: " + exitValue; - - // Give some diagnostic information to help track down why this fails on build - // machine. We only hard error on Linux, for other platforms we allow test to - // just be skipped until we track down what is causing - // https://github.com/eclipse-platform/eclipse.platform.swt/issues/2553 - assumeTrue(SwtTestUtil.isGTK, message); - throw new RuntimeException(message, e); - } - assertNotNull(remote); - - // Run a no-op on the Swing event loop so that we know it is idle - // and we can continue startup - remote.waitUntilReady(); - remote.setFocus(); - remote.waitUntilReady(); + return port; } @Override @@ -207,7 +218,7 @@ public void waitUntilReady() throws RemoteException { } @Override - public void waitForButtonPress() throws RemoteException { + public void waitForButtonPress() throws RemoteException { remote.waitForButtonPress(); } } \ No newline at end of file diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java index 56741a7673a..c98326bf237 100644 --- a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java +++ b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java @@ -26,6 +26,10 @@ public interface ClipboardCommands extends Remote { */ int SELECTION_CLIPBOARD = 1 << 1; + /** + * The port the RMI will listen on by default + */ + int DEFAULT_PORT = 3456; void stop() throws RemoteException; diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java index 41ddce44d03..d9498b12935 100644 --- a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java +++ b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java @@ -23,6 +23,7 @@ import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.RMISocketFactory; +import java.util.Arrays; import javax.swing.JButton; import javax.swing.JFrame; @@ -117,9 +118,11 @@ public void log(String log) { public static void main(String[] args) throws IOException { System.setProperty("java.rmi.server.hostname", "127.0.0.1"); + boolean autoPort = Arrays.asList(args).contains("-autoport"); + // Make sure RMI is localhost only RMISocketFactory.setSocketFactory(new LocalHostOnlySocketFactory()); - int chosenPort = getAvailablePort(); + int chosenPort = autoPort ? getAvailablePort() : ClipboardCommands.DEFAULT_PORT; rmiRegistry = LocateRegistry.createRegistry(chosenPort); System.out.println(ClipboardCommands.PORT_MESSAGE + chosenPort); From 9ff413ee9ad82d1882ad6b78f5e3cdca06475b7b Mon Sep 17 00:00:00 2001 From: Jonah Graham Date: Sun, 26 Oct 2025 17:47:48 -0400 Subject: [PATCH 3/5] Add a bit of info to the window so testers know what is happening In previous commits I added a lot more use of the remote app, so this commit adds a little bit of info to the window so that if someone running unit tests sees it they know what is going on. Part of https://github.com/eclipse-platform/eclipse.platform.swt/issues/2126 --- .../data/clipboard/ClipboardTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java index d9498b12935..4be556e92ee 100644 --- a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java +++ b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java @@ -27,6 +27,7 @@ import javax.swing.JButton; import javax.swing.JFrame; +import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -64,6 +65,14 @@ public ClipboardTest() throws RemoteException { commands = new ClipboardCommandsImpl(this); rmiRegistry.rebind(ClipboardCommands.ID, commands); + JLabel label = new JLabel(""" + This window is a AWT/Swing window used to test the SWT Clipboard
+ This window will be opened and closed repeatedly while running
+ clipboard JUnit tests. Do not close this window (unless the JUnit
+ tests have completed)
+
+ If the Press Button to Continue Test becomes enabled, press it. + """); textArea = new JTextArea(10, 40); JScrollPane scrollPane = new JScrollPane(textArea); @@ -90,6 +99,7 @@ public ClipboardTest() throws RemoteException { } }); + pressToContinue.setEnabled(false); pressToContinue.addActionListener(e -> { commands.buttonPressed.countDown(); }); @@ -99,6 +109,7 @@ public ClipboardTest() throws RemoteException { buttonPanel.add(pasteButton); buttonPanel.add(pressToContinue); + add(label, BorderLayout.NORTH); add(scrollPane, BorderLayout.CENTER); add(buttonPanel, BorderLayout.SOUTH); From 41c21347d5d3f5f97f1a10d653726931a585db73 Mon Sep 17 00:00:00 2001 From: Jonah Graham Date: Sat, 25 Oct 2025 15:23:21 -0400 Subject: [PATCH 4/5] Clear Clipboard AfterEach in tests This is a workaround for the issue described in https://github.com/eclipse-platform/eclipse.platform.swt/issues/2675 Part of https://github.com/eclipse-platform/eclipse.platform.swt/issues/2126 --- .../JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java index 815f090065f..073a3c8b585 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java @@ -168,6 +168,7 @@ public void tearDown() throws Exception { } } finally { if (clipboard != null) { + supportedClipboardIds().forEach(clipboard::clearContents); clipboard.dispose(); } if (shell != null) { From ccfc322b8835e8c8426fb78765a3ad6d3c7e00aa Mon Sep 17 00:00:00 2001 From: Jonah Graham Date: Fri, 24 Oct 2025 11:44:47 -0400 Subject: [PATCH 5/5] Add tests for all DND transfer types The individual Transfer types had no automatic testing, so this commit provides tests for all the transfer types and ensures copy/paste to external to SWT works too by implementing flavors for all types in the Swing remote clipboard test app. Part of https://github.com/eclipse-platform/eclipse.platform.swt/issues/2126 --- .../swt/tests/junit/AllNonBrowserTests.java | 9 +- .../swt/tests/junit/ClipboardBase.java | 24 ++ .../swt/tests/junit/RemoteClipboard.java | 84 ++++- ...org_eclipse_swt_dnd_ByteArrayTransfer.java | 296 ++++++++++++++++++ ...Test_org_eclipse_swt_dnd_FileTransfer.java | 195 ++++++++++++ ...Test_org_eclipse_swt_dnd_HTMLTransfer.java | 158 ++++++++++ ...est_org_eclipse_swt_dnd_ImageTransfer.java | 164 ++++++++++ .../Test_org_eclipse_swt_dnd_RTFTransfer.java | 152 +++++++++ ...Test_org_eclipse_swt_dnd_TextTransfer.java | 151 +++++++++ .../Test_org_eclipse_swt_dnd_URLTransfer.java | 161 ++++++++++ .../data/clipboard/ClipboardCommands.java | 40 ++- .../data/clipboard/ClipboardCommandsImpl.java | 176 ++++++++++- .../data/clipboard/ClipboardTest.java | 14 +- .../data/clipboard/FileListSelection.java | 41 +++ .../data/clipboard/HtmlSelection.java | 68 ++++ .../data/clipboard/ImageSelection.java | 41 +++ .../data/clipboard/MyTypeSelection.java | 52 +++ .../data/clipboard/RtfSelection.java | 68 ++++ .../data/clipboard/UrlSelection.java | 68 ++++ 19 files changed, 1947 insertions(+), 15 deletions(-) create mode 100644 tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ByteArrayTransfer.java create mode 100644 tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_FileTransfer.java create mode 100644 tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_HTMLTransfer.java create mode 100644 tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ImageTransfer.java create mode 100644 tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_RTFTransfer.java create mode 100644 tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_TextTransfer.java create mode 100644 tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_URLTransfer.java create mode 100644 tests/org.eclipse.swt.tests/data/clipboard/FileListSelection.java create mode 100644 tests/org.eclipse.swt.tests/data/clipboard/HtmlSelection.java create mode 100644 tests/org.eclipse.swt.tests/data/clipboard/ImageSelection.java create mode 100644 tests/org.eclipse.swt.tests/data/clipboard/MyTypeSelection.java create mode 100644 tests/org.eclipse.swt.tests/data/clipboard/RtfSelection.java create mode 100644 tests/org.eclipse.swt.tests/data/clipboard/UrlSelection.java diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java index 52bc3d6a4d1..642edfd9bd8 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/AllNonBrowserTests.java @@ -43,7 +43,14 @@ Test_org_eclipse_swt_accessibility_AccessibleControlEvent.class, // Test_org_eclipse_swt_accessibility_AccessibleEvent.class, // Test_org_eclipse_swt_accessibility_AccessibleTextEvent.class, // - Test_org_eclipse_swt_dnd_Clipboard.class, + Test_org_eclipse_swt_dnd_ByteArrayTransfer.class, // + Test_org_eclipse_swt_dnd_Clipboard.class, // + Test_org_eclipse_swt_dnd_FileTransfer.class, // + Test_org_eclipse_swt_dnd_HTMLTransfer.class, // + Test_org_eclipse_swt_dnd_ImageTransfer.class, // + Test_org_eclipse_swt_dnd_RTFTransfer.class, // + Test_org_eclipse_swt_dnd_TextTransfer.class, // + Test_org_eclipse_swt_dnd_URLTransfer.class, // Test_org_eclipse_swt_events_ArmEvent.class, // Test_org_eclipse_swt_events_ControlEvent.class, // Test_org_eclipse_swt_events_DisposeEvent.class, // diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java index 073a3c8b585..d1d247d21cf 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/ClipboardBase.java @@ -31,6 +31,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import clipboard.ClipboardTest; + /** * Base class for tests that test clipboard and transfer types */ @@ -178,6 +180,28 @@ public void tearDown() throws Exception { } } + protected String addTrailingNulCharacter(String result) { + return result + '\0'; + } + + /** + * Trim trailing nul character - some transfer types require a trailing nul when + * copied to the clipboard, so use this method to remove it for tests that + * obtain bytes from the {@link ClipboardTest} app. + * + * @param result to trim terminating nul character from + * @return string with the nul character trimmed, or null if result was null. + */ + protected String trimTrailingNulCharacter(String result) { + if (result == null) { + return null; + } + if (result.charAt(result.length() - 1) == '\0') { + result = result.substring(0, result.length() - 1); + } + return result; + } + /** * Make sure to always copy/paste unique strings - this ensures that tests run * under {@link RepeatedTest}s don't false pass because of clipboard value on diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/RemoteClipboard.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/RemoteClipboard.java index 2ee066bebd0..b2a1c13bc12 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/RemoteClipboard.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/RemoteClipboard.java @@ -106,7 +106,13 @@ private int launchRemote() throws IOException { "ClipboardTest", // "ClipboardCommands", // "ClipboardCommandsImpl", // - "ClipboardTest$LocalHostOnlySocketFactory" // + "ClipboardTest$LocalHostOnlySocketFactory", // + "FileListSelection", // + "HtmlSelection", // + "ImageSelection", // + "MyTypeSelection", // + "RtfSelection", // + "UrlSelection" // ).forEach((f) -> { // extract the files and put them in the temp directory SwtTestUtil.copyFile("/clipboard/" + f + ".class", @@ -141,6 +147,9 @@ private int launchRemote() throws IOException { @Override public void stop() throws RemoteException { + if (DEBUG_REMOTE) { + return; + } try { stopProcess(); } catch (InterruptedException e) { @@ -197,6 +206,11 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx } } + @Override + public void setContents(String string) throws RemoteException { + setContents(string, CLIPBOARD); + } + @Override public void setContents(String string, int clipboardId) throws RemoteException { remote.setContents(string, clipboardId); @@ -207,6 +221,11 @@ public void setFocus() throws RemoteException { remote.setFocus(); } + @Override + public String getStringContents() throws RemoteException { + return getStringContents(CLIPBOARD); + } + @Override public String getStringContents(int clipboardId) throws RemoteException { return remote.getStringContents(clipboardId); @@ -221,4 +240,65 @@ public void waitUntilReady() throws RemoteException { public void waitForButtonPress() throws RemoteException { remote.waitForButtonPress(); } -} \ No newline at end of file + + @Override + public void setRtfContents(String test) throws RemoteException { + remote.setRtfContents(test); + } + + @Override + public String getRtfContents() throws RemoteException { + return remote.getRtfContents(); + } + + @Override + public void setHtmlContents(String test) throws RemoteException { + remote.setHtmlContents(test); + } + + @Override + public String getHtmlContents() throws RemoteException { + return remote.getHtmlContents(); + } + + @Override + public void setUrlContents(byte[] test) throws RemoteException { + remote.setUrlContents(test); + } + + @Override + public byte[] getUrlContents() throws RemoteException { + return remote.getUrlContents(); + } + + @Override + public void setImageContents(byte[] imageContents) throws RemoteException { + remote.setImageContents(imageContents); + } + + @Override + public byte[] getImageContents() throws RemoteException { + return remote.getImageContents(); + } + + @Override + public void setFileListContents(String[] fileList) throws RemoteException { + remote.setFileListContents(fileList); + } + + @Override + public String[] getFileListContents() throws RemoteException { + return remote.getFileListContents(); + } + + @Override + public void setMyTypeContents(byte[] bytes) throws RemoteException { + remote.setMyTypeContents(bytes); + } + + @Override + public byte[] getMyTypeContents() throws RemoteException { + return remote.getMyTypeContents(); + } + +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ByteArrayTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ByteArrayTransfer.java new file mode 100644 index 00000000000..e04b408cdf2 --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ByteArrayTransfer.java @@ -0,0 +1,296 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.swt.dnd.ByteArrayTransfer; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +/** + * Automated Test Suite for class {@link ByteArrayTransfer} + * + * ByteArrayTransfer is abstract, so we test it via a custom subclass based on + * the javadoc of ByteArrayTransfer, the inner classes below {@link MyType} and + * {@link MyTypeTransfer} + */ +@Tag("clipboard") +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_ByteArrayTransfer extends ClipboardBase { + private MyTypeTransfer myTypeTransfer; + + @BeforeEach + public void localSetup() { + myTypeTransfer = MyTypeTransfer.getInstance(); + } + + private MyType getMyTypeInstance() { + MyType myType = new MyType(); + myType.fileName = getUniqueTestString(); + myType.fileLength = 1234; + myType.lastModified = 5678; + return myType; + } + + /** + * Duplicates what is in + * {@link MyTypeTransfer#javaToNative(Object, TransferData)} + * + * It is duplicated so the MyTypeTransfer can stay matching what is in + * {@link ByteArrayTransfer} javadoc. + */ + private byte[] serializeMyType(MyType myType) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (DataOutputStream writeOut = new DataOutputStream(out)) { + byte[] fileNameBytes = myType.fileName.getBytes(StandardCharsets.UTF_8); + writeOut.writeInt(fileNameBytes.length); + writeOut.write(fileNameBytes); + writeOut.writeLong(myType.fileLength); + writeOut.writeLong(myType.lastModified); + return out.toByteArray(); + } + } + + /** + * Duplicates what is in {@link MyTypeTransfer#nativeToJava(TransferData)} + * + * It is duplicated so the MyTypeTransfer can stay matching what is in + * {@link ByteArrayTransfer} javadoc. + */ + private MyType deserializeMyType(byte[] buffer) throws IOException { + ByteArrayInputStream in = new ByteArrayInputStream(buffer); + try (DataInputStream readIn = new DataInputStream(in)) { + MyType myType = new MyType(); + int size = readIn.readInt(); + byte[] name = new byte[size]; + readIn.read(name); + myType.fileName = new String(name); + myType.fileLength = readIn.readLong(); + myType.lastModified = readIn.readLong(); + return myType; + } + } + + private void assertMyTypeEquals(MyType expected, MyType actual) { + assertEquals(expected.fileName, actual.fileName); + assertEquals(expected.fileLength, actual.fileLength); + assertEquals(expected.lastModified, actual.lastModified); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, myTypeTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private MyType getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(myTypeTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(MyType.class, o); + return (MyType) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + MyType test = getMyTypeInstance(); + setContents(test); + assertMyTypeEquals(test, getContents()); + assertThrows(IllegalArgumentException.class, () -> setContents(getUniqueTestString())); + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(getMyTypeInstance()); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(myTypeTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(new String[] { getUniqueTestString(), // + getUniqueTestString() }, FileTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(myTypeTransfer::isSupportedType)); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @Test + public void test_internalClone() throws Exception { + MyType test = getMyTypeInstance(); + + openAndFocusShell(false); + + setContents(test); + MyType contents = getContents(); + assertMyTypeEquals(test, contents); + } + + @Test + public void test_nativeToJava() throws Exception { + MyType test = getMyTypeInstance(); + + openAndFocusRemote(); + remote.setMyTypeContents(serializeMyType(test)); + openAndFocusShell(false); + assertMyTypeEquals(test, getContents()); + } + + @Order(1) + @Test + @DisabledOnOs(value = OS.MAC, disabledReason = """ + remote.getMyTypeContents doesn't work properly on macOS, this is + probably a test only issue on macOS + """) + public void test_javaToNative() throws Exception { + MyType test = getMyTypeInstance(); + + openAndFocusShell(true); + setContents(test); + openAndFocusRemote(); + byte[] resultBytes = SwtTestUtil.runOperationInThread(() -> remote.getMyTypeContents()); + MyType result = deserializeMyType(resultBytes); + assertMyTypeEquals(test, result); + } + + public static class MyType { + public String fileName; + public long fileLength; + public long lastModified; + } + + public static class MyTypeTransfer extends ByteArrayTransfer { + + private static final String MYTYPENAME = "my_type_name"; + private static final int MYTYPEID = registerType(MYTYPENAME); + private static MyTypeTransfer _instance = new MyTypeTransfer(); + + private MyTypeTransfer() { + } + + public static MyTypeTransfer getInstance() { + return _instance; + } + + @Override + public void javaToNative(Object object, TransferData transferData) { + if (!checkMyType(object) || !isSupportedType(transferData)) { + DND.error(DND.ERROR_INVALID_DATA); + } + + MyType myType = (MyType) object; + // write data to a byte array and then ask super to convert to native + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (DataOutputStream writeOut = new DataOutputStream(out)) { + byte[] fileNameBytes = myType.fileName.getBytes(StandardCharsets.UTF_8); + writeOut.writeInt(fileNameBytes.length); + writeOut.write(fileNameBytes); + writeOut.writeLong(myType.fileLength); + writeOut.writeLong(myType.lastModified); + super.javaToNative(out.toByteArray(), transferData); + } catch (IOException e) { + } + } + + @Override + public Object nativeToJava(TransferData transferData) { + if (!isSupportedType(transferData)) { + return null; + } + + byte[] buffer = (byte[]) super.nativeToJava(transferData); + if (buffer == null) { + return null; + } + + ByteArrayInputStream in = new ByteArrayInputStream(buffer); + try (DataInputStream readIn = new DataInputStream(in)) { + MyType myType = new MyType(); + int size = readIn.readInt(); + byte[] name = new byte[size]; + readIn.read(name); + myType.fileName = new String(name); + myType.fileLength = readIn.readLong(); + myType.lastModified = readIn.readLong(); + return myType; + } catch (IOException ex) { + } + return null; + } + + @Override + protected String[] getTypeNames() { + return new String[] { MYTYPENAME }; + } + + @Override + protected int[] getTypeIds() { + return new int[] { MYTYPEID }; + } + + private boolean checkMyType(Object object) { + return (object instanceof MyType); + } + + @Override + protected boolean validate(Object object) { + return checkMyType(object); + } + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_FileTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_FileTransfer.java new file mode 100644 index 00000000000..a960536ed27 --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_FileTransfer.java @@ -0,0 +1,195 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.RTFTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +/** + * Automated Test Suite for {@link FileTransfer} + */ +@Tag("clipboard") +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_FileTransfer extends ClipboardBase { + /** + * FileTransfer is slightly mis-named as it takes a list. + */ + private FileTransfer fileTransfer; + + private List tmpFilesToDelete = new ArrayList<>(); + + @BeforeEach + public void localSetup() { + fileTransfer = FileTransfer.getInstance(); + } + + /** + * Return a new name of a tempfile and clean it up at the end of the test + * + * This is probably more heavy weight than needed, but it is to ensure the file + * list has real paths suitable to the platform we are running on. + */ + private String tempFile() throws IOException { + Path tempFile = Files.createTempFile("swt-test", ""); + tmpFilesToDelete.add(tempFile); + return tempFile.toString(); + } + + @AfterEach + public void deleteTempFiles() throws IOException { + for (Path path : tmpFilesToDelete) { + Files.delete(path); + } + } + + private String[] getFileList() throws IOException { + return new String[] { tempFile(), tempFile(), tempFile() }; + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, fileTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private String[] getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(fileTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(String[].class, o); + return (String[]) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + + String[] fileList = getFileList(); + setContents(fileList); + assertArrayEquals(fileList, getContents()); + + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(Arrays.asList(getFileList()))); + assertThrows(IllegalArgumentException.class, () -> setContents(new String[0])); + assertThrows(IllegalArgumentException.class, () -> setContents(new String[] { null })); + assertThrows(IllegalArgumentException.class, () -> setContents(new String[] { "" })); + assertThrows(IllegalArgumentException.class, () -> setContents(new String[] { tempFile(), null })); + assertThrows(IllegalArgumentException.class, () -> setContents(new String[] { tempFile(), "" })); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(getFileList()); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(fileTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(getUniqueTestString(), RTFTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(fileTransfer::isSupportedType)); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @Test + public void test_internalClone() throws Exception { + String[] fileList = getFileList(); + + openAndFocusShell(false); + // Put a type on the clipboard and ensure we don't match it + setContents(fileList); + String[] contents = getContents(); + assertArrayEquals(fileList, contents); + } + + @Test + @DisabledOnOs(value = { OS.WINDOWS, OS.MAC }, disabledReason = """ + AWT's DataFlavor.javaFileListFlavor used to test this method is not quite + compatible with FileTransfer on Windows or macOS. + + Or the implementation of remote.setFileListContents is incorrect. + + On Windows DataFlavor.javaFileListFlavor does manipulate the contents of the + strings by replacing long versions of folder names with 8.3 versions, so + a different test method is needed to make this test stable + """) + public void test_nativeToJava() throws Exception { + + String[] fileList = getFileList(); + + openAndFocusRemote(); + remote.setFileListContents(fileList); + openAndFocusShell(false); + + assertArrayEquals(fileList, getContents()); + } + + @Order(1) + @Test + public void test_javaToNative() throws Exception { + String[] fileList = getFileList(); + + openAndFocusShell(true); + setContents(fileList); + openAndFocusRemote(); + String[] contents = SwtTestUtil.runOperationInThread(() -> remote.getFileListContents()); + assertArrayEquals(fileList, contents); + + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_HTMLTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_HTMLTransfer.java new file mode 100644 index 00000000000..344a10d8008 --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_HTMLTransfer.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.HTMLTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Automated Test Suite for {@link HTMLTransfer} + */ +@Tag("clipboard") +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_HTMLTransfer extends ClipboardBase { + private HTMLTransfer htmlTransfer; + + @BeforeEach + public void localSetup() { + htmlTransfer = HTMLTransfer.getInstance(); + } + + private String toHtml(String s) { + return "" + s + ""; + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, htmlTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private String getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(htmlTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(String.class, o); + return (String) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + String helloWorld = toHtml(getUniqueTestString()); + setContents(helloWorld); + assertEquals(helloWorld, getContents()); + setContents(" "); // whitespace only - HTML restrictions, at least within SWT are very mild + assertEquals(" ", getContents()); + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(toHtml(getUniqueTestString())); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(htmlTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(new String[] { getUniqueTestString(), // + getUniqueTestString() }, FileTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(htmlTransfer::isSupportedType)); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @Test + public void test_internalClone() throws Exception { + String test = toHtml(getUniqueTestString()); + + openAndFocusShell(false); + setContents(test); + String contents = getContents(); + assertEquals(test, contents); + } + + static Stream testData() { + return Stream.of(// + Arguments.of("hello_world", "Hello, World!") // + ); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @ParameterizedTest(name = "{0}") + @MethodSource("testData") + public void test_nativeToJava(String name, String test) throws Exception { + openAndFocusRemote(); + remote.setHtmlContents(test); + openAndFocusShell(false); + assertEquals(test, getContents()); + } + + @Order(1) + @Test + public void test_javaToNative() throws Exception { + String test = toHtml(getUniqueTestString()); + + openAndFocusShell(true); + setContents(test); + openAndFocusRemote(); + String result = SwtTestUtil.runOperationInThread(() -> remote.getHtmlContents()); + result = trimTrailingNulCharacter(result); + assertEquals(test, result); + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ImageTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ImageTransfer.java new file mode 100644 index 00000000000..158e1f0a8ea --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_ImageTransfer.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.eclipse.swt.tests.graphics.ImageDataTestHelper.imageDataComparator; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.ImageTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * Automated Test Suite for class {@link ImageTransfer} + */ +@Tag("clipboard") +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_ImageTransfer extends ClipboardBase { + private ImageTransfer imageTransfer; + + @BeforeEach + public void localSetup() { + imageTransfer = ImageTransfer.getInstance(); + } + + private InputStream getImageAsInputStream() { + return SwtTestUtil.class.getResourceAsStream(SwtTestUtil.imageFilenames[0] + "." + SwtTestUtil.imageFormats[0]); + } + + private ImageData getImageData() throws IOException { + try (InputStream stream = getImageAsInputStream()) { + return new ImageData(stream); + } + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, imageTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private ImageData getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(imageTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(ImageData.class, o); + return (ImageData) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + + ImageData imageData = getImageData(); + setContents(imageData); + assertEquals(0, imageDataComparator().compare(imageData, getContents())); + + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(new Image(display, imageData))); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(getImageData()); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(imageTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(new String[] { getUniqueTestString(), // + getUniqueTestString() }, FileTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(imageTransfer::isSupportedType)); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @Test + public void test_internalClone() throws Exception { + ImageData imageData = getImageData(); + + openAndFocusShell(false); + // Put a type on the clipboard and ensure we don't match it + setContents(imageData); + ImageData contents = getContents(); + assertEquals(0, imageDataComparator().compare(imageData, contents)); + } + + @Test + public void test_nativeToJava() throws Exception { + + byte[] fileContents; + try (InputStream stream = getImageAsInputStream()) { + fileContents = stream.readAllBytes(); + } + + openAndFocusRemote(); + remote.setImageContents(fileContents); + openAndFocusShell(false); + ImageData expected = getImageData(); + assertEquals(0, imageDataComparator().compare(expected, getContents())); + } + + @Order(1) + @Test + public void test_javaToNative() throws Exception { + ImageData expected = getImageData(); + + openAndFocusShell(true); + setContents(expected); + openAndFocusRemote(); + byte[] fileContents = SwtTestUtil.runOperationInThread(() -> remote.getImageContents()); + ImageData result = new ImageData(new ByteArrayInputStream(fileContents)); + assertEquals(0, imageDataComparator().compare(expected, result)); + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_RTFTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_RTFTransfer.java new file mode 100644 index 00000000000..6c53170cb90 --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_RTFTransfer.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.RTFTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * Automated Test Suite for {@link RTFTransfer} + */ +@Tag("clipboard") +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_RTFTransfer extends ClipboardBase { + private RTFTransfer rtfTransfer; + + @BeforeEach + public void localSetup() { + rtfTransfer = RTFTransfer.getInstance(); + } + + private String toRtf(String s) { + return "{\\rtf1\\b\\i " + s + "}"; + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, rtfTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private String getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(rtfTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(String.class, o); + return (String) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + String helloWorld = toRtf(getUniqueTestString()); + setContents(helloWorld); + assertEquals(helloWorld, getContents()); + setContents(" "); // whitespace only - RTF restrictions, at least within SWT are very mild + assertEquals(" ", getContents()); + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(toRtf(getUniqueTestString())); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(rtfTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(new String[] { getUniqueTestString(), // + getUniqueTestString() }, FileTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(rtfTransfer::isSupportedType)); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @Test + public void test_internalClone() throws Exception { + String test = toRtf(getUniqueTestString()); + + openAndFocusShell(false); + setContents(test); + String contents = getContents(); + assertEquals(test, contents); + } + + @Test + public void test_nativeToJava() throws Exception { + String test = toRtf(getUniqueTestString()); + + openAndFocusRemote(); + String trailingNulCharacter = test; + if (SwtTestUtil.isWindows) { + // Windows require nul trailing character, but + // other Platforms don't want that + trailingNulCharacter = addTrailingNulCharacter(test); + } + + remote.setRtfContents(trailingNulCharacter); + openAndFocusShell(false); + assertEquals(test, getContents()); + } + + @Order(1) + @Test + public void test_javaToNative() throws Exception { + String test = toRtf(getUniqueTestString()); + + openAndFocusShell(true); + setContents(test); + openAndFocusRemote(); + String result = SwtTestUtil.runOperationInThread(() -> remote.getRtfContents()); + result = trimTrailingNulCharacter(result); + assertEquals(test, result); + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_TextTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_TextTransfer.java new file mode 100644 index 00000000000..04478f1134f --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_TextTransfer.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Automated Test Suite for {@link TextTransfer} + */ +@Tag("clipboard") +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_TextTransfer extends ClipboardBase { + private TextTransfer textTransfer; + + @BeforeEach + public void localSetup() { + textTransfer = TextTransfer.getInstance(); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, textTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private String getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(textTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(String.class, o); + return (String) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + String helloWorld = getUniqueTestString(); + setContents(helloWorld); + assertEquals(helloWorld, getContents()); + setContents(" "); // whitespace only + assertEquals(" ", getContents()); + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(getUniqueTestString()); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(textTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(new String[] { getUniqueTestString(), // + getUniqueTestString() }, FileTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(textTransfer::isSupportedType)); + } + + static Stream testData() { + return Stream.of(// + Arguments.of("hello_world", "Hello, World!"), // + Arguments.of("blank_non_empty", " "), // + Arguments.of("emoji_grinning_face", "\uD83D\uDE00") // + ); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @ParameterizedTest(name = "{0}") + @MethodSource("testData") + public void test_internalClone(String name, String test) throws Exception { + openAndFocusShell(false); + + setContents(test); + String contents = getContents(); + assertEquals(test, contents); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("testData") + public void test_nativeToJava(String name, String test) throws Exception { + openAndFocusRemote(); + remote.setContents(test); + openAndFocusShell(false); + assertEquals(test, getContents()); + } + + @Order(1) + @ParameterizedTest(name = "{0}") + @MethodSource("testData") + public void test_javaToNative(String name, String test) throws Exception { + openAndFocusShell(true); + setContents(test); + openAndFocusRemote(); + String result = SwtTestUtil.runOperationInThread(() -> remote.getStringContents()); + result = trimTrailingNulCharacter(result); + assertEquals(test, result); + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_URLTransfer.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_URLTransfer.java new file mode 100644 index 00000000000..73ffdde14d6 --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_dnd_URLTransfer.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.eclipse.swt.dnd.URLTransfer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +/** + * Automated Test Suite for {@link URLTransfer} + */ +@Tag("clipboard") +@DisabledOnOs(value=OS.MAC, disabledReason = """ + URLTransfer doesn't seem to work properly on mac, perhaps + this is a side effect of mac's version using a deprecated + transfer type. See https://github.com/eclipse-platform/eclipse.platform.swt/pull/2669 + """) +@TestMethodOrder(OrderAnnotation.class) // run tests needing button presses first +public class Test_org_eclipse_swt_dnd_URLTransfer extends ClipboardBase { + private URLTransfer urlTransfer; + + @BeforeEach + public void localSetup() { + urlTransfer = URLTransfer.getInstance(); + } + + /** + * Returns a string matching text/x-moz-url according to + * https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_data_store + */ + private String getURLTransferString() { + return "https://eclipse.dev/eclipse/swt/" + "\n" + getUniqueTestString(); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o) { + setContents(o, urlTransfer); + } + + /** + * Convenience method to set the contents less verbosely + */ + private void setContents(Object o, Transfer transfer) { + clipboard.setContents(new Object[] { o }, new Transfer[] { transfer }); + } + + /** + * Convenience method to get the contents with a short timeout + */ + private String getContents() throws Exception { + CompletableFuture future = clipboard.getContentsAsync(urlTransfer); + SwtTestUtil.processEvents(1000, () -> { + return future.isDone(); + }); + Object o = future.get(); + assertInstanceOf(String.class, o); + return (String) o; + } + + @Test + public void test_Validate() throws Exception { + openAndFocusShell(false); + String mozUrl = getURLTransferString(); + setContents(mozUrl); + assertEquals(mozUrl, getContents()); + assertThrows(IllegalArgumentException.class, () -> setContents("")); + assertThrows(IllegalArgumentException.class, () -> setContents(new Object())); + } + + /** + * Indirecty tests getTypeIds. The actual values of getTypeIds are not part of + * the API, but that they can be applied to isSupportedType is. + */ + @Test + public void test_isSupportedType() throws Exception { + openAndFocusShell(false); + + // Put a type on the clipboard and ensure we match it + setContents(getURLTransferString()); + TransferData[] availableTypes = clipboard.getAvailableTypes(); + assertTrue(Arrays.stream(availableTypes).anyMatch(urlTransfer::isSupportedType)); + + // Put an incompatible type on the clipboard and ensure we don't match it + setContents(new String[] { getUniqueTestString(), // + getUniqueTestString() }, FileTransfer.getInstance()); + availableTypes = clipboard.getAvailableTypes(); + assertFalse(Arrays.stream(availableTypes).anyMatch(urlTransfer::isSupportedType)); + } + + /** + * Tests nativeToJava and javaToNative as a pair to ensure that objects are + * cloned as expected within the application. + */ + @Test + public void test_internalClone() throws Exception { + String test = getURLTransferString(); + + openAndFocusShell(false); + setContents(test); + String contents = getContents(); + assertEquals(test, contents); + } + + @Test + public void test_nativeToJava() throws Exception { + String test = getURLTransferString(); + + openAndFocusRemote(); + + byte[] bytes = test.getBytes(StandardCharsets.UTF_16LE); + remote.setUrlContents(bytes); + openAndFocusShell(false); + assertEquals(test, getContents()); + } + + @Order(1) + @Test + public void test_javaToNative() throws Exception { + String test = getURLTransferString(); + + openAndFocusShell(true); + setContents(test); + openAndFocusRemote(); + byte[] bytes = SwtTestUtil.runOperationInThread(() -> remote.getUrlContents()); + + // This is an attempt to recreate the same bytestream that Firefox provides when + // doing text/x-moz-url which appears to be this encoding (specifically no BOM) + String result = new String(bytes, StandardCharsets.UTF_16LE); + result = trimTrailingNulCharacter(result); + assertEquals(test, result); + } +} diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java index c98326bf237..7007706f09d 100644 --- a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java +++ b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommands.java @@ -33,20 +33,52 @@ public interface ClipboardCommands extends Remote { void stop() throws RemoteException; + void setFocus() throws RemoteException; + + void waitUntilReady() throws RemoteException; + + void waitForButtonPress() throws RemoteException; + /** - * @param string string to set as contents + * @param string string to set as contents on {@link #CLIPBOARD} + */ + void setContents(String string) throws RemoteException; + + /** + * @param string string to set as contents * @param clipboardId {@link #CLIPBOARD} or {@value #SELECTION_CLIPBOARD} */ void setContents(String string, int clipboardId) throws RemoteException; - void setFocus() throws RemoteException; + void setRtfContents(String string) throws RemoteException; + + String getRtfContents() throws RemoteException; + + void setHtmlContents(String string) throws RemoteException; + + String getHtmlContents() throws RemoteException; + + void setUrlContents(byte[] string) throws RemoteException; + + byte[] getUrlContents() throws RemoteException; + + String getStringContents() throws RemoteException; /** * @param clipboardId {@link #CLIPBOARD} or {@value #SELECTION_CLIPBOARD} */ String getStringContents(int clipboardId) throws RemoteException; - void waitUntilReady() throws RemoteException; + void setImageContents(byte[] imageContents) throws RemoteException; + + byte[] getImageContents() throws RemoteException; + + void setFileListContents(String[] fileList) throws RemoteException; + + String[] getFileListContents() throws RemoteException; + + void setMyTypeContents(byte[] bytes) throws RemoteException; + + byte[] getMyTypeContents() throws RemoteException; - void waitForButtonPress() throws RemoteException; } diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommandsImpl.java b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommandsImpl.java index 9a6c7877207..0f1e366f29f 100644 --- a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommandsImpl.java +++ b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardCommandsImpl.java @@ -10,17 +10,28 @@ *******************************************************************************/ package clipboard; +import java.awt.Image; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; +import java.awt.image.BufferedImage; +import java.awt.image.MultiResolutionImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.Arrays; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import javax.imageio.ImageIO; import javax.swing.SwingUtilities; public class ClipboardCommandsImpl extends UnicastRemoteObject implements ClipboardCommands { @@ -60,6 +71,11 @@ public void stop() throws RemoteException { }); } + @Override + public void setContents(String string) throws RemoteException { + setContents(string, CLIPBOARD); + } + @Override public void setContents(String text, int clipboardId) throws RemoteException { invokeAndWait(() -> { @@ -67,10 +83,153 @@ public void setContents(String text, int clipboardId) throws RemoteException { clipboardTest.log("setContents(" + display + ", " + clipboardId + ")"); StringSelection selection = new StringSelection(text); getClipboard(clipboardId).setContents(selection, null); + }); + } + + @Override + public void setRtfContents(String text) throws RemoteException { + invokeAndWait(() -> { + String display = text == null ? "null" : ("\"" + text + "\""); + clipboardTest.log("setRtfContents(" + display + ")"); + getClipboard(CLIPBOARD).setContents(new RtfSelection(text.getBytes(StandardCharsets.UTF_8)), null); + }); + } + + @Override + public String getRtfContents() throws RemoteException { + Object contents = getContents(CLIPBOARD, "getRtfContents", RtfSelection.flavor); + if (!(contents instanceof InputStream stream)) { + return null; + } + try (stream) { + byte[] allBytes = stream.readAllBytes(); + return new String(allBytes, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RemoteException("Failed to convert stream to String", e); + } + } + + @Override + public void setHtmlContents(String text) throws RemoteException { + invokeAndWait(() -> { + String display = text == null ? "null" : ("\"" + text + "\""); + clipboardTest.log("setHtmlContents(" + display + ")"); + getClipboard(CLIPBOARD).setContents(new HtmlSelection(text.getBytes(StandardCharsets.UTF_8)), null); + }); + } + + @Override + public String getHtmlContents() throws RemoteException { + Object contents = getContents(CLIPBOARD, "getHtmlContents", HtmlSelection.flavor); + if (!(contents instanceof InputStream stream)) { + return null; + } + try (stream) { + byte[] allBytes = stream.readAllBytes(); + return new String(allBytes, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RemoteException("Failed to convert stream to String", e); + } + } + + @Override + public void setUrlContents(byte[] bytes) throws RemoteException { + invokeAndWait(() -> { + String display = bytes == null ? "null" : ("bytes"); + clipboardTest.log("setUrlContents(" + display + ")"); + getClipboard(CLIPBOARD).setContents(new UrlSelection(bytes), null); + }); + } + + @Override + public byte[] getUrlContents() throws RemoteException { + Object contents = getContents(CLIPBOARD, "getUrlContents", UrlSelection.flavor); + if (!(contents instanceof InputStream stream)) { + return null; + } + try (stream) { + return stream.readAllBytes(); + } catch (IOException e) { + throw new RemoteException("Failed to convert stream to byte[]", e); + } + } + @Override + public void setMyTypeContents(byte[] bytes) throws RemoteException { + invokeAndWait(() -> { + String display = bytes == null ? "null" : ("bytes"); + clipboardTest.log("setMyTypeContents(" + display + ")"); + getClipboard(CLIPBOARD).setContents(new MyTypeSelection(bytes), null); }); } + @Override + public byte[] getMyTypeContents() throws RemoteException { + Object contents = getContents(CLIPBOARD, "getMyTypeContents", MyTypeSelection.flavor); + if (!(contents instanceof InputStream stream)) { + return null; + } + try (stream) { + return stream.readAllBytes(); + } catch (IOException e) { + throw new RemoteException("Failed to convert stream to byte[]", e); + } + } + + @Override + public void setImageContents(byte[] imageContents) throws RemoteException { + invokeAndWait(() -> { + BufferedImage img; + try { + img = ImageIO.read(new ByteArrayInputStream(imageContents)); + if (img == null) { + throw new IOException("ImageIO.read unable to convert bytes to image file"); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + getClipboard(CLIPBOARD).setContents(new ImageSelection(img), null); + }); + } + + @Override + public byte[] getImageContents() throws RemoteException { + Object contents = getContents(CLIPBOARD, "getImageContents", DataFlavor.imageFlavor); + if (!(contents instanceof Image image)) { + return null; + } + if (contents instanceof MultiResolutionImage mri) { + image = mri.getResolutionVariant(image.getWidth(null), image.getHeight(null)); + } + if (!(image instanceof BufferedImage bufferedImage)) { + return null; + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(bufferedImage, "png", baos); + } catch (IOException e) { + throw new RemoteException("Failed to convert image to png byte array", e); + } + return baos.toByteArray(); + } + + @Override + public void setFileListContents(String[] fileList) throws RemoteException { + invokeAndWait(() -> { + getClipboard(CLIPBOARD).setContents(new FileListSelection(Arrays.asList(fileList)), null); + }); + } + + @Override + public String[] getFileListContents() throws RemoteException { + Object contents = getContents(CLIPBOARD, "getFileListContents", DataFlavor.javaFileListFlavor); + if (!(contents instanceof List fileList)) { + return null; + } + + return fileList.stream().map(f -> (File) f).map(File::toString).toArray(String[]::new); + } + private Clipboard getClipboard(int clipboardId) { Clipboard clipboard; if (clipboardId == SELECTION_CLIPBOARD) { @@ -83,18 +242,27 @@ private Clipboard getClipboard(int clipboardId) { return clipboard; } + @Override + public String getStringContents() throws RemoteException { + return getStringContents(CLIPBOARD); + } + @Override public String getStringContents(int clipboardId) throws RemoteException { - String[] data = new String[] { null }; + return (String) getContents(clipboardId, "getStringContents", DataFlavor.stringFlavor); + } + + private Object getContents(int clipboardId, String methodName, DataFlavor flavor) throws RemoteException { + Object[] data = new Object[] { null }; invokeAndWait(() -> { Clipboard clipboard = getClipboard(clipboardId); try { - data[0] = (String) clipboard.getData(DataFlavor.stringFlavor); - clipboardTest.log("getStringContents(" + clipboardId + ") returned " + data[0]); + data[0] = clipboard.getData(flavor); + clipboardTest.log(methodName + "(" + clipboardId + ") returned " + data[0]); } catch (Exception e) { data[0] = null; DataFlavor[] availableDataFlavors = clipboard.getAvailableDataFlavors(); - clipboardTest.log("getStringContents(" + clipboardId + ") threw " + e.toString() + clipboardTest.log(methodName + "(" + clipboardId + ") threw " + e.toString() + " and returned null. The clipboard had availableDataFlavors = " + Arrays.asList(availableDataFlavors)); } diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java index 4be556e92ee..a15cf655b90 100644 --- a/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java +++ b/tests/org.eclipse.swt.tests/data/clipboard/ClipboardTest.java @@ -19,7 +19,6 @@ import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; -import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.RMISocketFactory; @@ -60,8 +59,9 @@ public Socket createSocket(String host, int port) throws IOException { private JTextArea textArea; private ClipboardCommandsImpl commands; - public ClipboardTest() throws RemoteException { + public ClipboardTest() throws Exception { super("ClipboardTest"); + registerSelectionTypes(); commands = new ClipboardCommandsImpl(this); rmiRegistry.rebind(ClipboardCommands.ID, commands); @@ -79,7 +79,6 @@ clipboard JUnit tests. Do not close this window (unless the JUnit
JButton copyButton = new JButton("Copy"); JButton pasteButton = new JButton("Paste"); JButton pressToContinue = new JButton("Press To Continue Test"); - copyButton.addActionListener(e -> { String text = textArea.getSelectedText(); if (text != null) { @@ -140,7 +139,7 @@ public static void main(String[] args) throws IOException { SwingUtilities.invokeLater(() -> { try { new ClipboardTest(); - } catch (RemoteException e) { + } catch (Exception e) { System.err.println("Failed to start ClipboardTest"); e.printStackTrace(); System.exit(1); @@ -157,4 +156,11 @@ private static int getAvailablePort() throws IOException { return ss.getLocalPort(); } } + + static void registerSelectionTypes() throws Exception { + RtfSelection.register(); + HtmlSelection.register(); + UrlSelection.register(); + MyTypeSelection.register(); + } } diff --git a/tests/org.eclipse.swt.tests/data/clipboard/FileListSelection.java b/tests/org.eclipse.swt.tests/data/clipboard/FileListSelection.java new file mode 100644 index 00000000000..b602f9d60ca --- /dev/null +++ b/tests/org.eclipse.swt.tests/data/clipboard/FileListSelection.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package clipboard; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.util.List; + +final class FileListSelection implements Transferable { + private final List files; + + FileListSelection(List files) { + this.files = files; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { DataFlavor.javaFileListFlavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return DataFlavor.javaFileListFlavor.equals(flavor); + } + + @Override + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { + if (!isDataFlavorSupported(flavor)) + throw new UnsupportedFlavorException(flavor); + return files; + } +} \ No newline at end of file diff --git a/tests/org.eclipse.swt.tests/data/clipboard/HtmlSelection.java b/tests/org.eclipse.swt.tests/data/clipboard/HtmlSelection.java new file mode 100644 index 00000000000..0be9b955050 --- /dev/null +++ b/tests/org.eclipse.swt.tests/data/clipboard/HtmlSelection.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package clipboard; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.SystemFlavorMap; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.ByteArrayInputStream; +import java.util.List; + +class HtmlSelection implements Transferable { + + static DataFlavor flavor; + + static void register() throws ClassNotFoundException { + flavor = new DataFlavor("text/html;class=java.io.InputStream"); + SystemFlavorMap map = (SystemFlavorMap) SystemFlavorMap.getDefaultFlavorMap(); + + String os = System.getProperty("os.name", "").toLowerCase(); + if (os.contains("win")) { + String nativeFmt = "HTML Format"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } else if (os.contains("mac")) { + String nativeFmt = "public.html"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } else { + // X11/Wayland + for (String nativeFmt : List.of("text/html")) { + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } + } + } + + private final byte[] html; + + HtmlSelection(byte[] html) { + this.html = html; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { flavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor f) { + return f.equals(flavor); + } + + @Override + public Object getTransferData(DataFlavor f) throws UnsupportedFlavorException { + if (f.equals(flavor)) + return new ByteArrayInputStream(html); + throw new UnsupportedFlavorException(f); + } +} \ No newline at end of file diff --git a/tests/org.eclipse.swt.tests/data/clipboard/ImageSelection.java b/tests/org.eclipse.swt.tests/data/clipboard/ImageSelection.java new file mode 100644 index 00000000000..ba7610f1717 --- /dev/null +++ b/tests/org.eclipse.swt.tests/data/clipboard/ImageSelection.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package clipboard; + +import java.awt.Image; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; + +final class ImageSelection implements Transferable { + private final Image image; + + ImageSelection(Image image) { + this.image = image; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { DataFlavor.imageFlavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return DataFlavor.imageFlavor.equals(flavor); + } + + @Override + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { + if (!isDataFlavorSupported(flavor)) + throw new UnsupportedFlavorException(flavor); + return image; + } +} \ No newline at end of file diff --git a/tests/org.eclipse.swt.tests/data/clipboard/MyTypeSelection.java b/tests/org.eclipse.swt.tests/data/clipboard/MyTypeSelection.java new file mode 100644 index 00000000000..69c8ecc0b2e --- /dev/null +++ b/tests/org.eclipse.swt.tests/data/clipboard/MyTypeSelection.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package clipboard; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.SystemFlavorMap; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.ByteArrayInputStream; + +public final class MyTypeSelection implements Transferable { + + static DataFlavor flavor; + + static void register() throws ClassNotFoundException { + flavor = new DataFlavor("application/x-my_type_name;class=java.io.InputStream", "my_type_name"); + SystemFlavorMap map = (SystemFlavorMap) SystemFlavorMap.getDefaultFlavorMap(); + map.addUnencodedNativeForFlavor(flavor, "my_type_name"); + map.addFlavorForUnencodedNative("my_type_name", flavor); + } + + private final byte[] bytes; + + MyTypeSelection(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { flavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return flavor.equals(flavor); + } + + @Override + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { + if (!isDataFlavorSupported(flavor)) + throw new UnsupportedFlavorException(flavor); + return new ByteArrayInputStream(bytes); + } +} \ No newline at end of file diff --git a/tests/org.eclipse.swt.tests/data/clipboard/RtfSelection.java b/tests/org.eclipse.swt.tests/data/clipboard/RtfSelection.java new file mode 100644 index 00000000000..7272b226602 --- /dev/null +++ b/tests/org.eclipse.swt.tests/data/clipboard/RtfSelection.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package clipboard; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.SystemFlavorMap; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.ByteArrayInputStream; +import java.util.List; + +class RtfSelection implements Transferable { + static DataFlavor flavor; + + static void register() throws Exception { + flavor = new DataFlavor("text/rtf;class=java.io.InputStream"); + SystemFlavorMap map = (SystemFlavorMap) SystemFlavorMap.getDefaultFlavorMap(); + + String os = System.getProperty("os.name", "").toLowerCase(); + if (os.contains("win")) { + String nativeFmt = "Rich Text Format"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } else if (os.contains("mac")) { + String nativeFmt = "public.rtf"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } else { + // X11/Wayland + for (String nativeFmt : List.of("text/rtf", "application/rtf")) { + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } + } + } + + private final byte[] rtf; + + RtfSelection(byte[] rtf) { + this.rtf = rtf; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { flavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor f) { + return f.equals(flavor); + } + + @Override + public Object getTransferData(DataFlavor f) throws UnsupportedFlavorException { + if (f.equals(flavor)) + return new ByteArrayInputStream(rtf); + throw new UnsupportedFlavorException(f); + } + +} \ No newline at end of file diff --git a/tests/org.eclipse.swt.tests/data/clipboard/UrlSelection.java b/tests/org.eclipse.swt.tests/data/clipboard/UrlSelection.java new file mode 100644 index 00000000000..ccb170cda96 --- /dev/null +++ b/tests/org.eclipse.swt.tests/data/clipboard/UrlSelection.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2025 Kichwa Coders Canada, Inc. + * + * 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 + *******************************************************************************/ +package clipboard; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.SystemFlavorMap; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.ByteArrayInputStream; + +class UrlSelection implements Transferable { + + static DataFlavor flavor; + + static void register() throws ClassNotFoundException { + flavor = new DataFlavor("application/x-uniform-resourcelocatorw;class=java.io.InputStream"); + SystemFlavorMap map = (SystemFlavorMap) SystemFlavorMap.getDefaultFlavorMap(); + + String os = System.getProperty("os.name", "").toLowerCase(); + if (os.contains("win")) { + String nativeFmt = "UniformResourceLocatorW"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } else if (os.contains("mac")) { + String nativeFmt = "public.url"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + } else { + // X11/Wayland + String nativeFmt = "text/x-moz-url"; + map.addUnencodedNativeForFlavor(flavor, nativeFmt); + map.addFlavorForUnencodedNative(nativeFmt, flavor); + + } + } + + private final byte[] url; + + UrlSelection(byte[] url) { + this.url = url; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { flavor }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor f) { + return f.equals(flavor); + } + + @Override + public Object getTransferData(DataFlavor f) throws UnsupportedFlavorException { + if (f.equals(flavor)) + return new ByteArrayInputStream(url); + throw new UnsupportedFlavorException(f); + } + +} \ No newline at end of file