| /* | |
| * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| import java.io.ByteArrayOutputStream; | |
| import java.io.IOException; | |
| import java.io.OutputStream; | |
| import java.io.PrintStream; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collections; | |
| import java.util.HashMap; | |
| import java.util.List; | |
| import java.util.Locale; | |
| import java.util.Map; | |
| import java.util.function.Consumer; | |
| import java.util.function.Function; | |
| import java.util.function.Predicate; | |
| import java.util.logging.Level; | |
| import java.util.logging.Logger; | |
| import java.util.prefs.AbstractPreferences; | |
| import java.util.prefs.BackingStoreException; | |
| import java.util.regex.Matcher; | |
| import java.util.regex.Pattern; | |
| import java.util.stream.Collectors; | |
| import java.util.stream.Stream; | |
| import org.testng.annotations.BeforeMethod; | |
| import jdk.jshell.tool.JavaShellToolBuilder; | |
| import static java.util.stream.Collectors.toList; | |
| import static org.testng.Assert.assertEquals; | |
| import static org.testng.Assert.assertNotNull; | |
| import static org.testng.Assert.assertTrue; | |
| import static org.testng.Assert.fail; | |
| public class ReplToolTesting { | |
| private final static String DEFAULT_STARTUP_MESSAGE = "| Welcome to"; | |
| final static List<ImportInfo> START_UP_IMPORTS = List.of( | |
| new ImportInfo("import module java.base;", "", "java.base")); | |
| final static List<MethodInfo> START_UP_METHODS = Stream.<MethodInfo>of() | |
| .collect(toList()); | |
| final static List<String> START_UP_CMD_METHOD = Stream.<String>of() | |
| .collect(toList()); | |
| final static List<String> PRINTING_CMD_METHOD = Stream.of( | |
| "| void print(boolean)", | |
| "| void print(char)", | |
| "| void print(int)", | |
| "| void print(long)", | |
| "| void print(float)", | |
| "| void print(double)", | |
| "| void print(char s[])", | |
| "| void print(String)", | |
| "| void print(Object)", | |
| "| void println()", | |
| "| void println(boolean)", | |
| "| void println(char)", | |
| "| void println(int)", | |
| "| void println(long)", | |
| "| void println(float)", | |
| "| void println(double)", | |
| "| void println(char s[])", | |
| "| void println(String)", | |
| "| void println(Object)", | |
| "| void printf(java.util.Locale,String,Object...)", | |
| "| void printf(String,Object...)") | |
| .collect(toList()); | |
| final static List<String> START_UP = Collections.unmodifiableList( | |
| Stream.concat(START_UP_IMPORTS.stream(), START_UP_METHODS.stream()) | |
| .map(s -> s.getSource()) | |
| .collect(toList())); | |
| private WaitingTestingInputStream cmdin = null; | |
| private ByteArrayOutputStream cmdout = null; | |
| private ByteArrayOutputStream cmderr = null; | |
| private PromptedCommandOutputStream console = null; | |
| private TestingInputStream userin = null; | |
| private ByteArrayOutputStream userout = null; | |
| private ByteArrayOutputStream usererr = null; | |
| private List<MemberInfo> keys; | |
| private Map<String, VariableInfo> variables; | |
| private Map<String, MethodInfo> methods; | |
| private Map<String, ClassInfo> classes; | |
| private Map<String, ImportInfo> imports; | |
| private boolean isDefaultStartUp = true; | |
| protected Map<String, String> prefsMap; | |
| private Map<String, String> envvars; | |
| public interface ReplTest { | |
| void run(boolean after); | |
| } | |
| public void setCommandInput(String s) { | |
| cmdin.setInput(s); | |
| } | |
| public void closeCommandInput() { | |
| try { | |
| cmdin.close(); | |
| } catch (IOException ex) { | |
| throw new IllegalStateException(ex); | |
| } | |
| } | |
| public final static Pattern idPattern = Pattern.compile("^\\s+(\\d+)"); | |
| public Consumer<String> assertList() { | |
| return s -> { | |
| List<String> lines = Stream.of(s.split("\n")) | |
| .filter(l -> !l.isEmpty()) | |
| .collect(Collectors.toList()); | |
| int previousId = Integer.MIN_VALUE; | |
| assertEquals(lines.size(), keys.size(), "Number of keys"); | |
| for (int i = 0; i < lines.size(); ++i) { | |
| String line = lines.get(i); | |
| Matcher matcher = idPattern.matcher(line); | |
| assertTrue(matcher.find(), "Snippet id not found: " + line); | |
| String src = keys.get(i).getSource(); | |
| assertTrue(line.endsWith(src), "Line '" + line + "' does not end with: " + src); | |
| int id = Integer.parseInt(matcher.group(1)); | |
| assertTrue(previousId < id, | |
| String.format("The previous id is not less than the next one: previous: %d, next: %d", | |
| previousId, id)); | |
| previousId = id; | |
| } | |
| }; | |
| } | |
| private final static Pattern extractPattern = Pattern.compile("^\\| *(.*)$"); | |
| private Consumer<String> assertMembers(String message, Map<String, ? extends MemberInfo> set) { | |
| return s -> { | |
| List<String> lines = Stream.of(s.split("\n")) | |
| .filter(l -> !l.isEmpty()) | |
| .filter(l -> !l.startsWith("| ")) // error/unresolved info | |
| .collect(Collectors.toList()); | |
| assertEquals(lines.size(), set.size(), message + " : expected: " + set.keySet() + "\ngot:\n" + lines); | |
| for (String line : lines) { | |
| Matcher matcher = extractPattern.matcher(line); | |
| assertTrue(matcher.find(), line); | |
| String src = matcher.group(1); | |
| MemberInfo info = set.get(src); | |
| assertNotNull(info, "Not found snippet with signature: " + src + ", line: " | |
| + line + ", keys: " + set.keySet() + "\n"); | |
| } | |
| }; | |
| } | |
| public Consumer<String> assertVariables() { | |
| return assertMembers("Variables", variables); | |
| } | |
| public Consumer<String> assertMethods() { | |
| return assertMembers("Methods", methods); | |
| } | |
| public Consumer<String> assertClasses() { | |
| return assertMembers("Classes", classes); | |
| } | |
| public Consumer<String> assertImports() { | |
| return assertMembers("Imports", imports); | |
| } | |
| public String getCommandOutput() { | |
| String s = normalizeLineEndings(cmdout.toString()); | |
| cmdout.reset(); | |
| return s; | |
| } | |
| public String getCommandErrorOutput() { | |
| String s = normalizeLineEndings(cmderr.toString()); | |
| cmderr.reset(); | |
| return s; | |
| } | |
| public void setUserInput(String s) { | |
| userin.setInput(s); | |
| } | |
| public String getUserOutput() { | |
| String s = normalizeLineEndings(userout.toString()); | |
| userout.reset(); | |
| return s; | |
| } | |
| public String getUserErrorOutput() { | |
| String s = normalizeLineEndings(usererr.toString()); | |
| usererr.reset(); | |
| return s; | |
| } | |
| public String getTerminalOutput() { | |
| String s = normalizeLineEndings("\r\n", console.data.toString()); | |
| console.data.reset(); | |
| return s; | |
| } | |
| public void test(ReplTest... tests) { | |
| test(new String[0], tests); | |
| } | |
| public void test(String[] args, ReplTest... tests) { | |
| test(true, args, tests); | |
| } | |
| public void test(String[] args, String expectedErrorOutput, | |
| ReplTest... tests) { | |
| test(Locale.ROOT, true, args, expectedErrorOutput, DEFAULT_STARTUP_MESSAGE, tests); | |
| } | |
| public void test(boolean isDefaultStartUp, String[] args, ReplTest... tests) { | |
| test(Locale.ROOT, isDefaultStartUp, args, DEFAULT_STARTUP_MESSAGE, tests); | |
| } | |
| public void testNoStartUp(ReplTest... tests) { | |
| test(Locale.ROOT, false, new String[] {"--no-startup"}, DEFAULT_STARTUP_MESSAGE, tests); | |
| } | |
| public void test(Locale locale, boolean isDefaultStartUp, String[] args, String startUpMessage, ReplTest... tests) { | |
| test(locale, isDefaultStartUp, args, "", startUpMessage, tests); | |
| } | |
| public void test(Locale locale, boolean isDefaultStartUp, String[] args, | |
| String expectedErrorOutput, String startUpMessage, ReplTest... tests) { | |
| this.isDefaultStartUp = isDefaultStartUp; | |
| initSnippets(); | |
| ReplTest[] wtests = new ReplTest[tests.length + 3]; | |
| wtests[0] = a -> assertCommandCheckOutput(a, "<start>", | |
| s -> assertTrue(s.startsWith(startUpMessage), "Expected start-up message '" + startUpMessage + "' Got: " + s)); | |
| wtests[1] = a -> assertCommand(a, "/debug 0", null); | |
| System.arraycopy(tests, 0, wtests, 2, tests.length); | |
| wtests[tests.length + 2] = a -> assertCommand(a, "/exit", null); | |
| testRaw(locale, args, expectedErrorOutput, wtests); | |
| } | |
| private void initSnippets() { | |
| keys = new ArrayList<>(); | |
| variables = new HashMap<>(); | |
| methods = new HashMap<>(); | |
| classes = new HashMap<>(); | |
| imports = new HashMap<>(); | |
| if (isDefaultStartUp) { | |
| methods.putAll( | |
| START_UP_METHODS.stream() | |
| .collect(Collectors.toMap(Object::toString, Function.identity()))); | |
| imports.putAll( | |
| START_UP_IMPORTS.stream() | |
| .collect(Collectors.toMap(Object::toString, Function.identity()))); | |
| } | |
| } | |
| @BeforeMethod | |
| public void setUp() { | |
| prefsMap = new HashMap<>(); | |
| prefsMap.put("INDENT", "0"); | |
| envvars = new HashMap<>(); | |
| System.setProperty("jshell.test.allow.incomplete.inputs", "true"); | |
| } | |
| protected void setEnvVar(String name, String value) { | |
| envvars.put(name, value); | |
| } | |
| protected JavaShellToolBuilder builder(Locale locale) { | |
| // turn on logging of launch failures | |
| Logger.getLogger("jdk.jshell.execution").setLevel(Level.ALL); | |
| return JavaShellToolBuilder | |
| .builder() | |
| .in(cmdin, userin) | |
| .out(new PrintStream(cmdout), new PrintStream(console), new PrintStream(userout)) | |
| .err(new PrintStream(cmderr), new PrintStream(usererr)) | |
| .persistence(prefsMap) | |
| .env(envvars) | |
| .locale(locale) | |
| .promptCapture(true); | |
| } | |
| private void testRaw(Locale locale, String[] args, | |
| String expectedErrorOutput, ReplTest... tests) { | |
| testRawInit(tests); | |
| testRawRun(locale, Presets.addExecutionIfMissing(args)); | |
| testRawCheck(locale, expectedErrorOutput); | |
| } | |
| private void testRawInit(ReplTest... tests) { | |
| cmdin = new WaitingTestingInputStream(); | |
| cmdout = new ByteArrayOutputStream(); | |
| cmderr = new ByteArrayOutputStream(); | |
| console = new PromptedCommandOutputStream(tests); | |
| userin = new TestingInputStream(); | |
| userout = new ByteArrayOutputStream(); | |
| usererr = new ByteArrayOutputStream(); | |
| } | |
| protected void testRawRun(Locale locale, String[] args) { | |
| try { | |
| builder(locale) | |
| .run(args); | |
| } catch (Exception ex) { | |
| fail("Repl tool died with exception", ex); | |
| } | |
| } | |
| private void testRawCheck(Locale locale, String expectedErrorOutput) { | |
| // perform internal consistency checks on state, if desired | |
| String cos = getCommandOutput(); | |
| String ceos = getCommandErrorOutput(); | |
| String uos = getUserOutput(); | |
| String ueos = getUserErrorOutput(); | |
| assertTrue((cos.isEmpty() || cos.startsWith("| Goodbye") || !locale.equals(Locale.ROOT)), | |
| "Expected a goodbye, but got: " + cos); | |
| assertEquals(ceos, | |
| expectedErrorOutput, | |
| "Expected \"" + expectedErrorOutput + | |
| "\" command error output, got: \"" + ceos + "\""); | |
| assertTrue(uos.isEmpty(), "Expected empty user output, got: " + uos); | |
| assertTrue(ueos.isEmpty(), "Expected empty user error output, got: " + ueos); | |
| } | |
| public void assertReset(boolean after, String cmd) { | |
| assertCommand(after, cmd, "| Resetting state.\n"); | |
| initSnippets(); | |
| } | |
| public void evaluateExpression(boolean after, String type, String expr, String value) { | |
| String output = String.format("(\\$\\d+) ==> %s", value); | |
| Pattern outputPattern = Pattern.compile(output); | |
| assertCommandCheckOutput(after, expr, s -> { | |
| Matcher matcher = outputPattern.matcher(s); | |
| assertTrue(matcher.find(), "Output: '" + s + "' does not fit pattern: '" + output + "'"); | |
| String name = matcher.group(1); | |
| VariableInfo tempVar = new TempVariableInfo(expr, type, name, value); | |
| variables.put(tempVar.toString(), tempVar); | |
| addKey(after, tempVar); | |
| }); | |
| } | |
| public void loadVariable(boolean after, String type, String name) { | |
| loadVariable(after, type, name, null, null); | |
| } | |
| public void loadVariable(boolean after, String type, String name, String expr, String value) { | |
| String src = expr == null | |
| ? String.format("%s %s", type, name) | |
| : String.format("%s %s = %s", type, name, expr); | |
| VariableInfo var = expr == null | |
| ? new VariableInfo(src, type, name) | |
| : new VariableInfo(src, type, name, value); | |
| addKey(after, var, variables); | |
| addKey(after, var); | |
| } | |
| public void assertVariable(boolean after, String type, String name) { | |
| assertVariable(after, type, name, null, null); | |
| } | |
| public void assertVariable(boolean after, String type, String name, String expr, String value) { | |
| String src = expr == null | |
| ? String.format("%s %s", type, name) | |
| : String.format("%s %s = %s", type, name, expr); | |
| VariableInfo var = expr == null | |
| ? new VariableInfo(src, type, name) | |
| : new VariableInfo(src, type, name, value); | |
| assertCommandCheckOutput(after, src, var.checkOutput()); | |
| addKey(after, var, variables); | |
| addKey(after, var); | |
| } | |
| public void loadMethod(boolean after, String src, String signature, String name) { | |
| MethodInfo method = new MethodInfo(src, signature, name); | |
| addKey(after, method, methods); | |
| addKey(after, method); | |
| } | |
| public void assertMethod(boolean after, String src, String signature, String name) { | |
| MethodInfo method = new MethodInfo(src, signature, name); | |
| assertCommandCheckOutput(after, src, method.checkOutput()); | |
| addKey(after, method, methods); | |
| addKey(after, method); | |
| } | |
| public void loadClass(boolean after, String src, String type, String name) { | |
| ClassInfo clazz = new ClassInfo(src, type, name); | |
| addKey(after, clazz, classes); | |
| addKey(after, clazz); | |
| } | |
| public void assertClass(boolean after, String src, String type, String name) { | |
| ClassInfo clazz = new ClassInfo(src, type, name); | |
| assertCommandCheckOutput(after, src, clazz.checkOutput()); | |
| addKey(after, clazz, classes); | |
| addKey(after, clazz); | |
| } | |
| public void loadImport(boolean after, String src, String type, String name) { | |
| ImportInfo i = new ImportInfo(src, type, name); | |
| addKey(after, i, imports); | |
| addKey(after, i); | |
| } | |
| public void assertImport(boolean after, String src, String type, String name) { | |
| ImportInfo i = new ImportInfo(src, type, name); | |
| assertCommandCheckOutput(after, src, i.checkOutput()); | |
| addKey(after, i, imports); | |
| addKey(after, i); | |
| } | |
| private <T extends MemberInfo> void addKey(boolean after, T memberInfo, Map<String, T> map) { | |
| if (after) { | |
| map.entrySet().removeIf(e -> e.getValue().equals(memberInfo)); | |
| map.put(memberInfo.toString(), memberInfo); | |
| } | |
| } | |
| private <T extends MemberInfo> void addKey(boolean after, T memberInfo) { | |
| if (after) { | |
| for (int i = 0; i < keys.size(); ++i) { | |
| MemberInfo m = keys.get(i); | |
| if (m.equals(memberInfo)) { | |
| keys.set(i, memberInfo); | |
| return; | |
| } | |
| } | |
| keys.add(memberInfo); | |
| } | |
| } | |
| private void dropKey(boolean after, String cmd, String name, Map<String, ? extends MemberInfo> map, String output) { | |
| assertCommand(after, cmd, output); | |
| if (after) { | |
| map.remove(name); | |
| for (int i = 0; i < keys.size(); ++i) { | |
| MemberInfo m = keys.get(i); | |
| if (m.toString().equals(name)) { | |
| keys.remove(i); | |
| return; | |
| } | |
| } | |
| throw new AssertionError("Key not found: " + name + ", keys: " + keys); | |
| } | |
| } | |
| public void dropVariable(boolean after, String cmd, String name, String output) { | |
| dropKey(after, cmd, name, variables, output); | |
| } | |
| public void dropMethod(boolean after, String cmd, String name, String output) { | |
| dropKey(after, cmd, name, methods, output); | |
| } | |
| public void dropClass(boolean after, String cmd, String name, String output) { | |
| dropKey(after, cmd, name, classes, output); | |
| } | |
| public void dropImport(boolean after, String cmd, String name, String output) { | |
| dropKey(after, cmd, name, imports, output); | |
| } | |
| public void assertCommand(boolean after, String cmd, String out) { | |
| assertCommand(after, cmd, out, "", null, "", ""); | |
| } | |
| public void assertCommandOutputContains(boolean after, String cmd, String... hasThese) { | |
| assertCommandCheckOutput(after, cmd, (s) | |
| -> assertTrue(Arrays.stream(hasThese) | |
| .allMatch(has -> s.contains(has)), | |
| "Output: \'" + s + "' does not contain: " | |
| + Arrays.stream(hasThese) | |
| .filter(has -> !s.contains(has)) | |
| .collect(Collectors.joining(", ")))); | |
| } | |
| public void assertCommandOutputStartsWith(boolean after, String cmd, String starts) { | |
| assertCommandCheckOutput(after, cmd, assertStartsWith(starts)); | |
| } | |
| public void assertCommandCheckOutput(boolean after, String cmd, Consumer<String> check) { | |
| if (!after) { | |
| assertCommand(false, cmd, null); | |
| } else { | |
| String got = getCommandOutput(); | |
| check.accept(got); | |
| assertCommand(true, cmd, null); | |
| } | |
| } | |
| public void assertCommandUserOutputContains(boolean after, String cmd, String... hasThese) { | |
| assertCommandCheckUserOutput(after, cmd, (s) | |
| -> assertTrue(Arrays.stream(hasThese) | |
| .allMatch(has -> s.contains(has)), | |
| "User output: \'" + s + "' does not contain: " | |
| + Arrays.stream(hasThese) | |
| .filter(has -> !s.contains(has)) | |
| .collect(Collectors.joining(", ")))); | |
| } | |
| public void assertCommandCheckUserOutput(boolean after, String cmd, Consumer<String> check) { | |
| if (!after) { | |
| assertCommand(false, cmd, null); | |
| } else { | |
| String got = getUserOutput(); | |
| check.accept(got); | |
| assertCommand(true, cmd, null); | |
| } | |
| } | |
| public void assertCommand(boolean after, String cmd, String out, String err, | |
| String userinput, String print, String usererr) { | |
| assertCommand(after, cmd, out, err, userinput, print, usererr, null); | |
| } | |
| public void assertCommand(boolean after, String cmd, String out, String err, | |
| String userinput, String print, String usererr, String terminalOut) { | |
| if (!after) { | |
| if (userinput != null) { | |
| setUserInput(userinput); | |
| } | |
| if (cmd.endsWith("\u0003")) { | |
| setCommandInput(cmd); | |
| } else { | |
| setCommandInput(cmd + "\n"); | |
| } | |
| } else { | |
| assertOutput(getCommandOutput().trim(), out==null? out : out.trim(), "command output: " + cmd); | |
| assertOutput(getCommandErrorOutput(), err, "command error: " + cmd); | |
| assertOutput(getUserOutput(), print, "user output: " + cmd); | |
| assertOutput(getUserErrorOutput(), usererr, "user error: " + cmd); | |
| assertOutput(getTerminalOutput(), terminalOut, "terminal output: " + cmd); | |
| } | |
| } | |
| public Consumer<String> assertStartsWith(String prefix) { | |
| return (output) -> { | |
| if (!output.trim().startsWith(prefix)) { | |
| int i = 0; | |
| } | |
| assertTrue(output.trim().startsWith(prefix), "Output: \'" + output + "' does not start with: " + prefix); | |
| }; | |
| } | |
| public void assertOutput(String got, String expected, String display) { | |
| if (expected != null) { | |
| assertEquals(got, expected, display + ".\n"); | |
| } | |
| } | |
| private String normalizeLineEndings(String text) { | |
| return normalizeLineEndings(System.getProperty("line.separator"), text); | |
| } | |
| private String normalizeLineEndings(String lineSeparator, String text) { | |
| return ANSI_CODE_PATTERN.matcher(text.replace(lineSeparator, "\n")).replaceAll(""); | |
| } | |
| private static final Pattern ANSI_CODE_PATTERN = Pattern.compile("\033\\[[\060-\077]*[\040-\057]*[\100-\176]"); | |
| public static abstract class MemberInfo { | |
| public final String source; | |
| public final String type; | |
| public final String name; | |
| public MemberInfo(String source, String type, String name) { | |
| this.source = source; | |
| this.type = type; | |
| this.name = name; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return name.hashCode(); | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (o instanceof MemberInfo) { | |
| MemberInfo mi = (MemberInfo) o; | |
| return name.equals(mi.name); | |
| } | |
| return false; | |
| } | |
| public abstract Consumer<String> checkOutput(); | |
| public String getSource() { | |
| return source; | |
| } | |
| } | |
| public static class VariableInfo extends MemberInfo { | |
| public final String value; | |
| public final String initialValue; | |
| public VariableInfo(String src, String type, String name) { | |
| super(src, type, name); | |
| this.initialValue = null; | |
| switch (type) { | |
| case "byte": | |
| case "short": | |
| case "int": | |
| case "long": | |
| value = "0"; | |
| break; | |
| case "boolean": | |
| value = "false"; | |
| break; | |
| case "char": | |
| value = "''"; | |
| break; | |
| case "float": | |
| case "double": | |
| value = "0.0"; | |
| break; | |
| default: | |
| value = "null"; | |
| } | |
| } | |
| public VariableInfo(String src, String type, String name, String value) { | |
| super(src, type, name); | |
| this.value = value; | |
| this.initialValue = value; | |
| } | |
| @Override | |
| public Consumer<String> checkOutput() { | |
| String arrowPattern = String.format("%s ==> %s", name, value); | |
| Predicate<String> arrowCheckOutput = Pattern.compile(arrowPattern).asPredicate(); | |
| String howeverPattern = String.format("\\| *\\w+ variable %s, however*.", name); | |
| Predicate<String> howeverCheckOutput = Pattern.compile(howeverPattern).asPredicate(); | |
| return output -> { | |
| if (output.startsWith("| ")) { | |
| assertTrue(howeverCheckOutput.test(output), | |
| "Output: " + output + " does not fit pattern: " + howeverPattern); | |
| } else { | |
| assertTrue(arrowCheckOutput.test(output), | |
| "Output: " + output + " does not fit pattern: " + arrowPattern); | |
| } | |
| }; | |
| } | |
| @Override | |
| public int hashCode() { | |
| return name.hashCode(); | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (o instanceof VariableInfo) { | |
| VariableInfo v = (VariableInfo) o; | |
| return name.equals(v.name); | |
| } | |
| return false; | |
| } | |
| @Override | |
| public String toString() { | |
| return String.format("%s %s = %s", type, name, value); | |
| } | |
| @Override | |
| public String getSource() { | |
| String src = super.getSource(); | |
| return src.endsWith(";") ? src : src + ";"; | |
| } | |
| } | |
| public static class TempVariableInfo extends VariableInfo { | |
| public TempVariableInfo(String src, String type, String name, String value) { | |
| super(src, type, name, value); | |
| } | |
| @Override | |
| public String getSource() { | |
| return source; | |
| } | |
| } | |
| public static class MethodInfo extends MemberInfo { | |
| public final String signature; | |
| public MethodInfo(String source, String signature, String name) { | |
| super(source, signature.substring(0, signature.lastIndexOf(')') + 1), name); | |
| this.signature = signature; | |
| } | |
| @Override | |
| public Consumer<String> checkOutput() { | |
| String expectedOutput = String.format("\\| *\\w+ method %s", name); | |
| Predicate<String> checkOutput = Pattern.compile(expectedOutput).asPredicate(); | |
| return s -> assertTrue(checkOutput.test(s), "Expected: '" + expectedOutput + "', actual: " + s); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return (name.hashCode() << 2) ^ type.hashCode() ; | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (o instanceof MemberInfo) { | |
| MemberInfo m = (MemberInfo) o; | |
| return name.equals(m.name) && type.equals(m.type); | |
| } | |
| return false; | |
| } | |
| @Override | |
| public String toString() { | |
| int i = signature.lastIndexOf(")") + 1; | |
| if (i <= 0) { | |
| return String.format("%s", name); | |
| } else { | |
| return String.format("%s %s%s", signature.substring(i), name, signature.substring(0, i)); | |
| } | |
| } | |
| } | |
| public static class ClassInfo extends MemberInfo { | |
| public ClassInfo(String source, String type, String name) { | |
| super(source, type, name); | |
| } | |
| @Override | |
| public Consumer<String> checkOutput() { | |
| String fullType = type.equals("@interface")? "annotation interface" : type; | |
| String expectedOutput = String.format("\\| *\\w+ %s %s", fullType, name); | |
| Predicate<String> checkOutput = Pattern.compile(expectedOutput).asPredicate(); | |
| return s -> assertTrue(checkOutput.test(s), "Expected: '" + expectedOutput + "', actual: " + s); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return name.hashCode() ; | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (o instanceof ClassInfo) { | |
| ClassInfo c = (ClassInfo) o; | |
| return name.equals(c.name); | |
| } | |
| return false; | |
| } | |
| @Override | |
| public String toString() { | |
| return String.format("%s %s", type, name); | |
| } | |
| } | |
| public static class ImportInfo extends MemberInfo { | |
| public ImportInfo(String source, String type, String fullname) { | |
| super(source, type, fullname); | |
| } | |
| @Override | |
| public Consumer<String> checkOutput() { | |
| return s -> assertTrue("".equals(s), "Expected: '', actual: " + s); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return (name.hashCode() << 2) ^ type.hashCode() ; | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (o instanceof ImportInfo) { | |
| ImportInfo i = (ImportInfo) o; | |
| return name.equals(i.name) && type.equals(i.type); | |
| } | |
| return false; | |
| } | |
| @Override | |
| public String toString() { | |
| return String.format("import %s%s", type.equals("static") ? "static " : "", name); | |
| } | |
| } | |
| class WaitingTestingInputStream extends TestingInputStream { | |
| private boolean closed; | |
| @Override | |
| synchronized void setInput(String s) { | |
| super.setInput(s); | |
| notify(); | |
| } | |
| synchronized void waitForInput() { | |
| boolean interrupted = false; | |
| try { | |
| while (available() == 0 && !closed) { | |
| try { | |
| wait(); | |
| } catch (InterruptedException e) { | |
| interrupted = true; | |
| // fall through and retry | |
| } | |
| } | |
| } finally { | |
| if (interrupted) { | |
| Thread.currentThread().interrupt(); | |
| } | |
| } | |
| } | |
| @Override | |
| public int read() { | |
| waitForInput(); | |
| return super.read(); | |
| } | |
| @Override | |
| public int read(byte b[], int off, int len) { | |
| waitForInput(); | |
| return super.read(b, off, len); | |
| } | |
| @Override | |
| public synchronized void close() throws IOException { | |
| closed = true; | |
| notify(); | |
| } | |
| } | |
| class PromptedCommandOutputStream extends OutputStream { | |
| private final ReplTest[] tests; | |
| private final ByteArrayOutputStream data = new ByteArrayOutputStream(); | |
| private int index = 0; | |
| PromptedCommandOutputStream(ReplTest[] tests) { | |
| this.tests = tests; | |
| } | |
| @Override | |
| public synchronized void write(int b) { | |
| if (b == 5 || b == 6) { | |
| if (index < (tests.length - 1)) { | |
| tests[index].run(true); | |
| tests[index + 1].run(false); | |
| } else { | |
| fail("Did not exit Repl tool after test"); | |
| } | |
| ++index; | |
| } | |
| data.write(b); | |
| } | |
| @Override | |
| public synchronized void write(byte b[], int off, int len) { | |
| if ((off < 0) || (off > b.length) || (len < 0) | |
| || ((off + len) - b.length > 0)) { | |
| throw new IndexOutOfBoundsException(); | |
| } | |
| for (int i = 0; i < len; ++i) { | |
| write(b[off + i]); | |
| } | |
| } | |
| } | |
| public static final class MemoryPreferences extends AbstractPreferences { | |
| private final Map<String, String> values = new HashMap<>(); | |
| private final Map<String, MemoryPreferences> nodes = new HashMap<>(); | |
| public MemoryPreferences() { | |
| this(null, ""); | |
| } | |
| public MemoryPreferences(MemoryPreferences parent, String name) { | |
| super(parent, name); | |
| } | |
| @Override | |
| protected void putSpi(String key, String value) { | |
| values.put(key, value); | |
| } | |
| @Override | |
| protected String getSpi(String key) { | |
| return values.get(key); | |
| } | |
| @Override | |
| protected void removeSpi(String key) { | |
| values.remove(key); | |
| } | |
| @Override | |
| protected void removeNodeSpi() throws BackingStoreException { | |
| ((MemoryPreferences) parent()).nodes.remove(name()); | |
| } | |
| @Override | |
| protected String[] keysSpi() throws BackingStoreException { | |
| return values.keySet().toArray(new String[0]); | |
| } | |
| @Override | |
| protected String[] childrenNamesSpi() throws BackingStoreException { | |
| return nodes.keySet().toArray(new String[0]); | |
| } | |
| @Override | |
| protected AbstractPreferences childSpi(String name) { | |
| return nodes.computeIfAbsent(name, n -> new MemoryPreferences(this, name)); | |
| } | |
| @Override | |
| protected void syncSpi() throws BackingStoreException { | |
| } | |
| @Override | |
| protected void flushSpi() throws BackingStoreException { | |
| } | |
| } | |
| } |