diff 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 diff
new file mode 100644
--- /dev/null
+++ b/tests/UrlTester.java
@@ -0,0 +1,373 @@
+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);
+        }
+    }
+}