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