Mercurial > hg > monetdb-java
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); + } + } +}