1 /* 2 * Copyright (c) 2015, 2018, 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 import java.io.ByteArrayOutputStream; 25 import java.io.IOException; 26 import java.io.OutputStream; 27 import java.io.PrintStream; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Collections; 31 import java.util.HashMap; 32 import java.util.List; 33 import java.util.Locale; 34 import java.util.Map; 35 import java.util.function.Consumer; 36 import java.util.function.Function; 37 import java.util.function.Predicate; 38 import java.util.logging.Level; 39 import java.util.logging.Logger; 40 import java.util.prefs.AbstractPreferences; 41 import java.util.prefs.BackingStoreException; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 import java.util.stream.Collectors; 45 import java.util.stream.Stream; 46 47 48 import org.testng.annotations.BeforeMethod; 49 50 import jdk.jshell.tool.JavaShellToolBuilder; 51 import static java.util.stream.Collectors.toList; 52 import static org.testng.Assert.assertEquals; 53 import static org.testng.Assert.assertNotNull; 54 import static org.testng.Assert.assertTrue; 55 import static org.testng.Assert.fail; 56 57 public class ReplToolTesting { 58 59 private final static String DEFAULT_STARTUP_MESSAGE = "| Welcome to"; 60 final static List<ImportInfo> START_UP_IMPORTS = Stream.of( 61 "java.io.*", 62 "java.math.*", 63 "java.net.*", 64 "java.nio.file.*", 65 "java.util.*", 66 "java.util.concurrent.*", 67 "java.util.function.*", 68 "java.util.prefs.*", 69 "java.util.regex.*", 70 "java.util.stream.*") 71 .map(s -> new ImportInfo("import " + s + ";", "", s)) 72 .collect(toList()); 73 final static List<MethodInfo> START_UP_METHODS = Stream.<MethodInfo>of() 74 .collect(toList()); 75 final static List<String> START_UP_CMD_METHOD = Stream.<String>of() 76 .collect(toList()); 77 final static List<String> PRINTING_CMD_METHOD = Stream.of( 78 "| void print(boolean)", 79 "| void print(char)", 80 "| void print(int)", 81 "| void print(long)", 82 "| void print(float)", 83 "| void print(double)", 84 "| void print(char s[])", 85 "| void print(String)", 86 "| void print(Object)", 87 "| void println()", 88 "| void println(boolean)", 89 "| void println(char)", 90 "| void println(int)", 91 "| void println(long)", 92 "| void println(float)", 93 "| void println(double)", 94 "| void println(char s[])", 95 "| void println(String)", 96 "| void println(Object)", 97 "| void printf(java.util.Locale,String,Object...)", 98 "| void printf(String,Object...)") 99 .collect(toList()); 100 final static List<String> START_UP = Collections.unmodifiableList( 101 Stream.concat(START_UP_IMPORTS.stream(), START_UP_METHODS.stream()) 102 .map(s -> s.getSource()) 103 .collect(toList())); 104 105 private WaitingTestingInputStream cmdin = null; 106 private ByteArrayOutputStream cmdout = null; 107 private ByteArrayOutputStream cmderr = null; 108 private PromptedCommandOutputStream console = null; 109 private TestingInputStream userin = null; 110 private ByteArrayOutputStream userout = null; 111 private ByteArrayOutputStream usererr = null; 112 113 private List<MemberInfo> keys; 114 private Map<String, VariableInfo> variables; 115 private Map<String, MethodInfo> methods; 116 private Map<String, ClassInfo> classes; 117 private Map<String, ImportInfo> imports; 118 private boolean isDefaultStartUp = true; 119 protected Map<String, String> prefsMap; 120 private Map<String, String> envvars; 121 122 public interface ReplTest { run(boolean after)123 void run(boolean after); 124 } 125 setCommandInput(String s)126 public void setCommandInput(String s) { 127 cmdin.setInput(s); 128 } 129 closeCommandInput()130 public void closeCommandInput() { 131 try { 132 cmdin.close(); 133 } catch (IOException ex) { 134 throw new IllegalStateException(ex); 135 } 136 } 137 138 public final static Pattern idPattern = Pattern.compile("^\\s+(\\d+)"); assertList()139 public Consumer<String> assertList() { 140 return s -> { 141 List<String> lines = Stream.of(s.split("\n")) 142 .filter(l -> !l.isEmpty()) 143 .collect(Collectors.toList()); 144 int previousId = Integer.MIN_VALUE; 145 assertEquals(lines.size(), keys.size(), "Number of keys"); 146 for (int i = 0; i < lines.size(); ++i) { 147 String line = lines.get(i); 148 Matcher matcher = idPattern.matcher(line); 149 assertTrue(matcher.find(), "Snippet id not found: " + line); 150 String src = keys.get(i).getSource(); 151 assertTrue(line.endsWith(src), "Line '" + line + "' does not end with: " + src); 152 int id = Integer.parseInt(matcher.group(1)); 153 assertTrue(previousId < id, 154 String.format("The previous id is not less than the next one: previous: %d, next: %d", 155 previousId, id)); 156 previousId = id; 157 } 158 }; 159 } 160 161 private final static Pattern extractPattern = Pattern.compile("^\\| *(.*)$"); 162 private Consumer<String> assertMembers(String message, Map<String, ? extends MemberInfo> set) { 163 return s -> { 164 List<String> lines = Stream.of(s.split("\n")) 165 .filter(l -> !l.isEmpty()) 166 .filter(l -> !l.startsWith("| ")) // error/unresolved info 167 .collect(Collectors.toList()); 168 assertEquals(lines.size(), set.size(), message + " : expected: " + set.keySet() + "\ngot:\n" + lines); 169 for (String line : lines) { 170 Matcher matcher = extractPattern.matcher(line); 171 assertTrue(matcher.find(), line); 172 String src = matcher.group(1); 173 MemberInfo info = set.get(src); 174 assertNotNull(info, "Not found snippet with signature: " + src + ", line: " 175 + line + ", keys: " + set.keySet() + "\n"); 176 } 177 }; 178 } 179 180 public Consumer<String> assertVariables() { 181 return assertMembers("Variables", variables); 182 } 183 184 public Consumer<String> assertMethods() { 185 return assertMembers("Methods", methods); 186 } 187 188 public Consumer<String> assertClasses() { 189 return assertMembers("Classes", classes); 190 } 191 192 public Consumer<String> assertImports() { 193 return assertMembers("Imports", imports); 194 } 195 196 public String getCommandOutput() { 197 String s = normalizeLineEndings(cmdout.toString()); 198 cmdout.reset(); 199 return s; 200 } 201 202 public String getCommandErrorOutput() { 203 String s = normalizeLineEndings(cmderr.toString()); 204 cmderr.reset(); 205 return s; 206 } 207 208 public void setUserInput(String s) { 209 userin.setInput(s); 210 } 211 212 public String getUserOutput() { 213 String s = normalizeLineEndings(userout.toString()); 214 userout.reset(); 215 return s; 216 } 217 218 public String getUserErrorOutput() { 219 String s = normalizeLineEndings(usererr.toString()); 220 usererr.reset(); 221 return s; 222 } 223 224 public void test(ReplTest... tests) { 225 test(new String[0], tests); 226 } 227 228 public void test(String[] args, ReplTest... tests) { 229 test(true, args, tests); 230 } 231 232 public void test(boolean isDefaultStartUp, String[] args, ReplTest... tests) { 233 test(Locale.ROOT, isDefaultStartUp, args, DEFAULT_STARTUP_MESSAGE, tests); 234 } 235 236 public void testNoStartUp(ReplTest... tests) { 237 test(Locale.ROOT, false, new String[] {"--no-startup"}, DEFAULT_STARTUP_MESSAGE, tests); 238 } 239 240 public void test(Locale locale, boolean isDefaultStartUp, String[] args, String startUpMessage, ReplTest... tests) { 241 this.isDefaultStartUp = isDefaultStartUp; 242 initSnippets(); 243 ReplTest[] wtests = new ReplTest[tests.length + 3]; 244 wtests[0] = a -> assertCommandCheckOutput(a, "<start>", 245 s -> assertTrue(s.startsWith(startUpMessage), "Expected start-up message '" + startUpMessage + "' Got: " + s)); 246 wtests[1] = a -> assertCommand(a, "/debug 0", null); 247 System.arraycopy(tests, 0, wtests, 2, tests.length); 248 wtests[tests.length + 2] = a -> assertCommand(a, "/exit", null); 249 testRaw(locale, args, wtests); 250 } 251 252 private void initSnippets() { 253 keys = new ArrayList<>(); 254 variables = new HashMap<>(); 255 methods = new HashMap<>(); 256 classes = new HashMap<>(); 257 imports = new HashMap<>(); 258 if (isDefaultStartUp) { 259 methods.putAll( 260 START_UP_METHODS.stream() 261 .collect(Collectors.toMap(Object::toString, Function.identity()))); 262 imports.putAll( 263 START_UP_IMPORTS.stream() 264 .collect(Collectors.toMap(Object::toString, Function.identity()))); 265 } 266 } 267 268 @BeforeMethod 269 public void setUp() { 270 prefsMap = new HashMap<>(); 271 envvars = new HashMap<>(); 272 System.setProperty("jshell.test.allow.incomplete.inputs", "true"); 273 } 274 275 protected void setEnvVar(String name, String value) { 276 envvars.put(name, value); 277 } 278 279 protected JavaShellToolBuilder builder(Locale locale) { 280 // turn on logging of launch failures 281 Logger.getLogger("jdk.jshell.execution").setLevel(Level.ALL); 282 return JavaShellToolBuilder 283 .builder() 284 .in(cmdin, userin) 285 .out(new PrintStream(cmdout), new PrintStream(console), new PrintStream(userout)) 286 .err(new PrintStream(cmderr), new PrintStream(usererr)) 287 .persistence(prefsMap) 288 .env(envvars) 289 .locale(locale) 290 .promptCapture(true); 291 } 292 293 private void testRaw(Locale locale, String[] args, ReplTest... tests) { 294 testRawInit(tests); 295 testRawRun(locale, args); 296 testRawCheck(locale); 297 } 298 299 private void testRawInit(ReplTest... tests) { 300 cmdin = new WaitingTestingInputStream(); 301 cmdout = new ByteArrayOutputStream(); 302 cmderr = new ByteArrayOutputStream(); 303 console = new PromptedCommandOutputStream(tests); 304 userin = new TestingInputStream(); 305 userout = new ByteArrayOutputStream(); 306 usererr = new ByteArrayOutputStream(); 307 } 308 309 protected void testRawRun(Locale locale, String[] args) { 310 try { 311 builder(locale) 312 .run(args); 313 } catch (Exception ex) { 314 fail("Repl tool died with exception", ex); 315 } 316 } 317 318 private void testRawCheck(Locale locale) { 319 // perform internal consistency checks on state, if desired 320 String cos = getCommandOutput(); 321 String ceos = getCommandErrorOutput(); 322 String uos = getUserOutput(); 323 String ueos = getUserErrorOutput(); 324 assertTrue((cos.isEmpty() || cos.startsWith("| Goodbye") || !locale.equals(Locale.ROOT)), 325 "Expected a goodbye, but got: " + cos); 326 assertTrue(ceos.isEmpty(), "Expected empty command error output, got: " + ceos); 327 assertTrue(uos.isEmpty(), "Expected empty user output, got: " + uos); 328 assertTrue(ueos.isEmpty(), "Expected empty user error output, got: " + ueos); 329 } 330 331 public void assertReset(boolean after, String cmd) { 332 assertCommand(after, cmd, "| Resetting state.\n"); 333 initSnippets(); 334 } 335 336 public void evaluateExpression(boolean after, String type, String expr, String value) { 337 String output = String.format("(\\$\\d+) ==> %s", value); 338 Pattern outputPattern = Pattern.compile(output); 339 assertCommandCheckOutput(after, expr, s -> { 340 Matcher matcher = outputPattern.matcher(s); 341 assertTrue(matcher.find(), "Output: '" + s + "' does not fit pattern: '" + output + "'"); 342 String name = matcher.group(1); 343 VariableInfo tempVar = new TempVariableInfo(expr, type, name, value); 344 variables.put(tempVar.toString(), tempVar); 345 addKey(after, tempVar); 346 }); 347 } 348 349 public void loadVariable(boolean after, String type, String name) { 350 loadVariable(after, type, name, null, null); 351 } 352 353 public void loadVariable(boolean after, String type, String name, String expr, String value) { 354 String src = expr == null 355 ? String.format("%s %s", type, name) 356 : String.format("%s %s = %s", type, name, expr); 357 VariableInfo var = expr == null 358 ? new VariableInfo(src, type, name) 359 : new VariableInfo(src, type, name, value); 360 addKey(after, var, variables); 361 addKey(after, var); 362 } 363 364 public void assertVariable(boolean after, String type, String name) { 365 assertVariable(after, type, name, null, null); 366 } 367 368 public void assertVariable(boolean after, String type, String name, String expr, String value) { 369 String src = expr == null 370 ? String.format("%s %s", type, name) 371 : String.format("%s %s = %s", type, name, expr); 372 VariableInfo var = expr == null 373 ? new VariableInfo(src, type, name) 374 : new VariableInfo(src, type, name, value); 375 assertCommandCheckOutput(after, src, var.checkOutput()); 376 addKey(after, var, variables); 377 addKey(after, var); 378 } 379 380 public void loadMethod(boolean after, String src, String signature, String name) { 381 MethodInfo method = new MethodInfo(src, signature, name); 382 addKey(after, method, methods); 383 addKey(after, method); 384 } 385 386 public void assertMethod(boolean after, String src, String signature, String name) { 387 MethodInfo method = new MethodInfo(src, signature, name); 388 assertCommandCheckOutput(after, src, method.checkOutput()); 389 addKey(after, method, methods); 390 addKey(after, method); 391 } 392 393 public void loadClass(boolean after, String src, String type, String name) { 394 ClassInfo clazz = new ClassInfo(src, type, name); 395 addKey(after, clazz, classes); 396 addKey(after, clazz); 397 } 398 399 public void assertClass(boolean after, String src, String type, String name) { 400 ClassInfo clazz = new ClassInfo(src, type, name); 401 assertCommandCheckOutput(after, src, clazz.checkOutput()); 402 addKey(after, clazz, classes); 403 addKey(after, clazz); 404 } 405 406 public void loadImport(boolean after, String src, String type, String name) { 407 ImportInfo i = new ImportInfo(src, type, name); 408 addKey(after, i, imports); 409 addKey(after, i); 410 } 411 412 public void assertImport(boolean after, String src, String type, String name) { 413 ImportInfo i = new ImportInfo(src, type, name); 414 assertCommandCheckOutput(after, src, i.checkOutput()); 415 addKey(after, i, imports); 416 addKey(after, i); 417 } 418 419 private <T extends MemberInfo> void addKey(boolean after, T memberInfo, Map<String, T> map) { 420 if (after) { 421 map.entrySet().removeIf(e -> e.getValue().equals(memberInfo)); 422 map.put(memberInfo.toString(), memberInfo); 423 } 424 } 425 426 private <T extends MemberInfo> void addKey(boolean after, T memberInfo) { 427 if (after) { 428 for (int i = 0; i < keys.size(); ++i) { 429 MemberInfo m = keys.get(i); 430 if (m.equals(memberInfo)) { 431 keys.set(i, memberInfo); 432 return; 433 } 434 } 435 keys.add(memberInfo); 436 } 437 } 438 439 private void dropKey(boolean after, String cmd, String name, Map<String, ? extends MemberInfo> map, String output) { 440 assertCommand(after, cmd, output); 441 if (after) { 442 map.remove(name); 443 for (int i = 0; i < keys.size(); ++i) { 444 MemberInfo m = keys.get(i); 445 if (m.toString().equals(name)) { 446 keys.remove(i); 447 return; 448 } 449 } 450 throw new AssertionError("Key not found: " + name + ", keys: " + keys); 451 } 452 } 453 454 public void dropVariable(boolean after, String cmd, String name, String output) { 455 dropKey(after, cmd, name, variables, output); 456 } 457 458 public void dropMethod(boolean after, String cmd, String name, String output) { 459 dropKey(after, cmd, name, methods, output); 460 } 461 462 public void dropClass(boolean after, String cmd, String name, String output) { 463 dropKey(after, cmd, name, classes, output); 464 } 465 466 public void dropImport(boolean after, String cmd, String name, String output) { 467 dropKey(after, cmd, name, imports, output); 468 } 469 470 public void assertCommand(boolean after, String cmd, String out) { 471 assertCommand(after, cmd, out, "", null, "", ""); 472 } 473 474 public void assertCommandOutputContains(boolean after, String cmd, String... hasThese) { 475 assertCommandCheckOutput(after, cmd, (s) 476 -> assertTrue(Arrays.stream(hasThese) 477 .allMatch(has -> s.contains(has)), 478 "Output: \'" + s + "' does not contain: " 479 + Arrays.stream(hasThese) 480 .filter(has -> !s.contains(has)) 481 .collect(Collectors.joining(", ")))); 482 } 483 484 public void assertCommandOutputStartsWith(boolean after, String cmd, String starts) { 485 assertCommandCheckOutput(after, cmd, assertStartsWith(starts)); 486 } 487 488 public void assertCommandCheckOutput(boolean after, String cmd, Consumer<String> check) { 489 if (!after) { 490 assertCommand(false, cmd, null); 491 } else { 492 String got = getCommandOutput(); 493 check.accept(got); 494 assertCommand(true, cmd, null); 495 } 496 } 497 498 public void assertCommand(boolean after, String cmd, String out, String err, 499 String userinput, String print, String usererr) { 500 if (!after) { 501 if (userinput != null) { 502 setUserInput(userinput); 503 } 504 if (cmd.endsWith("\u0003")) { 505 setCommandInput(cmd); 506 } else { 507 setCommandInput(cmd + "\n"); 508 } 509 } else { 510 assertOutput(getCommandOutput().trim(), out==null? out : out.trim(), "command output: " + cmd); 511 assertOutput(getCommandErrorOutput(), err, "command error: " + cmd); 512 assertOutput(getUserOutput(), print, "user output: " + cmd); 513 assertOutput(getUserErrorOutput(), usererr, "user error: " + cmd); 514 } 515 } 516 517 public Consumer<String> assertStartsWith(String prefix) { 518 return (output) -> { 519 if (!output.trim().startsWith(prefix)) { 520 int i = 0; 521 } 522 assertTrue(output.trim().startsWith(prefix), "Output: \'" + output + "' does not start with: " + prefix); 523 }; 524 } 525 526 public void assertOutput(String got, String expected, String display) { 527 if (expected != null) { 528 assertEquals(got, expected, display + ".\n"); 529 } 530 } 531 532 private String normalizeLineEndings(String text) { 533 return ANSI_CODE_PATTERN.matcher(text.replace(System.getProperty("line.separator"), "\n")).replaceAll(""); 534 } 535 private static final Pattern ANSI_CODE_PATTERN = Pattern.compile("\033\\[[\060-\077]*[\040-\057]*[\100-\176]"); 536 537 public static abstract class MemberInfo { 538 public final String source; 539 public final String type; 540 public final String name; 541 542 public MemberInfo(String source, String type, String name) { 543 this.source = source; 544 this.type = type; 545 this.name = name; 546 } 547 548 @Override 549 public int hashCode() { 550 return name.hashCode(); 551 } 552 553 @Override 554 public boolean equals(Object o) { 555 if (o instanceof MemberInfo) { 556 MemberInfo mi = (MemberInfo) o; 557 return name.equals(mi.name); 558 } 559 return false; 560 } 561 562 public abstract Consumer<String> checkOutput(); 563 564 public String getSource() { 565 return source; 566 } 567 } 568 569 public static class VariableInfo extends MemberInfo { 570 571 public final String value; 572 public final String initialValue; 573 574 public VariableInfo(String src, String type, String name) { 575 super(src, type, name); 576 this.initialValue = null; 577 switch (type) { 578 case "byte": 579 case "short": 580 case "int": 581 case "long": 582 value = "0"; 583 break; 584 case "boolean": 585 value = "false"; 586 break; 587 case "char": 588 value = "''"; 589 break; 590 case "float": 591 case "double": 592 value = "0.0"; 593 break; 594 default: 595 value = "null"; 596 } 597 } 598 599 public VariableInfo(String src, String type, String name, String value) { 600 super(src, type, name); 601 this.value = value; 602 this.initialValue = value; 603 } 604 605 @Override 606 public Consumer<String> checkOutput() { 607 String arrowPattern = String.format("%s ==> %s", name, value); 608 Predicate<String> arrowCheckOutput = Pattern.compile(arrowPattern).asPredicate(); 609 String howeverPattern = String.format("\\| *\\w+ variable %s, however*.", name); 610 Predicate<String> howeverCheckOutput = Pattern.compile(howeverPattern).asPredicate(); 611 return output -> { 612 if (output.startsWith("| ")) { 613 assertTrue(howeverCheckOutput.test(output), 614 "Output: " + output + " does not fit pattern: " + howeverPattern); 615 } else { 616 assertTrue(arrowCheckOutput.test(output), 617 "Output: " + output + " does not fit pattern: " + arrowPattern); 618 } 619 }; 620 } 621 622 @Override 623 public int hashCode() { 624 return name.hashCode(); 625 } 626 627 @Override 628 public boolean equals(Object o) { 629 if (o instanceof VariableInfo) { 630 VariableInfo v = (VariableInfo) o; 631 return name.equals(v.name); 632 } 633 return false; 634 } 635 636 @Override 637 public String toString() { 638 return String.format("%s %s = %s", type, name, value); 639 } 640 641 @Override 642 public String getSource() { 643 String src = super.getSource(); 644 return src.endsWith(";") ? src : src + ";"; 645 } 646 } 647 648 public static class TempVariableInfo extends VariableInfo { 649 650 public TempVariableInfo(String src, String type, String name, String value) { 651 super(src, type, name, value); 652 } 653 654 @Override 655 public String getSource() { 656 return source; 657 } 658 } 659 660 public static class MethodInfo extends MemberInfo { 661 662 public final String signature; 663 664 public MethodInfo(String source, String signature, String name) { 665 super(source, signature.substring(0, signature.lastIndexOf(')') + 1), name); 666 this.signature = signature; 667 } 668 669 @Override 670 public Consumer<String> checkOutput() { 671 String expectedOutput = String.format("\\| *\\w+ method %s", name); 672 Predicate<String> checkOutput = Pattern.compile(expectedOutput).asPredicate(); 673 return s -> assertTrue(checkOutput.test(s), "Expected: '" + expectedOutput + "', actual: " + s); 674 } 675 676 @Override 677 public int hashCode() { 678 return (name.hashCode() << 2) ^ type.hashCode() ; 679 } 680 681 @Override 682 public boolean equals(Object o) { 683 if (o instanceof MemberInfo) { 684 MemberInfo m = (MemberInfo) o; 685 return name.equals(m.name) && type.equals(m.type); 686 } 687 return false; 688 } 689 690 @Override 691 public String toString() { 692 int i = signature.lastIndexOf(")") + 1; 693 if (i <= 0) { 694 return String.format("%s", name); 695 } else { 696 return String.format("%s %s%s", signature.substring(i), name, signature.substring(0, i)); 697 } 698 } 699 } 700 701 public static class ClassInfo extends MemberInfo { 702 703 public ClassInfo(String source, String type, String name) { 704 super(source, type, name); 705 } 706 707 @Override 708 public Consumer<String> checkOutput() { 709 String fullType = type.equals("@interface")? "annotation interface" : type; 710 String expectedOutput = String.format("\\| *\\w+ %s %s", fullType, name); 711 Predicate<String> checkOutput = Pattern.compile(expectedOutput).asPredicate(); 712 return s -> assertTrue(checkOutput.test(s), "Expected: '" + expectedOutput + "', actual: " + s); 713 } 714 715 @Override 716 public int hashCode() { 717 return name.hashCode() ; 718 } 719 720 @Override 721 public boolean equals(Object o) { 722 if (o instanceof ClassInfo) { 723 ClassInfo c = (ClassInfo) o; 724 return name.equals(c.name); 725 } 726 return false; 727 } 728 729 @Override 730 public String toString() { 731 return String.format("%s %s", type, name); 732 } 733 } 734 735 public static class ImportInfo extends MemberInfo { 736 public ImportInfo(String source, String type, String fullname) { 737 super(source, type, fullname); 738 } 739 740 @Override 741 public Consumer<String> checkOutput() { 742 return s -> assertTrue("".equals(s), "Expected: '', actual: " + s); 743 } 744 745 @Override 746 public int hashCode() { 747 return (name.hashCode() << 2) ^ type.hashCode() ; 748 } 749 750 @Override 751 public boolean equals(Object o) { 752 if (o instanceof ImportInfo) { 753 ImportInfo i = (ImportInfo) o; 754 return name.equals(i.name) && type.equals(i.type); 755 } 756 return false; 757 } 758 759 @Override 760 public String toString() { 761 return String.format("import %s%s", type.equals("static") ? "static " : "", name); 762 } 763 } 764 765 class WaitingTestingInputStream extends TestingInputStream { 766 767 private boolean closed; 768 769 @Override 770 synchronized void setInput(String s) { 771 super.setInput(s); 772 notify(); 773 } 774 775 synchronized void waitForInput() { 776 boolean interrupted = false; 777 try { 778 while (available() == 0 && !closed) { 779 try { 780 wait(); 781 } catch (InterruptedException e) { 782 interrupted = true; 783 // fall through and retry 784 } 785 } 786 } finally { 787 if (interrupted) { 788 Thread.currentThread().interrupt(); 789 } 790 } 791 } 792 793 @Override 794 public int read() { 795 waitForInput(); 796 return super.read(); 797 } 798 799 @Override 800 public int read(byte b[], int off, int len) { 801 waitForInput(); 802 return super.read(b, off, len); 803 } 804 805 @Override 806 public synchronized void close() throws IOException { 807 closed = true; 808 notify(); 809 } 810 } 811 812 class PromptedCommandOutputStream extends OutputStream { 813 private final ReplTest[] tests; 814 private int index = 0; 815 PromptedCommandOutputStream(ReplTest[] tests) { 816 this.tests = tests; 817 } 818 819 @Override 820 public synchronized void write(int b) { 821 if (b == 5 || b == 6) { 822 if (index < (tests.length - 1)) { 823 tests[index].run(true); 824 tests[index + 1].run(false); 825 } else { 826 fail("Did not exit Repl tool after test"); 827 } 828 ++index; 829 } // For now, anything else is thrown away 830 } 831 832 @Override 833 public synchronized void write(byte b[], int off, int len) { 834 if ((off < 0) || (off > b.length) || (len < 0) 835 || ((off + len) - b.length > 0)) { 836 throw new IndexOutOfBoundsException(); 837 } 838 for (int i = 0; i < len; ++i) { 839 write(b[off + i]); 840 } 841 } 842 } 843 844 public static final class MemoryPreferences extends AbstractPreferences { 845 846 private final Map<String, String> values = new HashMap<>(); 847 private final Map<String, MemoryPreferences> nodes = new HashMap<>(); 848 849 public MemoryPreferences() { 850 this(null, ""); 851 } 852 853 public MemoryPreferences(MemoryPreferences parent, String name) { 854 super(parent, name); 855 } 856 857 @Override 858 protected void putSpi(String key, String value) { 859 values.put(key, value); 860 } 861 862 @Override 863 protected String getSpi(String key) { 864 return values.get(key); 865 } 866 867 @Override 868 protected void removeSpi(String key) { 869 values.remove(key); 870 } 871 872 @Override 873 protected void removeNodeSpi() throws BackingStoreException { 874 ((MemoryPreferences) parent()).nodes.remove(name()); 875 } 876 877 @Override 878 protected String[] keysSpi() throws BackingStoreException { 879 return values.keySet().toArray(new String[0]); 880 } 881 882 @Override 883 protected String[] childrenNamesSpi() throws BackingStoreException { 884 return nodes.keySet().toArray(new String[0]); 885 } 886 887 @Override 888 protected AbstractPreferences childSpi(String name) { 889 return nodes.computeIfAbsent(name, n -> new MemoryPreferences(this, name)); 890 } 891 892 @Override 893 protected void syncSpi() throws BackingStoreException { 894 } 895 896 @Override 897 protected void flushSpi() throws BackingStoreException { 898 } 899 900 } 901 } 902