java/ch/wlkl/wsh/Tester.java

package ch.wlkl.wsh;

import static ch.wlkl.wsh.Env.env;
import static ch.wlkl.wsh.Strings.fill;
import static ch.wlkl.wsh.Strings.quote;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * @author Keller
 * test Infrasturcture and all tests for package shBuf 
 *
 */

public class Tester extends Top implements Read<String>, Write<String> {
    final BufferedReader reader; 
    char type = ' ';
    int nextStart = 0;
    String val = null;
    String token = null;
    String line = null;
    String scriptLine = null;
    int scriptErase = -1;
    int lineNo = 0;
    HashMap<String, String> vars = new HashMap<String, String>();
    List<String> compare = null;
    final java.util.ArrayList<String> input = new java.util.ArrayList<String>();
    ArrayList<String> output = new ArrayList<String> ();
    ArrayList<String> script = new ArrayList<String> ();
    int compareIx = -1;
    
    int errors = 0;
    String errorTests = "";
    public final static String tempDir = "\\tmpelieli\\zwei/drei";

    int readIx = -1;
    Env<?, ?> envTop = null;

    public static final Pattern space = Pattern.compile("\\s+");
    public static final Pattern comment = Pattern.compile("\\*\\*");
    public static final Pattern special = Pattern.compile("[+,:;()=]");
    public static final Pattern name = Pattern.compile("\\w+");
    public static final Pattern string = Pattern.compile("'([^']|'')*'");

    final Matcher match = space.matcher("");
        
    public Tester(Reader r) {
        vars.put("nl", "\n");
        reader = new BufferedReader(r);
        parseNL();
        parse();
    }
    
    boolean parseNL () {
        if (scriptLine != null && scriptErase >= -1) {
            if (scriptErase >= 0) {
                scriptLine = scriptLine.substring(0, scriptErase);
                scriptErase = -9;
            }
            script.add(scriptLine);
        }
        try {
            if (null == (scriptLine = line = reader.readLine()))
                return false;
        } catch (IOException e) {
            fail("exception in readLine " + e);
        }
        lineNo++;
        match.reset(line);
        nextStart = 0;
        return true;
    }
    
    boolean parse(Pattern p) {
        if (! match.region(nextStart, line.length()).usePattern(p).lookingAt())
            return false;
        nextStart = match.end();
        return true;
    }
    boolean parse() {
        if (line == null)
            return false;
        while (true) {
            parse(space);
            if (nextStart < line.length() && ! parse(comment))
                break;
            if (! parseNL()) {
                type ='e';
                token = val = null;
                return false;
            }
        }
        if (parse(special)) {
            type = (token = val = match.group()).charAt(0);
        } else if (parse(name)) {
            type = 'n';
            token = val = match.group();
        } else if (parse(string)) {
            type = 's';
            val = Strings.unquote(token = val = match.group());
        } else {
            parseErr("cannot parse");
        }
        return true;
    }
    
    boolean parseName(String name) {
        if (type != 'n' || ! val.equals(name))
            return false;
        parse();
        return true;
    }

    boolean parseSp(char sp) {
        if (type != sp)
            return false;
        parse();
        return true;
    }

    void parseErr(String msg) {
        fail("parse error: " + msg 
                + "\n  last token: " + token + " next: " + (line == null ? "endOfFile" :line.substring(nextStart, line.length() < nextStart+30 ? line.length() : nextStart+30)) 
                + "\n  nextPos: " + nextStart + " in line " + lineNo + ": " + line);
    }

    static void parseTest() {
        String src = "und wie gehts +;      \n\n        'string  mit ''.' " 
            + "\n und so weiter ** kommentar ?"
            + "\n\n      ** kommentar \n    \n nachher .";
        Tester t = new Tester(new StringReader(src));
        do
            sSay("type " + t.type + ", val " + t.val + ", token " + t.token);        
        while (t.parse());
    }

    String string() {
        StringBuffer b = new StringBuffer();
        boolean afterPlus = false;
        while (true) {
            if (type == 's') {
                b.append(val);
                parse();
            } else if (type == 'n') {
                String name = val;
                if (parse() && type == '(') {
                    parse();
                    b.append(function(name, stream()));
                    if (type != ')')
                        parseErr("closing ) of function missing");
                    parse();
                } else {
                    b.append(variable(name));
                }
            } else if (afterPlus) {
                parseErr("no operand after +");
            } else {
                return null;
            }
            if (type != '+')
                return b.toString();
            parse();
            afterPlus = true;
        }
    }
    
    String variable(String name) {
        if (! vars.containsKey(name))
            parseErr("undefined variable " + name); 
        return vars.get(name);
    }
    
    String function(String name, List<String> args) {
        if ("writeTestFiles".equals(name)) {
            return writeTestFiles(args);
        } else if ("removeTestFiles".equals(name)) {
            removeTestFiles();
            return null;
        } else if ("timestamp".equals(name)) {
            return timestamp(args.isEmpty() ? null : args.get(0));
        } else if ("java".equals(name) && args.size() == 2) {
            return args.get(1);
        } else {
            parseErr("unsupported function " + name);
            return null;
        }
    }
    
    static void stringTest() {
        String r, src = "und wie gehts + 'aha '' und so' + weiter()       \n\n        'string  mit ''.' " 
            + "\n und(so() + weiter('sd') +qu(  )) ** kommentar ?"
            + "\n\n      ** kommentar \n    \n nachher ";
        Tester t = new Tester(new StringReader(src));
        t.vars.put("und", "<value of var und>");
        t.vars.put("wie", "<value of var wie>");
        t.vars.put("gehts", "<value of var gehts>");
        while (null != (r = t.string()))
            sSay("string() ==> " + r);        
        
    }
    
    void scriptOn(int sx) {
        if (scriptErase == -1)
            parseErr("scriptOn but already true");
        int ex = scriptErase >= 0 ? scriptErase : 0;
        if (scriptLine != null)
            scriptLine = scriptLine.substring(0, ex) + Strings.fill(" ", sx-ex) + scriptLine.substring(sx);
        scriptErase = -1;
    }

    void scriptOff(int ex) {
        if (scriptErase != -1)
            parseErr("scriptOff but already false");
        scriptErase = ex;
    }

    List<String> stream() {
        List<String> b = new ArrayList<String>();
        String one;
        while (true) {
            if (null == (one = string())) {
                if (b.isEmpty())
                    return b;
                parseErr("string expeceted after ,");
            }
            b.add(one);
            if (type != ',')
                return b;
            parse();
        }
    }
    
    public void run() {
        String name = null;
        String category = null;
        String opt = null;
        List<String> stream = null;
        int cnt = 0;
        while (true ? errors <= 0 : true) {
            if (parseName("test")) {
                if ( type != 'n')
                    parseErr("test name expected");
                name = val;
                if (! parse() || type != 'n')
                    parseErr("test category expected");
                category = val;
                if (parse() && (type == 's' || type == 'n')) {
                    opt = val;
                    parse();
                }
                if (! parseSp(':'))
                    parseErr(": expected after test " + name + "...");
                stream = stream();
            } else if (parseName("compare")) {
                if (name == null)
                    parseErr("compare not following test");
                if (type != ':') 
                    parseErr(": after compare expected");
                scriptOff(nextStart);
                parse();
                compare = stream();
                runOne(name, category, opt, stream);
                cnt = cnt + 1;
                name = category = opt = null;
                script.add(";");
                scriptOn(nextStart);
            } else if (parseName("remove")) {
                while (type == 'n') {
                    vars.remove(val);
                    parse();
                }
            } else if (type == 'n') {
                String nm = val;
                parse();
                if (! parseSp('=')) 
                    parseErr("= for assignment expected");                    
                String vl = string();
                if (vl == null)
                    parseErr("assignment string expected after " + nm + " =");
                vars.put(nm, vl);
            }
            if (type == 'e')
                break;
            if (! parseSp(';')) 
                parseErr("; expected");
        }
        msg("--- --- --- " + cnt + " tests with " + errors + " errors in" + errorTests);
//        sayScript();
    }

    public void sayScript() {
        msg("new script " + script.size() + " lines");
        for (String l: script)
            msg(l);
    }

    void runOne (String name, String category, String opt, List<String> stream) {
        output.clear();
        input.clear();
        readIx = -1;
        final int oldErrors = errors;
        out("--- --- test begin " + name + ' ' + category + ' ' + opt + " >>> " + stream.size() + " lines");
        Env.push(Env.make((Env<String, String>) null, "<-?", this, ">-?", this));
        envTop = env();

        if (! category(category, opt, stream))
            parseErr("unsupported category " + category);
        if (envTop != env())
            err("env has changed");
        else if (env().in() != this)
            err("env in changed");
        else if (env().out() != this)
            err("env out changed");
        else
            Env.pop();
        if (output.size() < compare.size()) {
            err("more compare lines (" + compare.size() + ") than output (" + output.size() +")");
            for (int ix = compare.size(); ix < (compare.size() < output.size() + 5 ? compare.size(): output.size() + 5); ix++)
                 msg("  " + ix +": " + compare.get(ix));
        }    
        msg("--- --- test end   " + name + ' ' + category + (oldErrors == errors ? " ok" : " *****" + (errors - oldErrors) + " errors *****"));
        if (oldErrors != errors)
            errorTests += ' ' + name;
    }

    public boolean category(String category, String opt, List<String> stream) {
        if (category.equals("prefix")) {
            for (String s : stream)
                out(opt + s);
        } else if (category.equals("comp")) {
            out("--- compile " + (opt.charAt(0) == 's' ? "coSh: " : "data: ") + stream.size() + " lines: " + stream.get(0));
            Run r = new Compiler(Env.loader(), new Buf<String>(stream)).compileInstance(opt.charAt(0));
            say("compiled: >>>>>>>>>>> " + r + " <<<<<<<<<<<<");
            testRun(r);
        } else {
            return false;
        }
        return true;
    }

    public void testRun(Run r) {
        out("--- run without input");
        input();
        r.run();
        out("--- run with 3 inputs");
        input("eins zwei drei", "zehn elf zw?lf?", "zwanzig 21 22 23 24 ... 29 und Schluss !");
        r.run();
    }

    static void runTest() {
        String src =
                "v1 = 'value eins';" +
                "v2 = 'werteZwei';" +
                "test prefinxOne prefix 'the Prefix: ' :" + 
                "\n'line one v1 = ' + v1 + ', v2 = ' + v2 + ' = ' + v2 + ' v1 = ' + v1 + '                            super lang'" +
                "\n , 'line two long to here'" +
                "\n;" +
                "\ncompare    :    " +
                "\n'>>>>> begin test prefinxOne prefix the prefix:  >>> 2 lines'" +
                "\n,'the prefix: line one                                    super lanG'" +
                "\n,'the prefix: line one'" +
                "\n,'the prefix: line one'" ;
        Tester t = new Tester(new StringReader(src));
        t.run();

        
    }
    public void msg(String msg) {
        System.out.println(msg);
    }
    
//    public void compare(String... c) {
//        for (int i=0; i < c.length; i++)
//            comp.add(c[i]);        
//    }
//    
//    public void end(String txt) {
//        String r = "", c, o;
//        StringBuffer t = new StringBuffer("");
//        int min, d;
//        for (String s: out)
//            r = r + ", " + xQuote(s);
//        say("output  = (" + (r.equals("") ? "" : r.substring(2)) + ')');
//        r = "";
//        for (String s: comp)
//            r = r + ", " + xQuote(s);
//        say("compare = (" + (r.equals("") ? "" : r.substring(2)) + ')');
//        if (comp.size() != out.size())
//            err("size mismatch out " + out.size() + " != " + comp.size() + " compare");
//        min = out.size() < comp.size() ? out.size() : comp.size();
//        for (int i=0; i < min; i++) {
//            if (! (o =out.get(i)).equals((c = comp.get(i)))) {
//                for (d=0;d < o.length() && d < c.length() && o.charAt(d) == c.charAt(d) ;d++);
//                t.delete(0, t.length());
//                t.append("*** difference at line " + i + ",char " + d);
//                if (t.length() < d+9)
//                    t = t.append(fill(" ", d+9-t.length()));
//                t.insert(d+9, '*');
//                say(new String(t));    
//                say ("compare: " + c);
//                say ("out    : " + o);
//                errors++;
//            }
//        }
//    }
//    
    public void out(String msg) {
        int d, ix = output.size();
        output.add(msg);
        if (output.size() > compare.size()) {
            if (output.size() == compare.size() + 1)
                err("more new outputlines than " + compare.size() + " compare lines");
        } else if (! msg.equals(compare.get(ix))) {
            String cl = compare.get(ix);
            for (d=0;d < msg.length() && d < cl.length() && msg.charAt(d) == cl.charAt(d) ;d++);
            String e = "*** compare line " + ix + "(next) differs from output (overnext) at pos " + d;
            if (d >= e.length()+2)
                e += fill(" ", d-e.length() - 2) + " >*< ";
            else if (d > 4)
                e = e.substring(0, d-2) + " >*< " + e.substring(d-2);
            msg(e);
            msg(cl);
            errors++;
        }
        msg(msg);
        scriptString(output.size() == 1 ? "   " : " , ", msg);
    }
    
    /**
     * write a stream to the script expanding variables.
     * @param pr
     * @param msgs
     */
    void scriptStream(String pr, String[] msgs) {
        for (String m : msgs) {
            scriptString(pr, m);
            pr = " , ";
        }
    }

    /**
     * write a stream to the script expanding variables.
     * @param pr
     * @param msgs
     */
    void scriptStream(String pr, List<String> msgs) {
        scriptStream(pr, msgs.toArray(new String[msgs.size()]));
    }

    /**
     * write a string to the script, expanding variables
     * @param pr
     * @param msg
     */
    void scriptString(String pr, String msg) {
        scriptString(pr, msg, vars.keySet().toArray(new String[vars.size()]), 0);
    }

    /**
     * write a string to the script, expanding variables with keys keys[kx ... keys.length]
     * @param pr
     * @param msg
     * @param keys
     * @param kx
     */
    void scriptString(String pr, String msg, String[] keys, int kx) {
        int sx = -1;
        String v = "";
        for ( ; kx < keys.length && 0 > (sx = msg.indexOf(v = vars.get(keys[kx]))); kx++) ;
        if (sx < 0 ){
            script.add(pr + quote(msg, "'"));
            return;
        }
        int ex = 0;
        do {
            if (sx > ex) {
                    scriptString(pr, msg.substring(ex, sx), keys, kx+1);
                    pr = "  +";
            }
            script.add(pr + keys[kx]);
            pr = "  +";
        } while (0 <= (sx = msg.indexOf(v, ex = sx + v.length())));
        if (ex < msg.length())
            scriptString(pr, msg.substring(ex), keys, kx+1);
    }


    public void err(String msg) {
        msg("*** error: " + msg);
        errors++;
    }
    
    public String writeTestFiles(List<String> names) {
        FileRW wf = new FileRW();
        for (int ix=0; ix < names.size(); ix++) {
            wf.reset(tempDir + '\\' + names.get(ix));
            wf.open("w");
            Ut.write(wf, "test file " + ix + " of " + names.size(), "name = " + names.get(ix), "]end of test file " + ix + ": " + names.get(ix));
            wf.close();
        }
        return tempDir;
    }

    public void removeTestFiles() {
        removeRecursive(new File(tempDir));
    }

    public void removeRecursive(File f) {
        if (f.isDirectory()) {
            File [] li = f.listFiles();
            for (int ix=0; ix < li.length; ix++)
                removeRecursive(li[ix]);
        }
        if (! f.delete())
            say("could not delete " + f);
    }

    public String timestamp(String arg) {
        String r = new java.util.Date().toString();
        if (arg == null)
            r += " und verl?ngert !";
        else if (arg.equals("fix"))
            r = Strings.cut(r.replace(' ', '+'), 40, "+");
        return r;
    }

    public static void runFile(String fn) {
        Reader r = null;
        Tester t = null;
        try {
            r = new FileReader(fn);
        } catch (FileNotFoundException e) {
            sFail("create FileReader " + fn + " caught " + e);
        }
        t = new Tester(r);
        t.run();
        try {
            r.close();
        } catch (IOException e) {
            sFail("close FileReader " + fn + " caught " + e);
        }            
    }
    /**
     * test the test machine of class Test itself
     */

    @SuppressWarnings("unused") 
    public static void main(String[] args) {
        String src = "und wie gehts +;      \n\n        'string  mit ''.' " 
            + "\n und so weiter ** kommentar ?"
            + "\n\n      ** kommentar \n    \n nachher .";
        Tester t = new Tester(new StringReader(src));

//         parseTest();
//        stringTest();
        runFile("testScript");
/*        String [] def1 = {"test", "testEnv", "envVars", "unQu", "bar", "barLazy", "wc", "words", "writeCat", "dir", "parser", "cmpLoad",
                "sSyntax", "sJavaSyntax", "dConst", "dComment", "dString", "dJava", "dVars", "dInp", "dData",
                "sJava", "sString", "sJava", "sString", "sVars",  "sBlock", "sShString",
                "sBar", "sRedirB", "sRedirF", "sHereData" , "sRedirCat", "sRedirRW",
                "sCatAss", "sCatWr", "sRunD", "sRunS", "sReadInto", "sTypeVar", "typeRW", "sTypeRW", "sInDo", "sData", "dFor", "sForColl", "war"};
//        still toDo war, sInDo mit Schachtelung
        String [] def2 = {"sRedirB"};  // {"sTypeRW"};
        String [] def = def1;
        if ( args == null || args.length == 0)
            args = def;
        if (false) {
            String [] a2 = new String [args.length * 2];
            for (int i=0; i<args.length;i++) {
                a2[2*i] = args[i];
                a2[2*i + 1] = args[i];
            }
            args = a2;
        }
        int [] ers = new int [args.length];
        String aa = ":";
        int fE = -1;
        for (int i=0; i<args.length;i++) {
//            ers[i] = t.test(args[i]);
            aa += ' ' + args[i] + "=" + ers[i];
            if (fE < 0 && ers[i] != 0)
                fE = i;
//            if (t.errors > 0)
//                break;
        }
        String to = "test main end " + t.errors + " errors" + (t.errors <= 0 ? "" : ", first " + fE + " " + args[fE]);
        
        if (fE >= 0 && fE < args.length - 2) {
            sSay("replay first error " + fE + ' ' + args[fE]);
//            t.test(args[fE]);
            sSay(to);
        }
        sSay(aa);
        sSay(to);
*/    }

    public void input(String... ins) {
        readIx = -1;
        input.clear();
        Ut.addAll(input, ins);
    }
    public String read() {
        readIx++;
        if (readIx < input.size()) {
            out("--- read " + readIx + ": " + input.get(readIx));
            return input.get(readIx);
        } else {
            out("--- read " + readIx + "<<endOfInput>>");
            return null;
        }
    }

    public void close() {
//        fail("Tester close");
    }

    public void open(String opt) {
//        fail("Tester open");
    }

    public void reset(Object... args) {
        fail("Tester reset");
    }

    public void write(String arg) {
        out(arg);
    }
    public     void writeAll(String opt, Read<String> r) {
        Cat.writeAll(this, opt, r);
    }

    
}