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 8163816 8143006 8169828 8171130 28 * @modules jdk.jshell/jdk.internal.jshell.tool 29 * @build ReplToolTesting CustomEditor EditorTestBase 30 * @run testng ExternalEditorTest 31 */ 32 33import java.io.BufferedWriter; 34import java.io.DataInputStream; 35import java.io.DataOutputStream; 36import java.io.IOException; 37import java.io.UncheckedIOException; 38import java.net.ServerSocket; 39import java.net.Socket; 40import java.net.SocketTimeoutException; 41import java.nio.charset.StandardCharsets; 42import java.nio.file.Files; 43import java.nio.file.Path; 44import java.nio.file.Paths; 45import java.util.concurrent.ExecutionException; 46import java.util.concurrent.Future; 47import java.util.function.Consumer; 48 49import org.testng.annotations.AfterClass; 50import org.testng.annotations.BeforeClass; 51import org.testng.annotations.Test; 52 53import static org.testng.Assert.assertEquals; 54import static org.testng.Assert.fail; 55 56public class ExternalEditorTest extends EditorTestBase { 57 58 private static Path executionScript; 59 private static ServerSocket listener; 60 61 private DataInputStream inputStream; 62 private DataOutputStream outputStream; 63 64 @Override 65 public void writeSource(String s) { 66 try { 67 outputStream.writeInt(CustomEditor.SOURCE_CODE); 68 byte[] bytes = s.getBytes(StandardCharsets.UTF_8); 69 outputStream.writeInt(bytes.length); 70 outputStream.write(bytes); 71 } catch (IOException e) { 72 throw new UncheckedIOException(e); 73 } 74 } 75 76 @Override 77 public String getSource() { 78 try { 79 outputStream.writeInt(CustomEditor.GET_SOURCE_CODE); 80 int length = inputStream.readInt(); 81 byte[] bytes = new byte[length]; 82 inputStream.readFully(bytes); 83 return new String(bytes, StandardCharsets.UTF_8); 84 } catch (IOException e) { 85 throw new UncheckedIOException(e); 86 } 87 } 88 89 private void sendCode(int code) { 90 try { 91 outputStream.writeInt(code); 92 } catch (IOException e) { 93 throw new UncheckedIOException(e); 94 } 95 } 96 97 @Override 98 public void accept() { 99 sendCode(CustomEditor.ACCEPT_CODE); 100 } 101 102 @Override 103 public void exit() { 104 sendCode(CustomEditor.EXIT_CODE); 105 inputStream = null; 106 outputStream = null; 107 } 108 109 @Override 110 public void cancel() { 111 sendCode(CustomEditor.CANCEL_CODE); 112 } 113 114 @Override 115 public void testEditor(boolean defaultStartup, String[] args, ReplTest... tests) { 116 ReplTest[] t = new ReplTest[tests.length + 1]; 117 t[0] = a -> assertCommandCheckOutput(a, "/set editor " + executionScript, 118 assertStartsWith("| Editor set to: " + executionScript)); 119 System.arraycopy(tests, 0, t, 1, tests.length); 120 super.testEditor(defaultStartup, args, t); 121 } 122 123 @Test 124 public void testStatementSemicolonAddition() { 125 testEditor( 126 a -> assertCommand(a, "if (true) {}", ""), 127 a -> assertCommand(a, "if (true) {} else {}", ""), 128 a -> assertCommand(a, "Object o", "o ==> null"), 129 a -> assertCommand(a, "if (true) o = new Object() { int x; }", ""), 130 a -> assertCommand(a, "if (true) o = new Object() { int y; }", ""), 131 a -> assertCommand(a, "System.err.flush()", ""), // test still ; for expression statement 132 a -> assertEditOutput(a, "/ed", "", () -> { 133 assertEquals(getSource(), 134 "if (true) {}\n" + 135 "if (true) {} else {}\n" + 136 "Object o;\n" + 137 "if (true) o = new Object() { int x; };\n" + 138 "if (true) o = new Object() { int y; };\n" + 139 "System.err.flush();\n"); 140 exit(); 141 }) 142 ); 143 } 144 145 private static boolean isWindows() { 146 return System.getProperty("os.name").startsWith("Windows"); 147 } 148 149 @BeforeClass 150 public static void setUpExternalEditorTest() throws IOException { 151 listener = new ServerSocket(0); 152 listener.setSoTimeout(30000); 153 int localPort = listener.getLocalPort(); 154 155 executionScript = Paths.get(isWindows() ? "editor.bat" : "editor.sh").toAbsolutePath(); 156 Path java = Paths.get(System.getProperty("java.home")).resolve("bin").resolve("java"); 157 try (BufferedWriter writer = Files.newBufferedWriter(executionScript)) { 158 if(!isWindows()) { 159 writer.append(java.toString()).append(" ") 160 .append(" -cp ").append(System.getProperty("java.class.path")) 161 .append(" CustomEditor ").append(Integer.toString(localPort)).append(" $@"); 162 executionScript.toFile().setExecutable(true); 163 } else { 164 writer.append(java.toString()).append(" ") 165 .append(" -cp ").append(System.getProperty("java.class.path")) 166 .append(" CustomEditor ").append(Integer.toString(localPort)).append(" %*"); 167 } 168 } 169 } 170 171 private Future<?> task; 172 @Override 173 public void assertEdit(boolean after, String cmd, 174 Consumer<String> checkInput, Consumer<String> checkOutput, Action action) { 175 if (!after) { 176 setCommandInput(cmd + "\n"); 177 task = getExecutor().submit(() -> { 178 try (Socket socket = listener.accept()) { 179 inputStream = new DataInputStream(socket.getInputStream()); 180 outputStream = new DataOutputStream(socket.getOutputStream()); 181 checkInput.accept(getSource()); 182 action.accept(); 183 } catch (SocketTimeoutException e) { 184 fail("Socket timeout exception.\n Output: " + getCommandOutput() + 185 "\n, error: " + getCommandErrorOutput()); 186 } catch (Throwable e) { 187 shutdownEditor(); 188 if (e instanceof AssertionError) { 189 throw (AssertionError) e; 190 } 191 throw new RuntimeException(e); 192 } 193 }); 194 } else { 195 try { 196 task.get(); 197 checkOutput.accept(getCommandOutput()); 198 } catch (ExecutionException e) { 199 if (e.getCause() instanceof AssertionError) { 200 throw (AssertionError) e.getCause(); 201 } 202 throw new RuntimeException(e); 203 } catch (Exception e) { 204 throw new RuntimeException(e); 205 } 206 } 207 } 208 209 @Override 210 public void shutdownEditor() { 211 if (outputStream != null) { 212 exit(); 213 } 214 } 215 216 @Test 217 public void setUnknownEditor() { 218 test( 219 a -> assertCommand(a, "/set editor UNKNOWN", "| Editor set to: UNKNOWN"), 220 a -> assertCommand(a, "int a;", null), 221 a -> assertCommandOutputStartsWith(a, "/ed 1", 222 "| Edit Error:") 223 ); 224 } 225 226 @Test(enabled = false) // TODO 8159229 227 public void testRemoveTempFile() { 228 test(new String[]{"--no-startup"}, 229 a -> assertCommandCheckOutput(a, "/set editor " + executionScript, 230 assertStartsWith("| Editor set to: " + executionScript)), 231 a -> assertVariable(a, "int", "a", "0", "0"), 232 a -> assertEditOutput(a, "/ed 1", assertStartsWith("| Edit Error: Failure in read edit file:"), () -> { 233 sendCode(CustomEditor.REMOVE_CODE); 234 exit(); 235 }), 236 a -> assertCommandCheckOutput(a, "/vars", assertVariables()) 237 ); 238 } 239 240 @AfterClass 241 public static void shutdown() throws IOException { 242 executorShutdown(); 243 if (listener != null) { 244 listener.close(); 245 } 246 } 247} 248