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