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