view tests/UrlTester.java @ 789:88c5b678e974 monetdbs

URL parser passes the tests. Some tests had to change to accomodate Java.
author Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com>
date Wed, 29 Nov 2023 13:28:52 +0100 (17 months ago)
parents
children 4de810c22328
line wrap: on
line source
import org.monetdb.mcl.net.*;

import java.io.*;
import java.net.URISyntaxException;
import java.util.Properties;

public class UrlTester {
    String filename = null;
    int verbose = 0;
    BufferedReader reader = null;
    int lineno = 0;
    int testCount = 0;
    Properties props = null;
    Target validated = null;

    public UrlTester() {
    }

    public UrlTester(String filename) {
        this.filename = filename;
    }

    public static void main(String[] args) throws Exception {
        int exitcode;
        UrlTester tester = new UrlTester();
        exitcode = tester.parseArgs(args);
        if (exitcode == 0)
            exitcode = tester.run();
        System.exit(exitcode);
    }

    private int parseArgs(String[] args) {
        for (int i = 0; i < args.length; i++) {
            String arg = args[i];
            if (arg.startsWith("-")) {
                int result = handleFlags(arg);
                if (result != 0)
                    return result;
            } else if (filename == null) {
                filename = arg;
            } else {
                System.err.println("Unexpected argument: " + arg);
                return 1;
            }
        }
        return 0;
    }

    private int run() throws IOException {
        if (filename != null) {
            reader = new BufferedReader(new FileReader(filename));
        } else {
            String resourceName = "/tests.md";
            InputStream stream = this.getClass().getResourceAsStream(resourceName);
            if (stream == null) {
                System.err.println("Resource " + resourceName + " not found");
                return 1;
            }
            reader = new BufferedReader(new InputStreamReader(stream));
            filename = "tests/tests.md";
        }

        try {
            processFile();
        } catch (Failure e) {
            System.err.println("" + filename + ":" + lineno + ": " + e.getMessage());
            return 1;
        }
        return 0;
    }

    private int handleFlags(String arg) {
        if (!arg.startsWith("-") || arg.equals("-")) {
            System.err.println("Invalid flag: " + arg);
        }
        String a = arg.substring(1);

        while (!a.isEmpty()) {
            char letter = a.charAt(0);
            a = a.substring(1);
            switch (letter) {
                case 'v':
                    verbose++;
                    break;
                default:
                    System.err.println("Unexpected flag " + letter + " in " + arg);
                    return -1;
            }
        }

        return 0;
    }

    private void processFile() throws IOException, Failure {
        while (true) {
            String line = reader.readLine();
            if (line == null)
                break;
            lineno++;
            processLine(line);
        }
        if (verbose >= 1) {
            System.out.println();
            System.out.println("Ran " + testCount + " tests in " + lineno + " lines");
        }
    }

    private void processLine(String line) throws Failure {
        line = line.replaceFirst("\\s+$", ""); // remove trailing
        if (props == null && line.equals("```test")) {
            if (verbose >= 2) {
                if (testCount > 0) {
                    System.out.println();
                }
                System.out.println("\u25B6 " + filename + ":" + lineno);
            }
            props = Target.defaultProperties();
            testCount++;
            return;
        }
        if (props != null) {
            if (line.equals("```")) {
                stopProcessing();
                return;
            }
            handleCommand(line);
        }
    }

    private void stopProcessing() {
        props = null;
        validated = null;
    }

    private void handleCommand(String line) throws Failure {
        if (verbose >= 3) {
            System.out.println(line);
        }
        if (line.isEmpty())
            return;

        String[] parts = line.split("\\s+", 2);
        String command = parts[0];
        switch (command.toUpperCase()) {
            case "ONLY":
                handleOnly(true, parts[1]);
                return;
            case "NOT":
                handleOnly(false, parts[1]);
                return;
            case "PARSE":
                handleParse(parts[1], null);
                return;
            case "ACCEPT":
                handleParse(parts[1], true);
                return;
            case "REJECT":
                handleParse(parts[1], false);
                return;
            case "SET":
                handleSet(parts[1]);
                return;
            case "EXPECT":
                handleExpect(parts[1]);
                return;
            default:
                throw new Failure("Unexpected command: " + command);
        }

    }

    private void handleOnly(boolean mustBePresent, String rest) throws Failure {
        boolean found = false;
        for (String part: rest.split("\\s+")) {
            if (part.equals("jdbc")) {
                found = true;
                break;
            }
        }
        if (found != mustBePresent) {
            // do not further process this block
            stopProcessing();
        }
    }

    private int findEqualSign(String rest) throws Failure {
        int index = rest.indexOf('=');
        if (index < -1)
            throw new Failure("Expected to find a '='");
        return index;
    }

    private String splitKey(String rest) throws Failure {
        int index = findEqualSign(rest);
        return rest.substring(0, index);
    }

    private String splitValue(String rest) throws Failure {
        int index = findEqualSign(rest);
        return rest.substring(index + 1);
    }

    private void handleSet(String rest) throws Failure {
        validated = null;
        String key = splitKey(rest);
        String value = splitValue(rest);

        props.put(key, value);
    }

    private void handleParse(String rest, Boolean shouldSucceed) throws Failure {
        URISyntaxException parseError = null;
        ValidationError validationError = null;

        validated = null;
        try {
            MonetUrlParser.parse(props, rest);
        } catch (URISyntaxException e) {
            parseError = e;
        }

        if (parseError == null) {
            try {
                validated = new Target(props);
            } catch (ValidationError e) {
                validationError = e;
            }
        }

        if (shouldSucceed == Boolean.FALSE) {
            if (parseError != null || validationError != null)
                return; // happy
            else
                throw new Failure("URL unexpectedly parsed and validated");
        }

        if (parseError != null)
            throw new Failure("Parse error: " + parseError);
        if (validationError != null && shouldSucceed == Boolean.TRUE)
            throw new Failure("Validation error: " + validationError);
    }

    private void handleExpect(String rest) throws Failure {
        String key = splitKey(rest);
        String expectedString = splitValue(rest);

        Object actual = null;
        try {
            actual = extract(key);
        } catch (ValidationError e) {
            throw new Failure(e.getMessage());
        }

        Object expected;
        try {
            if (actual instanceof Boolean)
                expected = ParameterType.Bool.parse(key, expectedString);
            else if (actual instanceof Integer)
                expected = ParameterType.Int.parse(key, expectedString);
            else
                expected = expectedString;
        } catch (ValidationError e) {
            String typ = actual.getClass().getName();
            throw new Failure("Cannot convert expected value <" + expectedString + "> to " + typ + ": " + e.getMessage());
        }

        if (actual.equals(expected))
            return;
        throw new Failure("Expected " + key + "=<" + expectedString + ">, found <" + actual + ">");
    }

    private Target tryValidate() throws ValidationError {
        if (validated == null)
            validated = new Target(props);
        return validated;
    }

    private Object extract(String key) throws ValidationError, Failure {
        switch (key) {
            case "tls":
                return Target.validateBoolean(props, Parameter.TLS);
            case "host":
                return Target.validateString(props, Parameter.HOST);
            case "port":
                return Target.validateInt(props, Parameter.PORT);
            case "database":
                return Target.validateString(props, Parameter.DATABASE);
            case "tableschema":
                return Target.validateString(props, Parameter.TABLESCHEMA);
            case "table":
                return Target.validateString(props, Parameter.TABLE);
            case "sock":
                return Target.validateString(props, Parameter.SOCK);
            case "sockdir":
                return Target.validateString(props, Parameter.SOCKDIR);
            case "cert":
                return Target.validateString(props, Parameter.CERT);
            case "certhash":
                return Target.validateString(props, Parameter.CERTHASH);
            case "clientkey":
                return Target.validateString(props, Parameter.CLIENTKEY);
            case "clientcert":
                return Target.validateString(props, Parameter.CLIENTCERT);
            case "user":
                return Target.validateString(props, Parameter.USER);
            case "password":
                return Target.validateString(props, Parameter.PASSWORD);
            case "language":
                return Target.validateString(props, Parameter.LANGUAGE);
            case "autocommit":
                return Target.validateBoolean(props, Parameter.AUTOCOMMIT);
            case "schema":
                return Target.validateString(props, Parameter.SCHEMA);
            case "timezone":
                return Target.validateInt(props, Parameter.TIMEZONE);
            case "binary":
                return Target.validateString(props, Parameter.BINARY);
            case "replysize":
                return Target.validateInt(props, Parameter.REPLYSIZE);
            case "fetchsize":
                return Target.validateInt(props, Parameter.FETCHSIZE);
            case "hash":
                return Target.validateString(props, Parameter.HASH);
            case "debug":
                return Target.validateBoolean(props, Parameter.DEBUG);
            case "logfile":
                return Target.validateString(props, Parameter.LOGFILE);

            case "valid":
                try {
                    tryValidate();
                } catch (ValidationError e) {
                    return Boolean.FALSE;
                }
                return Boolean.TRUE;

            case "connect_scan":
                return tryValidate().connectScan();
            case "connect_port":
                return tryValidate().connectPort();
            case "connect_unix":
                return tryValidate().connectUnix();
            case "connect_tcp":
                return tryValidate().connectTcp();
            case "connect_tls_verify":
                switch (tryValidate().connectVerify()) {
                    case None: return "";
                    case Cert: return "cert";
                    case Hash: return "hash";
                    case System: return "system";
                    default:
                        throw new IllegalStateException("unreachable");
                }
            case "connect_certhash_digits":
                return tryValidate().connectCertHashDigits();
            case "connect_binary":
                return tryValidate().connectBinary();
            case "connect_clientkey":
                return tryValidate().connectClientKey();
            case "connect_clientcert":
                return tryValidate().connectClientCert();

            default:
                throw new Failure("Unknown attribute: " + key);
        }
    }

    private class Failure extends Exception {
        public Failure(String message) {
            super(message);
        }
    }
}