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  * @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886 8148316 8148317 8143955 8157953 8080347 8154714 8166649 8167643 8170162 8172102 8165405 8174796 8174797 8175304 8167554 8180508 8166232 8196133 8199912
  27  * @summary Tests for Basic tests for REPL tool
  28  * @modules jdk.compiler/com.sun.tools.javac.api
  29  *          jdk.compiler/com.sun.tools.javac.main
  30  *          jdk.jdeps/com.sun.tools.javap
  31  *          jdk.jshell/jdk.internal.jshell.tool
  32  * @library /tools/lib
  33  * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
  34  * @build KullaTesting TestingInputStream Compiler
  35  * @run testng/timeout=600 ToolBasicTest
  36  */
  37 
  38 import java.io.File;
  39 import java.io.IOException;
  40 import java.io.PrintWriter;
  41 import java.io.StringWriter;
  42 import java.net.InetAddress;
  43 import java.net.InetSocketAddress;
  44 import java.nio.file.Files;
  45 import java.nio.file.Path;
  46 import java.nio.file.Paths;
  47 import java.util.ArrayList;
  48 import java.util.Arrays;
  49 import java.util.List;
  50 import java.util.Scanner;
  51 import java.util.function.BiFunction;
  52 import java.util.function.Consumer;
  53 import java.util.function.Function;
  54 import java.util.stream.Collectors;
  55 import java.util.stream.Stream;
  56 
  57 import com.sun.net.httpserver.HttpServer;
  58 import org.testng.annotations.Test;
  59 
  60 import static org.testng.Assert.assertEquals;
  61 import static org.testng.Assert.assertTrue;
  62 import static org.testng.Assert.fail;
  63 
  64 @Test
  65 public class ToolBasicTest extends ReplToolTesting {
  66 
  67     public void elideStartUpFromList() {
  68         test(
  69                 (a) -> assertCommandOutputContains(a, "123", "==> 123"),
  70                 (a) -> assertCommandCheckOutput(a, "/list", (s) -> {
  71                     int cnt;
  72                     try (Scanner scanner = new Scanner(s)) {
  73                         cnt = 0;
  74                         while (scanner.hasNextLine()) {
  75                             String line = scanner.nextLine();
  76                             if (!line.trim().isEmpty()) {
  77                                 ++cnt;
  78                             }
  79                         }
  80                     }
  81                     assertEquals(cnt, 1, "Expected only one listed line");
  82                 })
  83         );
  84     }
  85 
  86     public void elideStartUpFromSave() throws IOException {
  87         Compiler compiler = new Compiler();
  88         Path path = compiler.getPath("myfile");
  89         test(
  90                 (a) -> assertCommandOutputContains(a, "123", "==> 123"),
  91                 (a) -> assertCommand(a, "/save " + path.toString(), "")
  92         );
  93         try (Stream<String> lines = Files.lines(path)) {
  94             assertEquals(lines.count(), 1, "Expected only one saved line");
  95         }
  96     }
  97 
  98     public void testInterrupt() {
  99         ReplTest interrupt = (a) -> assertCommand(a, "\u0003", "");
 100         for (String s : new String[] { "", "\u0003" }) {
 101             test(false, new String[]{"--no-startup"},
 102                     (a) -> assertCommand(a, "int a = 2 +" + s, ""),
 103                     interrupt,
 104                     (a) -> assertCommand(a, "int a\u0003", ""),
 105                     (a) -> assertCommand(a, "int a = 2 + 2\u0003", ""),
 106                     (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
 107                     (a) -> evaluateExpression(a, "int", "2", "2"),
 108                     (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
 109                     (a) -> assertCommand(a, "void f() {", ""),
 110                     (a) -> assertCommand(a, "int q = 10;" + s, ""),
 111                     interrupt,
 112                     (a) -> assertCommand(a, "void f() {}\u0003", ""),
 113                     (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
 114                     (a) -> assertMethod(a, "int f() { return 0; }", "()int", "f"),
 115                     (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
 116                     (a) -> assertCommand(a, "class A {" + s, ""),
 117                     interrupt,
 118                     (a) -> assertCommand(a, "class A {}\u0003", ""),
 119                     (a) -> assertCommandCheckOutput(a, "/types", assertClasses()),
 120                     (a) -> assertClass(a, "interface A {}", "interface", "A"),
 121                     (a) -> assertCommandCheckOutput(a, "/types", assertClasses()),
 122                     (a) -> assertCommand(a, "import java.util.stream." + s, ""),
 123                     interrupt,
 124                     (a) -> assertCommand(a, "import java.util.stream.\u0003", ""),
 125                     (a) -> assertCommandCheckOutput(a, "/imports", assertImports()),
 126                     (a) -> assertImport(a, "import java.util.stream.Stream", "", "java.util.stream.Stream"),
 127                     (a) -> assertCommandCheckOutput(a, "/imports", assertImports())
 128             );
 129         }
 130     }
 131 
 132     private final Object lock = new Object();
 133     private PrintWriter out;
 134     private boolean isStopped;
 135     private Thread t;
 136     private void assertStop(boolean after, String cmd, String output) {
 137         if (!after) {
 138             isStopped = false;
 139             StringWriter writer = new StringWriter();
 140             out = new PrintWriter(writer);
 141             setCommandInput(cmd + "\n");
 142             t = new Thread(() -> {
 143                 try {
 144                     // no chance to know whether cmd is being evaluated
 145                     Thread.sleep(5000);
 146                 } catch (InterruptedException ignored) {
 147                 }
 148                 int i = 1;
 149                 int n = 30;
 150                 synchronized (lock) {
 151                     do {
 152                         setCommandInput("\u0003");
 153                         if (!isStopped) {
 154                             out.println("Not stopped. Try again: " + i);
 155                             try {
 156                                 lock.wait(1000);
 157                             } catch (InterruptedException ignored) {
 158                             }
 159                         }
 160                     } while (i++ < n && !isStopped);
 161                     if (!isStopped) {
 162                         System.err.println(writer.toString());
 163                         fail("Evaluation was not stopped: '" + cmd + "'");
 164                     }
 165                 }
 166             });
 167             t.start();
 168         } else {
 169             synchronized (lock)  {
 170                 out.println("Evaluation was stopped successfully: '" + cmd + "'");
 171                 isStopped = true;
 172                 lock.notify();
 173             }
 174             try {
 175                 t.join();
 176                 t = null;
 177             } catch (InterruptedException ignored) {
 178             }
 179             assertOutput(getCommandOutput(), "", "command");
 180             assertOutput(getCommandErrorOutput(), "", "command error");
 181             assertOutput(getUserOutput().trim(), output, "user");
 182             assertOutput(getUserErrorOutput(), "", "user error");
 183         }
 184     }
 185 
 186     public void testStop() {
 187         test(
 188                 (a) -> assertStop(a, "while (true) {}", ""),
 189                 (a) -> assertStop(a, "while (true) { try { Thread.sleep(100); } catch (InterruptedException ex) { } }", "")
 190         );
 191     }
 192 
 193     public void testRerun() {
 194         test(false, new String[] {"--no-startup"},
 195                 (a) -> assertCommand(a, "/0", "|  No snippet with ID: 0"),
 196                 (a) -> assertCommand(a, "/5", "|  No snippet with ID: 5")
 197         );
 198         String[] codes = new String[] {
 199                 "int a = 0;", // var
 200                 "class A {}", // class
 201                 "void f() {}", // method
 202                 "bool b;", // active failed
 203                 "void g() { h(); }", // active corralled
 204         };
 205         List<ReplTest> tests = new ArrayList<>();
 206         for (String s : codes) {
 207             tests.add((a) -> assertCommand(a, s, null));
 208         }
 209         // Test /1 through /5 -- assure references are correct
 210         for (int i = 0; i < codes.length; ++i) {
 211             final int finalI = i;
 212             Consumer<String> check = (s) -> {
 213                 String[] ss = s.split("\n");
 214                 assertEquals(ss[0], codes[finalI]);
 215                 assertTrue(ss.length > 1, s);
 216             };
 217             tests.add((a) -> assertCommandCheckOutput(a, "/" + (finalI + 1), check));
 218         }
 219         // Test /-1 ... note that the snippets added by history must be stepped over
 220         for (int i = 0; i < codes.length; ++i) {
 221             final int finalI = i;
 222             Consumer<String> check = (s) -> {
 223                 String[] ss = s.split("\n");
 224                 assertEquals(ss[0], codes[codes.length - finalI - 1]);
 225                 assertTrue(ss.length > 1, s);
 226             };
 227             tests.add((a) -> assertCommandCheckOutput(a, "/-" + (2 * finalI + 1), check));
 228         }
 229         tests.add((a) -> assertCommandCheckOutput(a, "/!", assertStartsWith("int a = 0;")));
 230         test(false, new String[]{"--no-startup"},
 231                 tests.toArray(new ReplTest[tests.size()]));
 232     }
 233 
 234     public void test8142447() {
 235         Function<String, BiFunction<String, Integer, ReplTest>> assertRerun = cmd -> (code, assertionCount) ->
 236                 (a) -> assertCommandCheckOutput(a, cmd, s -> {
 237                             String[] ss = s.split("\n");
 238                             assertEquals(ss[0], code);
 239                             loadVariable(a, "int", "assertionCount", Integer.toString(assertionCount), Integer.toString(assertionCount));
 240                         });
 241         ReplTest assertVariables = (a) -> assertCommandCheckOutput(a, "/v", assertVariables());
 242 
 243         Compiler compiler = new Compiler();
 244         Path startup = compiler.getPath("StartupFileOption/startup.txt");
 245         compiler.writeToFile(startup, "int assertionCount = 0;\n" + // id: s1
 246                 "void add(int n) { assertionCount += n; }");
 247         test(new String[]{"--startup", startup.toString()},
 248                 (a) -> assertCommand(a, "add(1)", ""), // id: 1
 249                 (a) -> assertCommandCheckOutput(a, "add(ONE)", s -> assertEquals(s.split("\n")[0], "|  Error:")), // id: e1
 250                 (a) -> assertVariable(a, "int", "ONE", "1", "1"),
 251                 assertRerun.apply("/1").apply("add(1)", 2), assertVariables,
 252                 assertRerun.apply("/e1").apply("add(ONE)", 3), assertVariables,
 253                 assertRerun.apply("/s1").apply("int assertionCount = 0;", 0), assertVariables
 254         );
 255 
 256         test(false, new String[] {"--no-startup"},
 257                 (a) -> assertCommand(a, "/s1", "|  No snippet with ID: s1"),
 258                 (a) -> assertCommand(a, "/1", "|  No snippet with ID: 1"),
 259                 (a) -> assertCommand(a, "/e1", "|  No snippet with ID: e1")
 260         );
 261     }
 262 
 263     public void testClasspathDirectory() {
 264         Compiler compiler = new Compiler();
 265         Path outDir = Paths.get("testClasspathDirectory");
 266         compiler.compile(outDir, "package pkg; public class A { public String toString() { return \"A\"; } }");
 267         Path classpath = compiler.getPath(outDir);
 268         test(
 269                 (a) -> assertCommand(a, "/env --class-path " + classpath,
 270                         "|  Setting new options and restoring state."),
 271                 (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "A")
 272         );
 273         test(new String[] { "--class-path", classpath.toString() },
 274                 (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "A")
 275         );
 276     }
 277 
 278     public void testEnvInStartUp() {
 279         Compiler compiler = new Compiler();
 280         Path outDir = Paths.get("testClasspathDirectory");
 281         compiler.compile(outDir, "package pkg; public class A { public String toString() { return \"A\"; } }");
 282         Path classpath = compiler.getPath(outDir);
 283         Path sup = compiler.getPath("startup.jsh");
 284         compiler.writeToFile(sup,
 285                 "int xxx;\n" +
 286                 "/env -class-path " + classpath + "\n" +
 287                 "int aaa = 735;\n"
 288         );
 289         test(
 290                 (a) -> assertCommand(a, "/set start -retain " + sup, ""),
 291                 (a) -> assertCommand(a, "/reset",
 292                         "|  Resetting state."),
 293                 (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "A"),
 294                 (a) -> assertCommand(a, "aaa", "aaa ==> 735")
 295         );
 296         test(
 297                 (a) -> assertCommandOutputContains(a, "/env", "--class-path"),
 298                 (a) -> assertCommandOutputContains(a, "xxx", "cannot find symbol", "variable xxx"),
 299                 (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "A"),
 300                 (a) -> assertCommand(a, "aaa", "aaa ==> 735")
 301         );
 302     }
 303 
 304     private String makeSimpleJar() {
 305         Compiler compiler = new Compiler();
 306         Path outDir = Paths.get("testClasspathJar");
 307         compiler.compile(outDir, "package pkg; public class A { public String toString() { return \"A\"; } }");
 308         String jarName = "test.jar";
 309         compiler.jar(outDir, jarName, "pkg/A.class");
 310         return compiler.getPath(outDir).resolve(jarName).toString();
 311     }
 312 
 313     public void testClasspathJar() {
 314         String jarPath = makeSimpleJar();
 315         test(
 316                 (a) -> assertCommand(a, "/env --class-path " + jarPath,
 317                         "|  Setting new options and restoring state."),
 318                 (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "A")
 319         );
 320         test(new String[] { "--class-path", jarPath },
 321                 (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "A")
 322         );
 323     }
 324 
 325     public void testClasspathUserHomeExpansion() {
 326         String jarPath = makeSimpleJar();
 327         String tilde = "~" + File.separator;
 328         test(
 329                 (a) -> assertCommand(a, "/env --class-path " + tilde + "forblato",
 330                         "|  File '" + Paths.get(System.getProperty("user.home"), "forblato").toString()
 331                                 + "' for '--class-path' is not found."),
 332                 (a) -> assertCommand(a, "/env --class-path " + jarPath + File.pathSeparator
 333                                                             + tilde + "forblato",
 334                         "|  File '" + Paths.get(System.getProperty("user.home"), "forblato").toString()
 335                                 + "' for '--class-path' is not found.")
 336         );
 337     }
 338 
 339     public void testBadClasspath() {
 340         String jarPath = makeSimpleJar();
 341         Compiler compiler = new Compiler();
 342         Path t1 = compiler.getPath("whatever/thing.zip");
 343         compiler.writeToFile(t1, "");
 344         Path t2 = compiler.getPath("whatever/thing.jmod");
 345         compiler.writeToFile(t2, "");
 346         test(
 347                 (a) -> assertCommand(a, "/env --class-path " + t1.toString(),
 348                         "|  Invalid '--class-path' argument: " + t1.toString()),
 349                 (a) -> assertCommand(a, "/env --class-path " + jarPath + File.pathSeparator + t1.toString(),
 350                         "|  Invalid '--class-path' argument: " + t1.toString()),
 351                 (a) -> assertCommand(a, "/env --class-path " + t2.toString(),
 352                         "|  Invalid '--class-path' argument: " + t2.toString())
 353         );
 354     }
 355 
 356     private String makeBadSourceJar() {
 357         Compiler compiler = new Compiler();
 358         Path outDir = Paths.get("testClasspathJar");
 359         Path src = compiler.getPath(outDir.resolve("pkg/A.java"));
 360         compiler.writeToFile(src, "package pkg; /** \u0086 */public class A { public String toString() { return \"A\"; } }");
 361         String jarName = "test.jar";
 362         compiler.jar(outDir, jarName, "pkg/A.java");
 363         return compiler.getPath(outDir).resolve(jarName).toString();
 364     }
 365 
 366     public void testBadSourceJarClasspath() {
 367         String jarPath = makeBadSourceJar();
 368         test(
 369                 (a) -> assertCommand(a, "/env --class-path " + jarPath,
 370                         "|  Setting new options and restoring state."),
 371                 (a) -> assertCommandOutputStartsWith(a, "new pkg.A();",
 372                         "|  Error:\n"
 373                         + "|  cannot find symbol\n"
 374                         + "|    symbol:   class A")
 375         );
 376         test(new String[]{"--class-path", jarPath},
 377                 (a) -> assertCommandOutputStartsWith(a, "new pkg.A();",
 378                         "|  Error:\n"
 379                         + "|  cannot find symbol\n"
 380                         + "|    symbol:   class A")
 381         );
 382     }
 383 
 384     public void testModulePath() {
 385         Compiler compiler = new Compiler();
 386         Path modsDir = Paths.get("mods");
 387         Path outDir = Paths.get("mods", "org.astro");
 388         compiler.compile(outDir, "package org.astro; public class World { public static String name() { return \"world\"; } }");
 389         compiler.compile(outDir, "module org.astro { exports org.astro; }");
 390         Path modsPath = compiler.getPath(modsDir);
 391         test(new String[] { "--module-path", modsPath.toString(), "--add-modules", "org.astro" },
 392                 (a) -> assertCommand(a, "import org.astro.World;", ""),
 393                 (a) -> evaluateExpression(a, "String",
 394                         "String.format(\"Greetings %s!\", World.name());",
 395                         "\"Greetings world!\"")
 396         );
 397     }
 398 
 399     public void testModulePathUserHomeExpansion() {
 400         String tilde = "~" + File.separatorChar;
 401         test(
 402                 (a) -> assertCommand(a, "/env --module-path " + tilde + "snardugol",
 403                         "|  File '" + Paths.get(System.getProperty("user.home"), "snardugol").toString()
 404                                 + "' for '--module-path' is not found.")
 405         );
 406     }
 407 
 408     public void testBadModulePath() {
 409         Compiler compiler = new Compiler();
 410         Path t1 = compiler.getPath("whatever/thing.zip");
 411         compiler.writeToFile(t1, "");
 412         test(
 413                 (a) -> assertCommand(a, "/env --module-path " + t1.toString(),
 414                         "|  Invalid '--module-path' argument: " + t1.toString())
 415         );
 416     }
 417 
 418     public void testStartupFileOption() {
 419         Compiler compiler = new Compiler();
 420         Path startup = compiler.getPath("StartupFileOption/startup.txt");
 421         compiler.writeToFile(startup, "class A { public String toString() { return \"A\"; } }");
 422         test(new String[]{"--startup", startup.toString()},
 423                 (a) -> evaluateExpression(a, "A", "new A()", "A")
 424         );
 425         test(new String[]{"--no-startup"},
 426                 (a) -> assertCommandCheckOutput(a, "Pattern.compile(\"x+\")", assertStartsWith("|  Error:\n|  cannot find symbol"))
 427         );
 428         test(
 429                 (a) -> assertCommand(a, "Pattern.compile(\"x+\")", "$1 ==> x+", "", null, "", "")
 430         );
 431     }
 432 
 433     public void testLoadingFromArgs() {
 434         Compiler compiler = new Compiler();
 435         Path path = compiler.getPath("loading.repl");
 436         compiler.writeToFile(path, "int a = 10; double x = 20; double a = 10;");
 437         test(new String[] { path.toString() },
 438                 (a) -> assertCommand(a, "x", "x ==> 20.0"),
 439                 (a) -> assertCommand(a, "a", "a ==> 10.0")
 440         );
 441     }
 442 
 443     public void testReset() {
 444         test(
 445                 (a) -> assertReset(a, "/res"),
 446                 (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
 447                 (a) -> assertVariable(a, "int", "x"),
 448                 (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
 449                 (a) -> assertMethod(a, "void f() { }", "()void", "f"),
 450                 (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
 451                 (a) -> assertClass(a, "class A { }", "class", "A"),
 452                 (a) -> assertCommandCheckOutput(a, "/types", assertClasses()),
 453                 (a) -> assertImport(a, "import java.util.stream.*;", "", "java.util.stream.*"),
 454                 (a) -> assertCommandCheckOutput(a, "/imports", assertImports()),
 455                 (a) -> assertReset(a, "/reset"),
 456                 (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
 457                 (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
 458                 (a) -> assertCommandCheckOutput(a, "/types", assertClasses()),
 459                 (a) -> assertCommandCheckOutput(a, "/imports", assertImports())
 460         );
 461     }
 462 
 463     public void testOpen() {
 464         Compiler compiler = new Compiler();
 465         Path path = compiler.getPath("testOpen.repl");
 466         compiler.writeToFile(path,
 467                 "int a = 10;\ndouble x = 20;\ndouble a = 10;\n" +
 468                         "class A { public String toString() { return \"A\"; } }\nimport java.util.stream.*;");
 469         for (String s : new String[]{"/o", "/open"}) {
 470             test(
 471                     (a) -> assertCommand(a, s + " " + path.toString(), ""),
 472                     (a) -> assertCommand(a, "a", "a ==> 10.0"),
 473                     (a) -> evaluateExpression(a, "A", "new A();", "A"),
 474                     (a) -> evaluateExpression(a, "long", "Stream.of(\"A\").count();", "1"),
 475                     (a) -> {
 476                         loadVariable(a, "double", "x", "20.0", "20.0");
 477                         loadVariable(a, "double", "a", "10.0", "10.0");
 478                         loadVariable(a, "A", "$7", "new A();", "A");
 479                         loadVariable(a, "long", "$8", "Stream.of(\"A\").count();", "1");
 480                         loadClass(a, "class A { public String toString() { return \"A\"; } }",
 481                                 "class", "A");
 482                         loadImport(a, "import java.util.stream.*;", "", "java.util.stream.*");
 483                         assertCommandCheckOutput(a, "/types", assertClasses());
 484                     },
 485                     (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
 486                     (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
 487                     (a) -> assertCommandCheckOutput(a, "/imports", assertImports())
 488             );
 489             Path unknown = compiler.getPath("UNKNOWN.repl");
 490             test(
 491                     (a) -> assertCommand(a, s + " " + unknown,
 492                             "|  File '" + unknown + "' for '/open' is not found.")
 493             );
 494         }
 495     }
 496 
 497     public void testOpenLocalFileUrl() {
 498         Compiler compiler = new Compiler();
 499         Path path = compiler.getPath("testOpen.repl");
 500         compiler.writeToFile(path, "int a = 10;int b = 20;int c = a + b;\n");
 501         for (String s : new String[]{"/o", "/open"}) {
 502             test(
 503                     (a) -> assertCommand(a, s + " file://" + path.toString(), ""),
 504                     (a) -> assertCommand(a, "a", "a ==> 10"),
 505                     (a) -> assertCommand(a, "b", "b ==> 20"),
 506                     (a) -> assertCommand(a, "c", "c ==> 30")
 507             );
 508         }
 509     }
 510 
 511     public void testOpenFileOverHttp() throws IOException {
 512         var script = "int a = 10;int b = 20;int c = a + b;";
 513 
 514         var localhostAddress = new InetSocketAddress(InetAddress.getLocalHost().getHostAddress(), 0);
 515         var httpServer = HttpServer.create(localhostAddress, 0);
 516         try {
 517             httpServer.createContext("/script", exchange -> {
 518                 exchange.sendResponseHeaders(200, script.length());
 519                 try (var output = exchange.getResponseBody()) {
 520                     output.write(script.getBytes());
 521                 }
 522             });
 523             httpServer.setExecutor(null);
 524             httpServer.start();
 525 
 526             var urlAddress = "http:/" + httpServer.getAddress().toString() + "/script";
 527             for (String s : new String[]{"/o", "/open"}) {
 528                 test(
 529                         (a) -> assertCommand(a, s + " " + urlAddress, ""),
 530                         (a) -> assertCommand(a, "a", "a ==> 10"),
 531                         (a) -> assertCommand(a, "b", "b ==> 20"),
 532                         (a) -> assertCommand(a, "c", "c ==> 30")
 533                 );
 534             }
 535         } finally {
 536             httpServer.stop(0);
 537         }
 538     }
 539 
 540     public void testOpenResource() {
 541         test(
 542                 (a) -> assertCommand(a, "/open PRINTING", ""),
 543                 (a) -> assertCommandOutputContains(a, "/list",
 544                         "void println", "System.out.printf"),
 545                 (a) -> assertCommand(a, "printf(\"%4.2f\", Math.PI)",
 546                         "", "", null, "3.14", "")
 547         );
 548     }
 549 
 550     public void testSave() throws IOException {
 551         Compiler compiler = new Compiler();
 552         Path path = compiler.getPath("testSave.repl");
 553         {
 554             List<String> list = Arrays.asList(
 555                     "int a;",
 556                     "class A { public String toString() { return \"A\"; } }"
 557             );
 558             test(
 559                     (a) -> assertVariable(a, "int", "a"),
 560                     (a) -> assertCommand(a, "()", null, null, null, "", ""),
 561                     (a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
 562                     (a) -> assertCommand(a, "/save " + path.toString(), "")
 563             );
 564             assertEquals(Files.readAllLines(path), list);
 565         }
 566         {
 567             List<String> output = new ArrayList<>();
 568             test(
 569                     (a) -> assertCommand(a, "int a;", null),
 570                     (a) -> assertCommand(a, "()", null, null, null, "", ""),
 571                     (a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
 572                     (a) -> assertCommandCheckOutput(a, "/list -all", (out) ->
 573                                     output.addAll(Stream.of(out.split("\n"))
 574                             .filter(str -> !str.isEmpty())
 575                             .map(str -> str.substring(str.indexOf(':') + 2))
 576                             .filter(str -> !str.startsWith("/"))
 577                             .collect(Collectors.toList()))),
 578                     (a) -> assertCommand(a, "/save -all " + path.toString(), "")
 579             );
 580             assertEquals(Files.readAllLines(path), output);
 581         }
 582         {
 583             List<String> output = new ArrayList<>();
 584             test(
 585                     (a) -> assertCommand(a, "int a;", null),
 586                     (a) -> assertCommand(a, "int b;", null),
 587                     (a) -> assertCommand(a, "int c;", null),
 588                     (a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
 589                     (a) -> assertCommandCheckOutput(a, "/list b c a A", (out) ->
 590                                     output.addAll(Stream.of(out.split("\n"))
 591                             .filter(str -> !str.isEmpty())
 592                             .map(str -> str.substring(str.indexOf(':') + 2))
 593                             .filter(str -> !str.startsWith("/"))
 594                             .collect(Collectors.toList()))),
 595                     (a) -> assertCommand(a, "/save 2-3 1 4 " + path.toString(), "")
 596             );
 597             assertEquals(Files.readAllLines(path), output);
 598         }
 599         {
 600             List<String> output = new ArrayList<>();
 601             test(
 602                     (a) -> assertVariable(a, "int", "a"),
 603                     (a) -> assertCommand(a, "()", null, null, null, "", ""),
 604                     (a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
 605                     (a) -> assertCommandCheckOutput(a, "/history", (out) ->
 606                                 output.addAll(Stream.of(out.split("\n"))
 607                             .filter(str -> !str.isEmpty())
 608                             .collect(Collectors.toList()))),
 609                     (a) -> assertCommand(a, "/save -history " + path.toString(), "")
 610             );
 611             output.add("/save -history " + path.toString());
 612             assertEquals(Files.readAllLines(path), output);
 613         }
 614     }
 615 
 616     public void testStartRetain() {
 617         Compiler compiler = new Compiler();
 618         Path startUpFile = compiler.getPath("startUp.txt");
 619         test(
 620                 (a) -> assertVariable(a, "int", "a"),
 621                 (a) -> assertVariable(a, "double", "b", "10", "10.0"),
 622                 (a) -> assertMethod(a, "void f() {}", "()V", "f"),
 623                 (a) -> assertImport(a, "import java.util.stream.*;", "", "java.util.stream.*"),
 624                 (a) -> assertCommand(a, "/save " + startUpFile.toString(), null),
 625                 (a) -> assertCommand(a, "/set start -retain " + startUpFile.toString(), null)
 626         );
 627         Path unknown = compiler.getPath("UNKNOWN");
 628         test(
 629                 (a) -> assertCommandOutputStartsWith(a, "/set start -retain " + unknown.toString(),
 630                         "|  File '" + unknown + "' for '/set start' is not found.")
 631         );
 632         test(false, new String[0],
 633                 (a) -> {
 634                     loadVariable(a, "int", "a");
 635                     loadVariable(a, "double", "b", "10.0", "10.0");
 636                     loadMethod(a, "void f() {}", "()void", "f");
 637                     loadImport(a, "import java.util.stream.*;", "", "java.util.stream.*");
 638                     assertCommandCheckOutput(a, "/types", assertClasses());
 639                 },
 640                 (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
 641                 (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
 642                 (a) -> assertCommandCheckOutput(a, "/imports", assertImports())
 643         );
 644     }
 645 
 646     public void testStartSave() throws IOException {
 647         Compiler compiler = new Compiler();
 648         Path startSave = compiler.getPath("startSave.txt");
 649         test(a -> assertCommand(a, "/save -start " + startSave.toString(), null));
 650         List<String> lines = Files.lines(startSave)
 651                 .filter(s -> !s.isEmpty())
 652                 .collect(Collectors.toList());
 653         assertEquals(lines, START_UP);
 654     }
 655 
 656     public void testConstrainedUpdates() {
 657         test(
 658                 a -> assertClass(a, "class XYZZY { }", "class", "XYZZY"),
 659                 a -> assertVariable(a, "XYZZY", "xyzzy"),
 660                 a -> assertCommandCheckOutput(a, "import java.util.stream.*",
 661                         (out) -> assertTrue(out.trim().isEmpty(), "Expected no output, got: " + out))
 662         );
 663     }
 664 
 665     public void testRemoteExit() {
 666         test(
 667                 a -> assertVariable(a, "int", "x"),
 668                 a -> assertCommandCheckOutput(a, "/vars", assertVariables()),
 669                 a -> assertCommandOutputContains(a, "System.exit(5);", "terminated"),
 670                 a -> assertCommandCheckOutput(a, "/vars", s ->
 671                         assertTrue(s.trim().isEmpty(), s)),
 672                 a -> assertMethod(a, "void f() { }", "()void", "f"),
 673                 a -> assertCommandCheckOutput(a, "/methods", assertMethods())
 674         );
 675     }
 676 
 677     public void testFeedbackNegative() {
 678         test(a -> assertCommandCheckOutput(a, "/set feedback aaaa",
 679                 assertStartsWith("|  Does not match any current feedback mode")));
 680     }
 681 
 682     public void testFeedbackSilent() {
 683         for (String off : new String[]{"s", "silent"}) {
 684             test(
 685                     a -> assertCommand(a, "/set feedback " + off, ""),
 686                     a -> assertCommand(a, "int a", ""),
 687                     a -> assertCommand(a, "void f() {}", ""),
 688                     a -> assertCommandCheckOutput(a, "aaaa", assertStartsWith("|  Error:")),
 689                     a -> assertCommandCheckOutput(a, "static void f() {}", assertStartsWith("|  Warning:"))
 690             );
 691         }
 692     }
 693 
 694     public void testFeedbackNormal() {
 695         Compiler compiler = new Compiler();
 696         Path testNormalFile = compiler.getPath("testConciseNormal");
 697         String[] sources = new String[] {"int a", "void f() {}", "class A {}", "a = 10"};
 698         String[] sources2 = new String[] {"int a //again", "void f() {int y = 4;}", "class A {} //again", "a = 10"};
 699         String[] output = new String[] {
 700                 "a ==> 0",
 701                 "|  created method f()",
 702                 "|  created class A",
 703                 "a ==> 10"
 704         };
 705         compiler.writeToFile(testNormalFile, sources2);
 706         for (String feedback : new String[]{"/set fe", "/set feedback"}) {
 707             for (String feedbackState : new String[]{"n", "normal"}) {
 708                 test(
 709                         a -> assertCommand(a, feedback + " " + feedbackState, "|  Feedback mode: normal"),
 710                         a -> assertCommand(a, sources[0], output[0]),
 711                         a -> assertCommand(a, sources[1], output[1]),
 712                         a -> assertCommand(a, sources[2], output[2]),
 713                         a -> assertCommand(a, sources[3], output[3]),
 714                         a -> assertCommand(a, "/o " + testNormalFile.toString(), "")
 715                 );
 716             }
 717         }
 718     }
 719 
 720     public void testVarsWithNotActive() {
 721         test(
 722                 a -> assertVariable(a, "Blath", "x"),
 723                 a -> assertCommandOutputContains(a, "/var -all", "(not-active)")
 724         );
 725     }
 726 
 727     public void testHistoryReference() {
 728         test(false, new String[]{"--no-startup"},
 729                 a -> assertCommand(a, "System.err.println(99)", "", "", null, "", "99\n"),
 730                 a -> assertCommand(a, "/exit", "")
 731         );
 732         test(false, new String[]{"--no-startup"},
 733                 a -> assertCommand(a, "System.err.println(1)", "", "", null, "", "1\n"),
 734                 a -> assertCommand(a, "System.err.println(2)", "", "", null, "", "2\n"),
 735                 a -> assertCommand(a, "/-2", "System.err.println(1)", "", null, "", "1\n"),
 736                 a -> assertCommand(a, "/history",
 737                                                     "/debug 0\n" +
 738                                                     "System.err.println(1)\n" +
 739                                                     "System.err.println(2)\n" +
 740                                                     "System.err.println(1)\n" +
 741                                                     "/history\n"),
 742                 a -> assertCommand(a, "/history -all",
 743                                                     "/debug 0\n" +
 744                                                     "System.err.println(99)\n" +
 745                                                     "/exit\n" +
 746                                                     "/debug 0\n" +
 747                                                     "System.err.println(1)\n" +
 748                                                     "System.err.println(2)\n" +
 749                                                     "System.err.println(1)\n" +
 750                                                     "/history\n" +
 751                                                     "/history -all\n"),
 752                 a -> assertCommand(a, "/-2", "System.err.println(2)", "", null, "", "2\n"),
 753                 a -> assertCommand(a, "/!", "System.err.println(2)", "", null, "", "2\n"),
 754                 a -> assertCommand(a, "/2", "System.err.println(2)", "", null, "", "2\n"),
 755                 a -> assertCommand(a, "/1", "System.err.println(1)", "", null, "", "1\n")
 756         );
 757     }
 758 
 759     public void testRerunIdRange() {
 760         Compiler compiler = new Compiler();
 761         Path startup = compiler.getPath("rangeStartup");
 762         String[] startupSources = new String[] {
 763             "boolean go = false",
 764             "void println(String s) { if (go) System.out.println(s); }",
 765             "void println(int i) { if (go) System.out.println(i); }",
 766             "println(\"s4\")",
 767             "println(\"s5\")",
 768             "println(\"s6\")"
 769         };
 770         String[] sources = new String[] {
 771             "frog",
 772             "go = true",
 773             "println(2)",
 774             "println(3)",
 775             "println(4)",
 776             "querty"
 777         };
 778         compiler.writeToFile(startup, startupSources);
 779         test(false, new String[]{"--startup", startup.toString()},
 780                 a -> assertCommandOutputStartsWith(a, sources[0], "|  Error:"),
 781                 a -> assertCommand(a, sources[1], "go ==> true", "", null, "", ""),
 782                 a -> assertCommand(a, sources[2], "", "", null, "2\n", ""),
 783                 a -> assertCommand(a, sources[3], "", "", null, "3\n", ""),
 784                 a -> assertCommand(a, sources[4], "", "", null, "4\n", ""),
 785                 a -> assertCommandOutputStartsWith(a, sources[5], "|  Error:"),
 786                 a -> assertCommand(a, "/3", "println(3)", "", null, "3\n", ""),
 787                 a -> assertCommand(a, "/s4", "println(\"s4\")", "", null, "s4\n", ""),
 788                 a -> assertCommandOutputStartsWith(a, "/e1", "frog\n|  Error:"),
 789                 a -> assertCommand(a, "/2-4",
 790                         "println(2)\nprintln(3)\nprintln(4)",
 791                         "", null, "2\n3\n4\n", ""),
 792                 a -> assertCommand(a, "/s4-s6",
 793                         startupSources[3] + "\n" +startupSources[4] + "\n" +startupSources[5],
 794                         "", null, "s4\ns5\ns6\n", ""),
 795                 a -> assertCommand(a, "/s4-4", null,
 796                         "", null, "s4\ns5\ns6\n2\n3\n4\n", ""),
 797                 a -> assertCommandCheckOutput(a, "/e1-e2",
 798                         s -> {
 799                             assertTrue(s.trim().startsWith("frog\n|  Error:"),
 800                                     "Output: \'" + s + "' does not start with: " + "|  Error:");
 801                             assertTrue(s.trim().lastIndexOf("|  Error:") > 10,
 802                                     "Output: \'" + s + "' does not have second: " + "|  Error:");
 803                         }),
 804                 a -> assertCommand(a, "/4  s4 2",
 805                         "println(4)\nprintln(\"s4\")\nprintln(2)",
 806                         "", null, "4\ns4\n2\n", ""),
 807                 a -> assertCommand(a, "/s5 2-4 3",
 808                         "println(\"s5\")\nprintln(2)\nprintln(3)\nprintln(4)\nprintln(3)",
 809                         "", null, "s5\n2\n3\n4\n3\n", ""),
 810                 a -> assertCommand(a, "/2 ff", "|  No such snippet: ff"),
 811                 a -> assertCommand(a, "/4-2", "|  End of snippet range less than start: 4 - 2"),
 812                 a -> assertCommand(a, "/s5-s3", "|  End of snippet range less than start: s5 - s3"),
 813                 a -> assertCommand(a, "/4-s5", "|  End of snippet range less than start: 4 - s5")
 814         );
 815     }
 816 
 817     @Test(enabled = false) // TODO 8158197
 818     public void testHeadlessEditPad() {
 819         String prevHeadless = System.getProperty("java.awt.headless");
 820         try {
 821             System.setProperty("java.awt.headless", "true");
 822             test(
 823                 (a) -> assertCommandOutputStartsWith(a, "/edit printf", "|  Cannot launch editor -- unexpected exception:")
 824             );
 825         } finally {
 826             System.setProperty("java.awt.headless", prevHeadless==null? "false" : prevHeadless);
 827         }
 828     }
 829 
 830     public void testAddExports() {
 831         test(false, new String[]{"--no-startup"},
 832                 a -> assertCommandOutputStartsWith(a, "import jdk.internal.misc.VM;", "|  Error:")
 833         );
 834         test(false, new String[]{"--no-startup",
 835             "-R--add-exports", "-Rjava.base/jdk.internal.misc=ALL-UNNAMED",
 836             "-C--add-exports", "-Cjava.base/jdk.internal.misc=ALL-UNNAMED"},
 837                 a -> assertImport(a, "import jdk.internal.misc.VM;", "", "jdk.internal.misc.VM"),
 838                 a -> assertCommand(a, "System.err.println(VM.isBooted())", "", "", null, "", "true\n")
 839         );
 840         test(false, new String[]{"--no-startup", "--add-exports", "java.base/jdk.internal.misc"},
 841                 a -> assertImport(a, "import jdk.internal.misc.VM;", "", "jdk.internal.misc.VM"),
 842                 a -> assertCommand(a, "System.err.println(VM.isBooted())", "", "", null, "", "true\n")
 843         );
 844     }
 845 
 846 }