1+ /*******************************************************************************
2+ * Copyright (c) 2025 Kichwa Coders Canada, Inc.
3+ *
4+ * This program and the accompanying materials
5+ * are made available under the terms of the Eclipse Public License 2.0
6+ * which accompanies this distribution, and is available at
7+ * https://www.eclipse.org/legal/epl-2.0/
8+ *
9+ * SPDX-License-Identifier: EPL-2.0
10+ *******************************************************************************/
11+ package org .eclipse .swt .tests .junit ;
12+
13+ import static org .junit .jupiter .api .Assertions .assertNotEquals ;
14+ import static org .junit .jupiter .api .Assertions .assertNotNull ;
15+ import static org .junit .jupiter .api .Assertions .assertNull ;
16+ import static org .junit .jupiter .api .Assertions .assertTrue ;
17+ import static org .junit .jupiter .api .Assumptions .assumeTrue ;
18+
19+ import java .io .BufferedReader ;
20+ import java .io .IOException ;
21+ import java .io .InputStreamReader ;
22+ import java .lang .ProcessBuilder .Redirect ;
23+ import java .nio .file .FileVisitResult ;
24+ import java .nio .file .Files ;
25+ import java .nio .file .Path ;
26+ import java .nio .file .SimpleFileVisitor ;
27+ import java .nio .file .attribute .BasicFileAttributes ;
28+ import java .rmi .NotBoundException ;
29+ import java .rmi .RemoteException ;
30+ import java .rmi .registry .LocateRegistry ;
31+ import java .rmi .registry .Registry ;
32+ import java .util .List ;
33+ import java .util .concurrent .TimeUnit ;
34+
35+ import clipboard .ClipboardCommands ;
36+
37+ public class RemoteClipboard implements ClipboardCommands {
38+ private ClipboardCommands remote ;
39+ private Process remoteClipboardProcess ;
40+ private Path remoteClipboardTempDir ;
41+
42+ public void start () throws Exception {
43+ assertNull (remote , "Create a new instance to restart" );
44+ /*
45+ * The below copy using getPath may be redundant (i.e. it may be possible to run
46+ * the class files from where they currently reside in the bin folder or the
47+ * jar), but this method of setting up the class files is very simple and is
48+ * done the same way that other files are extracted for tests.
49+ *
50+ * If the ClipboardTest starts to get more complicated, or other tests want to
51+ * replicate this design element, then refactoring this is an option.
52+ */
53+ remoteClipboardTempDir = Files .createTempDirectory ("swt-test-Clipboard" );
54+ List .of ( //
55+ "ClipboardTest" , //
56+ "ClipboardCommands" , //
57+ "ClipboardCommandsImpl" , //
58+ "ClipboardTest$LocalHostOnlySocketFactory" //
59+ ).forEach ((f ) -> {
60+ // extract the files and put them in the temp directory
61+ SwtTestUtil .copyFile ("/clipboard/" + f + ".class" ,
62+ remoteClipboardTempDir .resolve ("clipboard/" + f + ".class" ));
63+ });
64+
65+ String javaHome = System .getProperty ("java.home" );
66+ String javaExe = javaHome + "/bin/java" + (SwtTestUtil .isWindowsOS ? ".exe" : "" );
67+ assertTrue (Files .exists (Path .of (javaExe )));
68+
69+ ProcessBuilder pb = new ProcessBuilder (javaExe , "clipboard.ClipboardTest" )
70+ .directory (remoteClipboardTempDir .toFile ());
71+ pb .inheritIO ();
72+ pb .redirectOutput (Redirect .PIPE );
73+ remoteClipboardProcess = pb .start ();
74+
75+ // Read server output to find the port
76+ int port = SwtTestUtil .runOperationInThread (() -> {
77+ BufferedReader reader = new BufferedReader (new InputStreamReader (remoteClipboardProcess .getInputStream ()));
78+ String line ;
79+ while ((line = reader .readLine ()) != null ) {
80+ if (line .startsWith (ClipboardCommands .PORT_MESSAGE )) {
81+ String [] parts = line .split (":" );
82+ return Integer .parseInt (parts [1 ].trim ());
83+ }
84+ }
85+ throw new RuntimeException ("Failed to get port" );
86+ });
87+ assertNotEquals (0 , port );
88+ try {
89+ Registry reg = LocateRegistry .getRegistry ("127.0.0.1" , port );
90+ long stopTime = System .currentTimeMillis () + 10000 ;
91+ do {
92+ try {
93+ remote = (ClipboardCommands ) reg .lookup (ClipboardCommands .ID );
94+ break ;
95+ } catch (NotBoundException e ) {
96+ // try again because the remote app probably hasn't bound yet
97+ }
98+ } while (System .currentTimeMillis () < stopTime );
99+ } catch (RemoteException e ) {
100+
101+ Integer exitValue = null ;
102+ boolean waitFor = false ;
103+ try {
104+ waitFor = remoteClipboardProcess .waitFor (5 , TimeUnit .SECONDS );
105+ if (waitFor ) {
106+ exitValue = remoteClipboardProcess .exitValue ();
107+ }
108+ } catch (InterruptedException e1 ) {
109+ Thread .interrupted ();
110+ }
111+
112+ String message = "Failed to get remote clipboards command, this seems to happen on macOS on I-build tests. Exception: "
113+ + e .toString () + " waitFor: " + waitFor + " exitValue: " + exitValue ;
114+
115+ // Give some diagnostic information to help track down why this fails on build
116+ // machine. We only hard error on Linux, for other platforms we allow test to
117+ // just be skipped until we track down what is causing
118+ // https://github.com/eclipse-platform/eclipse.platform.swt/issues/2553
119+ assumeTrue (SwtTestUtil .isGTK , message );
120+ throw new RuntimeException (message , e );
121+ }
122+ assertNotNull (remote );
123+
124+ // Run a no-op on the Swing event loop so that we know it is idle
125+ // and we can continue startup
126+ remote .waitUntilReady ();
127+ remote .setFocus ();
128+ remote .waitUntilReady ();
129+ }
130+
131+ @ Override
132+ public void stop () throws RemoteException {
133+ try {
134+ stopProcess ();
135+ } catch (InterruptedException e ) {
136+ Thread .interrupted ();
137+ } finally {
138+ deleteRemoteTempDir ();
139+ }
140+ }
141+
142+ private void stopProcess () throws RemoteException , InterruptedException {
143+ try {
144+ if (remote != null ) {
145+ remote .stop ();
146+ remote = null ;
147+ }
148+ } finally {
149+ if (remoteClipboardProcess != null ) {
150+ try {
151+ remoteClipboardProcess .destroy ();
152+ assertTrue (remoteClipboardProcess .waitFor (10 , TimeUnit .SECONDS ));
153+ } finally {
154+ remoteClipboardProcess .destroyForcibly ();
155+ assertTrue (remoteClipboardProcess .waitFor (10 , TimeUnit .SECONDS ));
156+ remoteClipboardProcess = null ;
157+ }
158+ }
159+ }
160+ }
161+
162+ private void deleteRemoteTempDir () {
163+ if (remoteClipboardTempDir != null ) {
164+ // At this point the process is ideally destroyed - or at least the test will
165+ // report a failure if it isn't. Clean up the extracted files, but don't
166+ // fail test if we fail to delete
167+ try {
168+ Files .walkFileTree (remoteClipboardTempDir , new SimpleFileVisitor <Path >() {
169+ @ Override
170+ public FileVisitResult visitFile (Path file , BasicFileAttributes attrs ) throws IOException {
171+ Files .delete (file );
172+ return FileVisitResult .CONTINUE ;
173+ }
174+
175+ @ Override
176+ public FileVisitResult postVisitDirectory (Path dir , IOException exc ) throws IOException {
177+ Files .delete (dir );
178+ return FileVisitResult .CONTINUE ;
179+ }
180+ });
181+ } catch (IOException e ) {
182+ System .err .println ("SWT Warning: Failed to clean up temp directory " + remoteClipboardTempDir
183+ + " Error:" + e .toString ());
184+ e .printStackTrace ();
185+ }
186+ }
187+ }
188+
189+ @ Override
190+ public void setContents (String string , int clipboardId ) throws RemoteException {
191+ remote .setContents (string , clipboardId );
192+ }
193+
194+ @ Override
195+ public void setFocus () throws RemoteException {
196+ remote .setFocus ();
197+ }
198+
199+ @ Override
200+ public String getStringContents (int clipboardId ) throws RemoteException {
201+ return remote .getStringContents (clipboardId );
202+ }
203+
204+ @ Override
205+ public void waitUntilReady () throws RemoteException {
206+ remote .waitUntilReady ();
207+ }
208+ }
0 commit comments