view tests/UrlTester.java @ 793:5bfe3357fb1c monetdbs

Use the new url parser
author Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com>
date Wed, 06 Dec 2023 16:17:13 +0100 (17 months ago)
parents 9dea0795a926
children 9f6fe96c0ead
line wrap: on
line source
import org.monetdb.mcl.net.*;

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

public class UrlTester {
    String filename = null;
    int verbose = 0;
    BufferedReader reader = null;
    int lineno = 0;
    int testCount = 0;
    Target target = null;
    Target.Validated validated = null;

    public UrlTester() {
    }

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

    public static void main(String[] args) throws Exception {
        checkDefaults();
        checkParameters();

        int exitcode;
        UrlTester tester = new UrlTester();
        exitcode = tester.parseArgs(args);
        if (exitcode == 0)
            exitcode = tester.run();
        System.exit(exitcode);
    }


    private static void checkDefaults() {
        Target target = new Target();

        for (Parameter parm: Parameter.values()) {
            Object expected = parm.getDefault();
            if (expected == null)
                continue;
            Object actual = target.getObject(parm);
            if (!expected.equals(actual)) {
                throw new RuntimeException("Default for " + parm.name + " expected to be <" + expected + "> but is <" + actual + ">");
            }
        }
    }

    private static void checkParameters() {
        for (Parameter parm: Parameter.values()) {
            Parameter found = Parameter.forName(parm.name);
            if (parm != found) {
                String foundStr = found != null ? found.name : "null";
                throw new RuntimeException("Looking up <" + parm.name + ">, found <" + foundStr);
            }
        }
    }

    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 (target == null && line.equals("```test")) {
            if (verbose >= 2) {
                if (testCount > 0) {
                    System.out.println();
                }
                System.out.println("\u25B6 " + filename + ":" + lineno);
            }
            target = new Target();
            testCount++;
            return;
        }
        if (target != null) {
            if (line.equals("```")) {
                stopProcessing();
                return;
            }
            handleCommand(line);
        }
    }

    private void stopProcessing() {
        target = 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);

        try {
            target.setString(key, value);
        } catch (ValidationError e) {
            throw new Failure(e.getMessage());
        }
    }

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

        validated = null;
        try {
            target.barrier();
            MonetUrlParser.parse(target, rest);
        } catch (URISyntaxException e) {
            parseError = e;
        } catch (ValidationError e) {
            validationError = e;
        }

        if (parseError == null && validationError == null) {
            try {
                tryValidate();
            } 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.Validated tryValidate() throws ValidationError {
        if (validated == null)
            validated = target.validate();
        return validated;
    }

    private Object extract(String key) throws ValidationError, Failure {
        switch (key) {
            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:
                Parameter parm = Parameter.forName(key);
                if (parm != null)
                    return target.getObject(parm);
                else
                    throw new Failure("Unknown attribute: " + key);
        }
    }

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