1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @summary Testing external editor. 27 * @bug 8143955 8080843 28 * @modules jdk.jshell/jdk.internal.jshell.tool 29 * @build ReplToolTesting CustomEditor EditorTestBase 30 * @run testng ExternalEditorTest 31 */ 32 33 import java.io.BufferedWriter; 34 import java.io.DataInputStream; 35 import java.io.DataOutputStream; 36 import java.io.IOException; 37 import java.io.UncheckedIOException; 38 import java.net.ServerSocket; 39 import java.net.Socket; 40 import java.net.SocketTimeoutException; 41 import java.nio.charset.StandardCharsets; 42 import java.nio.file.Files; 43 import java.nio.file.Path; 44 import java.nio.file.Paths; 45 import java.util.concurrent.ExecutionException; 46 import java.util.concurrent.Future; 47 import java.util.function.Consumer; 48 49 import org.testng.annotations.AfterClass; 50 import org.testng.annotations.BeforeClass; 51 import org.testng.annotations.Test; 52 53 import static org.testng.Assert.fail; 54 55 public class ExternalEditorTest extends EditorTestBase { 56 57 private static Path executionScript; 58 private static ServerSocket listener; 59 60 private DataInputStream inputStream; 61 private DataOutputStream outputStream; 62 63 @Override 64 public void writeSource(String s) { 65 try { 66 outputStream.writeInt(CustomEditor.SOURCE_CODE); 67 byte[] bytes = s.getBytes(StandardCharsets.UTF_8); 68 outputStream.writeInt(bytes.length); 69 outputStream.write(bytes); 70 } catch (IOException e) { 71 throw new UncheckedIOException(e); 72 } 73 } 74 75 @Override 76 public String getSource() { 77 try { 78 outputStream.writeInt(CustomEditor.GET_SOURCE_CODE); 79 int length = inputStream.readInt(); 80 byte[] bytes = new byte[length]; 81 inputStream.readFully(bytes); 82 return new String(bytes, StandardCharsets.UTF_8); 83 } catch (IOException e) { 84 throw new UncheckedIOException(e); 85 } 86 } 87 88 private void sendCode(int code) { 89 try { 90 outputStream.writeInt(code); 91 } catch (IOException e) { 92 throw new UncheckedIOException(e); 93 } 94 } 95 96 @Override 97 public void accept() { 98 sendCode(CustomEditor.ACCEPT_CODE); 99 } 100 101 @Override 102 public void exit() { 103 sendCode(CustomEditor.EXIT_CODE); 104 inputStream = null; 105 outputStream = null; 106 } 107 108 @Override 109 public void cancel() { 110 sendCode(CustomEditor.CANCEL_CODE); 111 } 112 113 @Override 114 public void testEditor(boolean defaultStartup, String[] args, ReplTest... tests) { 115 ReplTest[] t = new ReplTest[tests.length + 1]; 116 t[0] = a -> assertCommandCheckOutput(a, "/set editor " + executionScript, 117 assertStartsWith("| Editor set to: " + executionScript)); 118 System.arraycopy(tests, 0, t, 1, tests.length); 119 super.testEditor(defaultStartup, args, t); 120 } 121 122 private static boolean isWindows() { 123 return System.getProperty("os.name").startsWith("Windows"); 124 } 125 126 @BeforeClass 127 public static void setUpExternalEditorTest() throws IOException { 128 listener = new ServerSocket(0); 129 listener.setSoTimeout(30000); 130 int localPort = listener.getLocalPort(); 131 132 executionScript = Paths.get(isWindows() ? "editor.bat" : "editor.sh").toAbsolutePath(); 133 Path java = Paths.get(System.getProperty("java.home")).resolve("bin").resolve("java"); 134 try (BufferedWriter writer = Files.newBufferedWriter(executionScript)) { 135 if(!isWindows()) { 136 writer.append(java.toString()).append(" ") 137 .append(" -cp ").append(System.getProperty("java.class.path")) 138 .append(" CustomEditor ").append(Integer.toString(localPort)).append(" $@"); 139 executionScript.toFile().setExecutable(true); 140 } else { 141 writer.append(java.toString()).append(" ") 142 .append(" -cp ").append(System.getProperty("java.class.path")) 143 .append(" CustomEditor ").append(Integer.toString(localPort)).append(" %*"); 144 } 145 } 146 } 147 148 private Future<?> task; 149 @Override 150 public void assertEdit(boolean after, String cmd, 151 Consumer<String> checkInput, Consumer<String> checkOutput, Action action) { 152 if (!after) { 153 setCommandInput(cmd + "\n"); 154 task = getExecutor().submit(() -> { 155 try (Socket socket = listener.accept()) { 156 inputStream = new DataInputStream(socket.getInputStream()); 157 outputStream = new DataOutputStream(socket.getOutputStream()); 158 checkInput.accept(getSource()); 159 action.accept(); 160 } catch (SocketTimeoutException e) { 161 fail("Socket timeout exception.\n Output: " + getCommandOutput() + 162 "\n, error: " + getCommandErrorOutput()); 163 } catch (Throwable e) { 164 shutdownEditor(); 165 if (e instanceof AssertionError) { 166 throw (AssertionError) e; 167 } 168 throw new RuntimeException(e); 169 } 170 }); 171 } else { 172 try { 173 task.get(); 174 checkOutput.accept(getCommandOutput()); 175 } catch (ExecutionException e) { 176 if (e.getCause() instanceof AssertionError) { 177 throw (AssertionError) e.getCause(); 178 } 179 throw new RuntimeException(e); 180 } catch (Exception e) { 181 throw new RuntimeException(e); 182 } 183 } 184 } 185 186 @Override 187 public void shutdownEditor() { 188 if (outputStream != null) { 189 exit(); 190 } 191 } 192 193 @Test 194 public void setUnknownEditor() { 195 test( 196 a -> assertCommand(a, "/set editor", "| The '/set editor' command requires a path argument"), 197 a -> assertCommand(a, "/set editor UNKNOWN", "| Editor set to: UNKNOWN"), 198 a -> assertCommand(a, "int a;", null), 199 a -> assertCommandOutputStartsWith(a, "/ed 1", 200 "| Edit Error:") 201 ); 202 } 203 204 @Test(enabled = false) // TODO 8159229 205 public void testRemoveTempFile() { 206 test(new String[]{"-nostartup"}, 207 a -> assertCommandCheckOutput(a, "/set editor " + executionScript, 208 assertStartsWith("| Editor set to: " + executionScript)), 209 a -> assertVariable(a, "int", "a", "0", "0"), 210 a -> assertEditOutput(a, "/ed 1", assertStartsWith("| Edit Error: Failure in read edit file:"), () -> { 211 sendCode(CustomEditor.REMOVE_CODE); 212 exit(); 213 }), 214 a -> assertCommandCheckOutput(a, "/vars", assertVariables()) 215 ); 216 } 217 218 @AfterClass 219 public static void shutdown() throws IOException { 220 executorShutdown(); 221 if (listener != null) { 222 listener.close(); 223 } 224 } 225 }