ReplToolTesting.java revision 4111:256d9fce6c53
126219Swpaul/*
226219Swpaul * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
326219Swpaul * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
426219Swpaul *
526219Swpaul * This code is free software; you can redistribute it and/or modify it
626219Swpaul * under the terms of the GNU General Public License version 2 only, as
726219Swpaul * published by the Free Software Foundation.
826219Swpaul *
926219Swpaul * This code is distributed in the hope that it will be useful, but WITHOUT
1026219Swpaul * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1126219Swpaul * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1226219Swpaul * version 2 for more details (a copy is included in the LICENSE file that
1326219Swpaul * accompanied this code).
1426219Swpaul *
1526219Swpaul * You should have received a copy of the GNU General Public License version
1626219Swpaul * 2 along with this work; if not, write to the Free Software Foundation,
1726219Swpaul * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1826219Swpaul *
1926219Swpaul * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2026219Swpaul * or visit www.oracle.com if you need additional information or have any
2126219Swpaul * questions.
2226219Swpaul */
2326219Swpaul
2426219Swpaulimport java.io.ByteArrayOutputStream;
2526219Swpaulimport java.io.OutputStream;
2626219Swpaulimport java.io.PrintStream;
2726219Swpaulimport java.util.ArrayList;
2826219Swpaulimport java.util.Arrays;
2926219Swpaulimport java.util.Collections;
3026219Swpaulimport java.util.HashMap;
3155837Sjasoneimport java.util.List;
3255837Sjasoneimport java.util.Locale;
3326219Swpaulimport java.util.Map;
3471579Sdeischenimport java.util.function.Consumer;
3526219Swpaulimport java.util.function.Function;
3626219Swpaulimport java.util.function.Predicate;
3726219Swpaulimport java.util.logging.Level;
3826219Swpaulimport java.util.logging.Logger;
3926219Swpaulimport java.util.prefs.AbstractPreferences;
4026219Swpaulimport java.util.prefs.BackingStoreException;
4126219Swpaulimport java.util.regex.Matcher;
4226219Swpaulimport java.util.regex.Pattern;
4326219Swpaulimport java.util.stream.Collectors;
4426219Swpaulimport java.util.stream.Stream;
4526219Swpaul
4626219Swpaul
4726219Swpaulimport org.testng.annotations.BeforeMethod;
4874462Salfred
4926219Swpaulimport jdk.jshell.tool.JavaShellToolBuilder;
5026219Swpaulimport static java.util.stream.Collectors.toList;
5171579Sdeischenimport static org.testng.Assert.assertEquals;
5226219Swpaulimport static org.testng.Assert.assertNotNull;
5326219Swpaulimport static org.testng.Assert.assertTrue;
5426219Swpaulimport static org.testng.Assert.fail;
5526219Swpaul
5626219Swpaulpublic class ReplToolTesting {
5726219Swpaul
5826219Swpaul    private final static String DEFAULT_STARTUP_MESSAGE = "|  Welcome to";
5926219Swpaul    final static List<ImportInfo> START_UP_IMPORTS = Stream.of(
6026219Swpaul                    "java.io.*",
6126219Swpaul                    "java.math.*",
6226219Swpaul                    "java.net.*",
6326219Swpaul                    "java.nio.file.*",
6426219Swpaul                    "java.util.*",
6526219Swpaul                    "java.util.concurrent.*",
6626219Swpaul                    "java.util.function.*",
6726219Swpaul                    "java.util.prefs.*",
6826219Swpaul                    "java.util.regex.*",
6926219Swpaul                    "java.util.stream.*")
7026219Swpaul                    .map(s -> new ImportInfo("import " + s + ";", "", s))
7126219Swpaul                    .collect(toList());
7226219Swpaul    final static List<MethodInfo> START_UP_METHODS = Stream.<MethodInfo>of()
7326219Swpaul                    .collect(toList());
7426219Swpaul    final static List<String> START_UP_CMD_METHOD = Stream.<String>of()
7526219Swpaul                    .collect(toList());
7626219Swpaul    final static List<String> PRINTING_CMD_METHOD = Stream.of(
7726219Swpaul            "|    void print(boolean)",
7826219Swpaul            "|    void print(char)",
7926219Swpaul            "|    void print(int)",
8026219Swpaul            "|    void print(long)",
8126219Swpaul            "|    void print(float)",
8226219Swpaul            "|    void print(double)",
8326219Swpaul            "|    void print(char s[])",
8426219Swpaul            "|    void print(String)",
8526219Swpaul            "|    void print(Object)",
8626219Swpaul            "|    void println()",
8726219Swpaul            "|    void println(boolean)",
8826219Swpaul            "|    void println(char)",
8926219Swpaul            "|    void println(int)",
9026219Swpaul            "|    void println(long)",
9126219Swpaul            "|    void println(float)",
9226219Swpaul            "|    void println(double)",
9326219Swpaul            "|    void println(char s[])",
9426219Swpaul            "|    void println(String)",
9526219Swpaul            "|    void println(Object)",
9626219Swpaul            "|    void printf(java.util.Locale,String,Object...)",
9726219Swpaul            "|    void printf(String,Object...)")
9826219Swpaul            .collect(toList());
9926219Swpaul    final static List<String> START_UP = Collections.unmodifiableList(
10026219Swpaul            Stream.concat(START_UP_IMPORTS.stream(), START_UP_METHODS.stream())
10126219Swpaul            .map(s -> s.getSource())
10226219Swpaul            .collect(toList()));
10326219Swpaul
10426219Swpaul    private WaitingTestingInputStream cmdin = null;
10526219Swpaul    private ByteArrayOutputStream cmdout = null;
10626219Swpaul    private ByteArrayOutputStream cmderr = null;
10726219Swpaul    private PromptedCommandOutputStream console = null;
10826219Swpaul    private TestingInputStream userin = null;
10926219Swpaul    private ByteArrayOutputStream userout = null;
11026219Swpaul    private ByteArrayOutputStream usererr = null;
11126219Swpaul
11226219Swpaul    private List<MemberInfo> keys;
11326219Swpaul    private Map<String, VariableInfo> variables;
11426219Swpaul    private Map<String, MethodInfo> methods;
11526219Swpaul    private Map<String, ClassInfo> classes;
11626219Swpaul    private Map<String, ImportInfo> imports;
11726219Swpaul    private boolean isDefaultStartUp = true;
11826219Swpaul    private Map<String, String> prefsMap;
11926219Swpaul    private Map<String, String> envvars;
12026219Swpaul
12126219Swpaul    public interface ReplTest {
12226219Swpaul        void run(boolean after);
12326219Swpaul    }
12426219Swpaul
12526219Swpaul    public void setCommandInput(String s) {
12626219Swpaul        cmdin.setInput(s);
12726219Swpaul    }
12826219Swpaul
12926219Swpaul    public final static Pattern idPattern = Pattern.compile("^\\s+(\\d+)");
13026219Swpaul    public Consumer<String> assertList() {
13126219Swpaul        return s -> {
13226219Swpaul            List<String> lines = Stream.of(s.split("\n"))
13326219Swpaul                    .filter(l -> !l.isEmpty())
13426219Swpaul                    .collect(Collectors.toList());
13526219Swpaul            int previousId = Integer.MIN_VALUE;
13626219Swpaul            assertEquals(lines.size(), keys.size(), "Number of keys");
13726219Swpaul            for (int i = 0; i < lines.size(); ++i) {
13826219Swpaul                String line = lines.get(i);
13926219Swpaul                Matcher matcher = idPattern.matcher(line);
14026219Swpaul                assertTrue(matcher.find(), "Snippet id not found: " + line);
14126219Swpaul                String src = keys.get(i).getSource();
14226219Swpaul                assertTrue(line.endsWith(src), "Line '" + line + "' does not end with: " + src);
14326219Swpaul                int id = Integer.parseInt(matcher.group(1));
14426219Swpaul                assertTrue(previousId < id,
14526219Swpaul                        String.format("The previous id is not less than the next one: previous: %d, next: %d",
14626219Swpaul                                previousId, id));
14726219Swpaul                previousId = id;
14826219Swpaul            }
14926219Swpaul        };
15026219Swpaul    }
15126219Swpaul
15226219Swpaul    private final static Pattern extractPattern = Pattern.compile("^\\| *(.*)$");
15326219Swpaul    private Consumer<String> assertMembers(String message, Map<String, ? extends MemberInfo> set) {
15426219Swpaul        return s -> {
15526219Swpaul            List<String> lines = Stream.of(s.split("\n"))
15626219Swpaul                    .filter(l -> !l.isEmpty())
15726219Swpaul                    .filter(l -> !l.startsWith("|     ")) // error/unresolved info
15826219Swpaul                    .collect(Collectors.toList());
15926219Swpaul            assertEquals(lines.size(), set.size(), message + " : expected: " + set.keySet() + "\ngot:\n" + lines);
16026219Swpaul            for (String line : lines) {
16126219Swpaul                Matcher matcher = extractPattern.matcher(line);
16226219Swpaul                assertTrue(matcher.find(), line);
16326219Swpaul                String src = matcher.group(1);
16426219Swpaul                MemberInfo info = set.get(src);
16526219Swpaul                assertNotNull(info, "Not found snippet with signature: " + src + ", line: "
16626219Swpaul                        + line + ", keys: " + set.keySet() + "\n");
16726219Swpaul            }
16826219Swpaul        };
16926219Swpaul    }
17026219Swpaul
17126219Swpaul    public Consumer<String> assertVariables() {
17226219Swpaul        return assertMembers("Variables", variables);
17326219Swpaul    }
17426219Swpaul
17526219Swpaul    public Consumer<String> assertMethods() {
17626219Swpaul        return assertMembers("Methods", methods);
17726219Swpaul    }
17826219Swpaul
17926219Swpaul    public Consumer<String> assertClasses() {
18026219Swpaul        return assertMembers("Classes", classes);
18126219Swpaul    }
18226219Swpaul
18326219Swpaul    public Consumer<String> assertImports() {
18426219Swpaul        return assertMembers("Imports", imports);
18526219Swpaul    }
18626219Swpaul
18726219Swpaul    public String getCommandOutput() {
18826219Swpaul        String s = normalizeLineEndings(cmdout.toString());
18926219Swpaul        cmdout.reset();
19026219Swpaul        return s;
19126219Swpaul    }
19226219Swpaul
19326219Swpaul    public String getCommandErrorOutput() {
19426219Swpaul        String s = normalizeLineEndings(cmderr.toString());
19526219Swpaul        cmderr.reset();
19626219Swpaul        return s;
19726219Swpaul    }
19826219Swpaul
19926219Swpaul    public void setUserInput(String s) {
20026219Swpaul        userin.setInput(s);
20126219Swpaul    }
20226219Swpaul
20326219Swpaul    public String getUserOutput() {
20426219Swpaul        String s = normalizeLineEndings(userout.toString());
20526219Swpaul        userout.reset();
20626219Swpaul        return s;
20726219Swpaul    }
20826219Swpaul
20926219Swpaul    public String getUserErrorOutput() {
21026219Swpaul        String s = normalizeLineEndings(usererr.toString());
21126219Swpaul        usererr.reset();
21226219Swpaul        return s;
21326219Swpaul    }
21426219Swpaul
21526219Swpaul    public void test(ReplTest... tests) {
21626219Swpaul        test(new String[0], tests);
21726219Swpaul    }
21826219Swpaul
21926219Swpaul    public void test(String[] args, ReplTest... tests) {
22026219Swpaul        test(true, args, tests);
22126219Swpaul    }
22226219Swpaul
22326219Swpaul    public void test(boolean isDefaultStartUp, String[] args, ReplTest... tests) {
22426219Swpaul        test(Locale.ROOT, isDefaultStartUp, args, DEFAULT_STARTUP_MESSAGE, tests);
22526219Swpaul    }
22626219Swpaul
22726219Swpaul    public void testNoStartUp(ReplTest... tests) {
22826219Swpaul        test(Locale.ROOT, false, new String[] {"--no-startup"}, DEFAULT_STARTUP_MESSAGE, tests);
22926219Swpaul    }
23026219Swpaul
23126219Swpaul    public void test(Locale locale, boolean isDefaultStartUp, String[] args, String startUpMessage, ReplTest... tests) {
23226219Swpaul        this.isDefaultStartUp = isDefaultStartUp;
23326219Swpaul        initSnippets();
23426219Swpaul        ReplTest[] wtests = new ReplTest[tests.length + 3];
23526219Swpaul        wtests[0] = a -> assertCommandCheckOutput(a, "<start>",
23626219Swpaul                s -> assertTrue(s.startsWith(startUpMessage), "Expected start-up message '" + startUpMessage + "' Got: " + s));
23726219Swpaul        wtests[1] = a -> assertCommand(a, "/debug 0", null);
23826219Swpaul        System.arraycopy(tests, 0, wtests, 2, tests.length);
23926219Swpaul        wtests[tests.length + 2] = a -> assertCommand(a, "/exit", null);
24026219Swpaul        testRaw(locale, args, wtests);
24126219Swpaul    }
24226219Swpaul
24326219Swpaul    private void initSnippets() {
24426219Swpaul        keys = new ArrayList<>();
24526219Swpaul        variables = new HashMap<>();
24626219Swpaul        methods = new HashMap<>();
24726219Swpaul        classes = new HashMap<>();
24826219Swpaul        imports = new HashMap<>();
24926219Swpaul        if (isDefaultStartUp) {
25026219Swpaul            methods.putAll(
25126219Swpaul                START_UP_METHODS.stream()
25226219Swpaul                    .collect(Collectors.toMap(Object::toString, Function.identity())));
25326219Swpaul            imports.putAll(
25426219Swpaul                START_UP_IMPORTS.stream()
25526219Swpaul                    .collect(Collectors.toMap(Object::toString, Function.identity())));
25626219Swpaul        }
25726219Swpaul    }
25826219Swpaul
25926219Swpaul    @BeforeMethod
26026219Swpaul    public void setUp() {
26126219Swpaul        prefsMap = new HashMap<>();
26226219Swpaul        envvars = new HashMap<>();
26326219Swpaul    }
26426219Swpaul
26526219Swpaul    protected void setEnvVar(String name, String value) {
26626219Swpaul        envvars.put(name, value);
26726219Swpaul    }
26826219Swpaul
26926219Swpaul    protected JavaShellToolBuilder builder(Locale locale) {
27026219Swpaul        // turn on logging of launch failures
27126219Swpaul        Logger.getLogger("jdk.jshell.execution").setLevel(Level.ALL);
27226219Swpaul        return JavaShellToolBuilder
27326219Swpaul                    .builder()
27426219Swpaul                    .in(cmdin, userin)
27526219Swpaul                    .out(new PrintStream(cmdout), new PrintStream(console), new PrintStream(userout))
27626219Swpaul                    .err(new PrintStream(cmderr), new PrintStream(usererr))
27726219Swpaul                    .persistence(prefsMap)
27826219Swpaul                    .env(envvars)
27926219Swpaul                    .locale(locale)
28026219Swpaul                    .promptCapture(true);
28126219Swpaul    }
28226219Swpaul
28326219Swpaul    private void testRaw(Locale locale, String[] args, ReplTest... tests) {
28426219Swpaul        testRawInit(tests);
28526219Swpaul        testRawRun(locale, args);
28626219Swpaul        testRawCheck(locale);
28726219Swpaul    }
28826219Swpaul
28926219Swpaul    private void testRawInit(ReplTest... tests) {
29026219Swpaul        cmdin = new WaitingTestingInputStream();
29126219Swpaul        cmdout = new ByteArrayOutputStream();
29226219Swpaul        cmderr = new ByteArrayOutputStream();
29326219Swpaul        console = new PromptedCommandOutputStream(tests);
29426219Swpaul        userin = new TestingInputStream();
29526219Swpaul        userout = new ByteArrayOutputStream();
29626219Swpaul        usererr = new ByteArrayOutputStream();
29726219Swpaul    }
29826219Swpaul
29926219Swpaul    protected void testRawRun(Locale locale, String[] args) {
30026219Swpaul        try {
30126219Swpaul            builder(locale)
30226219Swpaul                    .run(args);
30326219Swpaul        } catch (Exception ex) {
30426219Swpaul            fail("Repl tool died with exception", ex);
30526219Swpaul        }
30626219Swpaul    }
30726219Swpaul
30826219Swpaul    private void testRawCheck(Locale locale) {
30926219Swpaul        // perform internal consistency checks on state, if desired
31026219Swpaul        String cos = getCommandOutput();
31126219Swpaul        String ceos = getCommandErrorOutput();
31226219Swpaul        String uos = getUserOutput();
31326219Swpaul        String ueos = getUserErrorOutput();
31426219Swpaul        assertTrue((cos.isEmpty() || cos.startsWith("|  Goodbye") || !locale.equals(Locale.ROOT)),
31526219Swpaul                "Expected a goodbye, but got: " + cos);
31626219Swpaul        assertTrue(ceos.isEmpty(), "Expected empty command error output, got: " + ceos);
31726219Swpaul        assertTrue(uos.isEmpty(), "Expected empty user output, got: " + uos);
31826219Swpaul        assertTrue(ueos.isEmpty(), "Expected empty user error output, got: " + ueos);
31926219Swpaul    }
32026219Swpaul
32126666Swpaul    public void assertReset(boolean after, String cmd) {
32226219Swpaul        assertCommand(after, cmd, "|  Resetting state.\n");
32326219Swpaul        initSnippets();
32426219Swpaul    }
32526219Swpaul
32626219Swpaul    public void evaluateExpression(boolean after, String type, String expr, String value) {
32726219Swpaul        String output = String.format("(\\$\\d+) ==> %s", value);
32826219Swpaul        Pattern outputPattern = Pattern.compile(output);
32926219Swpaul        assertCommandCheckOutput(after, expr, s -> {
33026219Swpaul            Matcher matcher = outputPattern.matcher(s);
33126219Swpaul            assertTrue(matcher.find(), "Output: '" + s + "' does not fit pattern: '" + output + "'");
33226219Swpaul            String name = matcher.group(1);
33326219Swpaul            VariableInfo tempVar = new TempVariableInfo(expr, type, name, value);
33426219Swpaul            variables.put(tempVar.toString(), tempVar);
33526219Swpaul            addKey(after, tempVar);
33626219Swpaul        });
33726219Swpaul    }
33826219Swpaul
33926219Swpaul    public void loadVariable(boolean after, String type, String name) {
34026219Swpaul        loadVariable(after, type, name, null, null);
34126219Swpaul    }
34226219Swpaul
34326219Swpaul    public void loadVariable(boolean after, String type, String name, String expr, String value) {
34426219Swpaul        String src = expr == null
34526219Swpaul                ? String.format("%s %s", type, name)
34626219Swpaul                : String.format("%s %s = %s", type, name, expr);
34726219Swpaul        VariableInfo var = expr == null
34826219Swpaul                ? new VariableInfo(src, type, name)
34926219Swpaul                : new VariableInfo(src, type, name, value);
35026219Swpaul        addKey(after, var, variables);
35126219Swpaul        addKey(after, var);
35226219Swpaul    }
35326219Swpaul
35426219Swpaul    public void assertVariable(boolean after, String type, String name) {
35526219Swpaul        assertVariable(after, type, name, null, null);
35626219Swpaul    }
35726219Swpaul
35826219Swpaul    public void assertVariable(boolean after, String type, String name, String expr, String value) {
35926219Swpaul        String src = expr == null
36026219Swpaul                ? String.format("%s %s", type, name)
36126219Swpaul                : String.format("%s %s = %s", type, name, expr);
36226219Swpaul        VariableInfo var = expr == null
36326219Swpaul                ? new VariableInfo(src, type, name)
36426219Swpaul                : new VariableInfo(src, type, name, value);
36526219Swpaul        assertCommandCheckOutput(after, src, var.checkOutput());
36626219Swpaul        addKey(after, var, variables);
36726219Swpaul        addKey(after, var);
36826219Swpaul    }
36926219Swpaul
37026219Swpaul    public void loadMethod(boolean after, String src, String signature, String name) {
37126219Swpaul        MethodInfo method = new MethodInfo(src, signature, name);
37226219Swpaul        addKey(after, method, methods);
37326219Swpaul        addKey(after, method);
37426219Swpaul    }
37526219Swpaul
37626219Swpaul    public void assertMethod(boolean after, String src, String signature, String name) {
37726219Swpaul        MethodInfo method = new MethodInfo(src, signature, name);
37826219Swpaul        assertCommandCheckOutput(after, src, method.checkOutput());
37926219Swpaul        addKey(after, method, methods);
38026219Swpaul        addKey(after, method);
38171579Sdeischen    }
38226219Swpaul
38326219Swpaul    public void loadClass(boolean after, String src, String type, String name) {
38426219Swpaul        ClassInfo clazz = new ClassInfo(src, type, name);
38526219Swpaul        addKey(after, clazz, classes);
38626219Swpaul        addKey(after, clazz);
38726219Swpaul    }
38826219Swpaul
38926219Swpaul    public void assertClass(boolean after, String src, String type, String name) {
39026219Swpaul        ClassInfo clazz = new ClassInfo(src, type, name);
39126219Swpaul        assertCommandCheckOutput(after, src, clazz.checkOutput());
39226219Swpaul        addKey(after, clazz, classes);
39326219Swpaul        addKey(after, clazz);
39426219Swpaul    }
39526219Swpaul
39626219Swpaul    public void loadImport(boolean after, String src, String type, String name) {
39771579Sdeischen        ImportInfo i = new ImportInfo(src, type, name);
39826219Swpaul        addKey(after, i, imports);
39926219Swpaul        addKey(after, i);
40026219Swpaul    }
40126219Swpaul
40226219Swpaul    public void assertImport(boolean after, String src, String type, String name) {
40326219Swpaul        ImportInfo i = new ImportInfo(src, type, name);
40426219Swpaul        assertCommandCheckOutput(after, src, i.checkOutput());
40571579Sdeischen        addKey(after, i, imports);
40626219Swpaul        addKey(after, i);
40726219Swpaul    }
40826219Swpaul
40926219Swpaul    private <T extends MemberInfo> void addKey(boolean after, T memberInfo, Map<String, T> map) {
41026219Swpaul        if (after) {
41171579Sdeischen            map.entrySet().removeIf(e -> e.getValue().equals(memberInfo));
41226219Swpaul            map.put(memberInfo.toString(), memberInfo);
41326219Swpaul        }
41426219Swpaul    }
41526219Swpaul
41626219Swpaul    private <T extends MemberInfo> void addKey(boolean after, T memberInfo) {
41726219Swpaul        if (after) {
41826219Swpaul            for (int i = 0; i < keys.size(); ++i) {
41926219Swpaul                MemberInfo m = keys.get(i);
42026219Swpaul                if (m.equals(memberInfo)) {
42126219Swpaul                    keys.set(i, memberInfo);
42226219Swpaul                    return;
42326219Swpaul                }
42471579Sdeischen            }
42526219Swpaul            keys.add(memberInfo);
42626219Swpaul        }
42726219Swpaul    }
42826219Swpaul
42926219Swpaul    private void dropKey(boolean after, String cmd, String name, Map<String, ? extends MemberInfo> map, String output) {
43026219Swpaul        assertCommand(after, cmd, output);
43126219Swpaul        if (after) {
43226219Swpaul            map.remove(name);
43356698Sjasone            for (int i = 0; i < keys.size(); ++i) {
43426219Swpaul                MemberInfo m = keys.get(i);
43526219Swpaul                if (m.toString().equals(name)) {
43626219Swpaul                    keys.remove(i);
43726219Swpaul                    return;
43826219Swpaul                }
43926219Swpaul            }
44026219Swpaul            throw new AssertionError("Key not found: " + name + ", keys: " + keys);
44126219Swpaul        }
44226219Swpaul    }
44326219Swpaul
44426219Swpaul    public void dropVariable(boolean after, String cmd, String name, String output) {
44556698Sjasone        dropKey(after, cmd, name, variables, output);
44626219Swpaul    }
44726219Swpaul
44826219Swpaul    public void dropMethod(boolean after, String cmd, String name, String output) {
44926219Swpaul        dropKey(after, cmd, name, methods, output);
45026219Swpaul    }
45126219Swpaul
45226219Swpaul    public void dropClass(boolean after, String cmd, String name, String output) {
45326219Swpaul        dropKey(after, cmd, name, classes, output);
45426219Swpaul    }
45526219Swpaul
45626219Swpaul    public void dropImport(boolean after, String cmd, String name, String output) {
45726219Swpaul        dropKey(after, cmd, name, imports, output);
45826219Swpaul    }
45926219Swpaul
46026219Swpaul    public void assertCommand(boolean after, String cmd, String out) {
46126219Swpaul        assertCommand(after, cmd, out, "", null, "", "");
46226219Swpaul    }
46326219Swpaul
46456698Sjasone    public void assertCommandOutputContains(boolean after, String cmd, String... hasThese) {
46526219Swpaul        assertCommandCheckOutput(after, cmd, (s)
46626219Swpaul                -> assertTrue(Arrays.stream(hasThese)
46726219Swpaul                                    .allMatch(has -> s.contains(has)),
46826219Swpaul                        "Output: \'" + s + "' does not contain: "
46926219Swpaul                                + Arrays.stream(hasThese)
47026219Swpaul                                        .filter(has -> !s.contains(has))
47126219Swpaul                                        .collect(Collectors.joining(", "))));
47226219Swpaul    }
47326219Swpaul
47426219Swpaul    public void assertCommandOutputStartsWith(boolean after, String cmd, String starts) {
47526219Swpaul        assertCommandCheckOutput(after, cmd, assertStartsWith(starts));
47626219Swpaul    }
47726219Swpaul
47826219Swpaul    public void assertCommandCheckOutput(boolean after, String cmd, Consumer<String> check) {
47926219Swpaul        if (!after) {
48026219Swpaul            assertCommand(false, cmd, null);
48126219Swpaul        } else {
48226219Swpaul            String got = getCommandOutput();
48326219Swpaul            check.accept(got);
48426219Swpaul            assertCommand(true, cmd, null);
48526219Swpaul        }
48626219Swpaul    }
48726219Swpaul
48826219Swpaul    public void assertCommand(boolean after, String cmd, String out, String err,
48926219Swpaul            String userinput, String print, String usererr) {
49026219Swpaul        if (!after) {
49126219Swpaul            if (userinput != null) {
49226219Swpaul                setUserInput(userinput);
49326219Swpaul            }
49426219Swpaul            setCommandInput(cmd + "\n");
49526219Swpaul        } else {
49626219Swpaul            assertOutput(getCommandOutput().trim(), out==null? out : out.trim(), "command output: " + cmd);
497            assertOutput(getCommandErrorOutput(), err, "command error: " + cmd);
498            assertOutput(getUserOutput(), print, "user output: " + cmd);
499            assertOutput(getUserErrorOutput(), usererr, "user error: " + cmd);
500        }
501    }
502
503    public Consumer<String> assertStartsWith(String prefix) {
504        return (output) -> assertTrue(output.trim().startsWith(prefix), "Output: \'" + output + "' does not start with: " + prefix);
505    }
506
507    public void assertOutput(String got, String expected, String display) {
508        if (expected != null) {
509            assertEquals(got, expected, display + ".\n");
510        }
511    }
512
513    private String normalizeLineEndings(String text) {
514        return text.replace(System.getProperty("line.separator"), "\n");
515    }
516
517    public static abstract class MemberInfo {
518        public final String source;
519        public final String type;
520        public final String name;
521
522        public MemberInfo(String source, String type, String name) {
523            this.source = source;
524            this.type = type;
525            this.name = name;
526        }
527
528        @Override
529        public int hashCode() {
530            return name.hashCode();
531        }
532
533        @Override
534        public boolean equals(Object o) {
535            if (o instanceof MemberInfo) {
536                MemberInfo mi = (MemberInfo) o;
537                return name.equals(mi.name);
538            }
539            return false;
540        }
541
542        public abstract Consumer<String> checkOutput();
543
544        public String getSource() {
545            return source;
546        }
547    }
548
549    public static class VariableInfo extends MemberInfo {
550
551        public final String value;
552        public final String initialValue;
553
554        public VariableInfo(String src, String type, String name) {
555            super(src, type, name);
556            this.initialValue = null;
557            switch (type) {
558                case "byte":
559                case "short":
560                case "int":
561                case "long":
562                    value = "0";
563                    break;
564                case "boolean":
565                    value = "false";
566                    break;
567                case "char":
568                    value = "''";
569                    break;
570                case "float":
571                case "double":
572                    value = "0.0";
573                    break;
574                default:
575                    value = "null";
576            }
577        }
578
579        public VariableInfo(String src, String type, String name, String value) {
580            super(src, type, name);
581            this.value = value;
582            this.initialValue = value;
583        }
584
585        @Override
586        public Consumer<String> checkOutput() {
587            String arrowPattern = String.format("%s ==> %s", name, value);
588            Predicate<String> arrowCheckOutput = Pattern.compile(arrowPattern).asPredicate();
589            String howeverPattern = String.format("\\| *\\w+ variable %s, however*.", name);
590            Predicate<String> howeverCheckOutput = Pattern.compile(howeverPattern).asPredicate();
591            return output -> {
592                if (output.startsWith("|  ")) {
593                    assertTrue(howeverCheckOutput.test(output),
594                    "Output: " + output + " does not fit pattern: " + howeverPattern);
595                } else {
596                    assertTrue(arrowCheckOutput.test(output),
597                    "Output: " + output + " does not fit pattern: " + arrowPattern);
598                }
599            };
600        }
601
602        @Override
603        public int hashCode() {
604            return name.hashCode();
605        }
606
607        @Override
608        public boolean equals(Object o) {
609            if (o instanceof VariableInfo) {
610                VariableInfo v = (VariableInfo) o;
611                return name.equals(v.name);
612            }
613            return false;
614        }
615
616        @Override
617        public String toString() {
618            return String.format("%s %s = %s", type, name, value);
619        }
620
621        @Override
622        public String getSource() {
623            String src = super.getSource();
624            return src.endsWith(";") ? src : src + ";";
625        }
626    }
627
628    public static class TempVariableInfo extends VariableInfo {
629
630        public TempVariableInfo(String src, String type, String name, String value) {
631            super(src, type, name, value);
632        }
633
634        @Override
635        public String getSource() {
636            return source;
637        }
638    }
639
640    public static class MethodInfo extends MemberInfo {
641
642        public final String signature;
643
644        public MethodInfo(String source, String signature, String name) {
645            super(source, signature.substring(0, signature.lastIndexOf(')') + 1), name);
646            this.signature = signature;
647        }
648
649        @Override
650        public Consumer<String> checkOutput() {
651            String expectedOutput = String.format("\\| *\\w+ method %s", name);
652            Predicate<String> checkOutput = Pattern.compile(expectedOutput).asPredicate();
653            return s -> assertTrue(checkOutput.test(s), "Expected: '" + expectedOutput + "', actual: " + s);
654        }
655
656        @Override
657        public int hashCode() {
658            return (name.hashCode() << 2) ^ type.hashCode() ;
659        }
660
661        @Override
662        public boolean equals(Object o) {
663            if (o instanceof MemberInfo) {
664                MemberInfo m = (MemberInfo) o;
665                return name.equals(m.name) && type.equals(m.type);
666            }
667            return false;
668        }
669
670        @Override
671        public String toString() {
672            int i = signature.lastIndexOf(")") + 1;
673            if (i <= 0) {
674                return String.format("%s", name);
675            } else {
676                return String.format("%s %s%s", signature.substring(i), name, signature.substring(0, i));
677            }
678        }
679    }
680
681    public static class ClassInfo extends MemberInfo {
682
683        public ClassInfo(String source, String type, String name) {
684            super(source, type, name);
685        }
686
687        @Override
688        public Consumer<String> checkOutput() {
689            String fullType = type.equals("@interface")? "annotation interface" : type;
690            String expectedOutput = String.format("\\| *\\w+ %s %s", fullType, name);
691            Predicate<String> checkOutput = Pattern.compile(expectedOutput).asPredicate();
692            return s -> assertTrue(checkOutput.test(s), "Expected: '" + expectedOutput + "', actual: " + s);
693        }
694
695        @Override
696        public int hashCode() {
697            return name.hashCode() ;
698        }
699
700        @Override
701        public boolean equals(Object o) {
702            if (o instanceof ClassInfo) {
703                ClassInfo c = (ClassInfo) o;
704                return name.equals(c.name);
705            }
706            return false;
707        }
708
709        @Override
710        public String toString() {
711            return String.format("%s %s", type, name);
712        }
713    }
714
715    public static class ImportInfo extends MemberInfo {
716        public ImportInfo(String source, String type, String fullname) {
717            super(source, type, fullname);
718        }
719
720        @Override
721        public Consumer<String> checkOutput() {
722            return s -> assertTrue("".equals(s), "Expected: '', actual: " + s);
723        }
724
725        @Override
726        public int hashCode() {
727            return (name.hashCode() << 2) ^ type.hashCode() ;
728        }
729
730        @Override
731        public boolean equals(Object o) {
732            if (o instanceof ImportInfo) {
733                ImportInfo i = (ImportInfo) o;
734                return name.equals(i.name) && type.equals(i.type);
735            }
736            return false;
737        }
738
739        @Override
740        public String toString() {
741            return String.format("import %s%s", type.equals("static") ? "static " : "", name);
742        }
743    }
744
745    class WaitingTestingInputStream extends TestingInputStream {
746
747        @Override
748        synchronized void setInput(String s) {
749            super.setInput(s);
750            notify();
751        }
752
753        synchronized void waitForInput() {
754            boolean interrupted = false;
755            try {
756                while (available() == 0) {
757                    try {
758                        wait();
759                    } catch (InterruptedException e) {
760                        interrupted = true;
761                        // fall through and retry
762                    }
763                }
764            } finally {
765                if (interrupted) {
766                    Thread.currentThread().interrupt();
767                }
768            }
769        }
770
771        @Override
772        public int read() {
773            waitForInput();
774            return super.read();
775        }
776
777        @Override
778        public int read(byte b[], int off, int len) {
779            waitForInput();
780            return super.read(b, off, len);
781        }
782    }
783
784    class PromptedCommandOutputStream extends OutputStream {
785        private final ReplTest[] tests;
786        private int index = 0;
787        PromptedCommandOutputStream(ReplTest[] tests) {
788            this.tests = tests;
789        }
790
791        @Override
792        public synchronized void write(int b) {
793            if (b == 5 || b == 6) {
794                if (index < (tests.length - 1)) {
795                    tests[index].run(true);
796                    tests[index + 1].run(false);
797                } else {
798                    fail("Did not exit Repl tool after test");
799                }
800                ++index;
801            } // For now, anything else is thrown away
802        }
803
804        @Override
805        public synchronized void write(byte b[], int off, int len) {
806            if ((off < 0) || (off > b.length) || (len < 0)
807                    || ((off + len) - b.length > 0)) {
808                throw new IndexOutOfBoundsException();
809            }
810            for (int i = 0; i < len; ++i) {
811                write(b[off + i]);
812            }
813        }
814    }
815
816    public static final class MemoryPreferences extends AbstractPreferences {
817
818        private final Map<String, String> values = new HashMap<>();
819        private final Map<String, MemoryPreferences> nodes = new HashMap<>();
820
821        public MemoryPreferences() {
822            this(null, "");
823        }
824
825        public MemoryPreferences(MemoryPreferences parent, String name) {
826            super(parent, name);
827        }
828
829        @Override
830        protected void putSpi(String key, String value) {
831            values.put(key, value);
832        }
833
834        @Override
835        protected String getSpi(String key) {
836            return values.get(key);
837        }
838
839        @Override
840        protected void removeSpi(String key) {
841            values.remove(key);
842        }
843
844        @Override
845        protected void removeNodeSpi() throws BackingStoreException {
846            ((MemoryPreferences) parent()).nodes.remove(name());
847        }
848
849        @Override
850        protected String[] keysSpi() throws BackingStoreException {
851            return values.keySet().toArray(new String[0]);
852        }
853
854        @Override
855        protected String[] childrenNamesSpi() throws BackingStoreException {
856            return nodes.keySet().toArray(new String[0]);
857        }
858
859        @Override
860        protected AbstractPreferences childSpi(String name) {
861            return nodes.computeIfAbsent(name, n -> new MemoryPreferences(this, name));
862        }
863
864        @Override
865        protected void syncSpi() throws BackingStoreException {
866        }
867
868        @Override
869        protected void flushSpi() throws BackingStoreException {
870        }
871
872    }
873}
874