org.apache.asterix.test.aql.TestExecutor.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.asterix.test.aql.TestExecutor.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.asterix.test.aql;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Inet4Address;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.asterix.common.config.GlobalConfig;
import org.apache.asterix.common.utils.ServletUtil.Servlets;
import org.apache.asterix.test.base.ComparisonException;
import org.apache.asterix.test.server.ITestServer;
import org.apache.asterix.test.server.TestServerProvider;
import org.apache.asterix.testframework.context.TestCaseContext;
import org.apache.asterix.testframework.context.TestCaseContext.OutputFormat;
import org.apache.asterix.testframework.context.TestFileContext;
import org.apache.asterix.testframework.xml.TestCase.CompilationUnit;
import org.apache.asterix.testframework.xml.TestGroup;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
import org.apache.http.util.EntityUtils;
import org.apache.hyracks.util.StorageUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class TestExecutor {

    /*
     * Static variables
     */
    protected static final Logger LOGGER = Logger.getLogger(TestExecutor.class.getName());
    // see
    // https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers/417184
    private static final long MAX_URL_LENGTH = 2000l;
    private static final Pattern JAVA_BLOCK_COMMENT_PATTERN = Pattern.compile("/\\*.*\\*/",
            Pattern.MULTILINE | Pattern.DOTALL);
    private static final Pattern REGEX_LINES_PATTERN = Pattern.compile("^(-)?/(.*)/([im]*)$");
    private static final Pattern POLL_TIMEOUT_PATTERN = Pattern.compile("polltimeoutsecs=(\\d+)(\\D|$)",
            Pattern.MULTILINE);
    private static final Pattern POLL_DELAY_PATTERN = Pattern.compile("polldelaysecs=(\\d+)(\\D|$)",
            Pattern.MULTILINE);
    public static final int TRUNCATE_THRESHOLD = 16384;

    private static Method managixExecuteMethod = null;
    private static final HashMap<Integer, ITestServer> runningTestServers = new HashMap<>();

    /*
     * Instance members
     */
    protected final String host;
    protected final int port;
    protected ITestLibrarian librarian;

    public TestExecutor(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public TestExecutor() {
        this(Inet4Address.getLoopbackAddress().getHostAddress(), 19002);
    }

    public void setLibrarian(ITestLibrarian librarian) {
        this.librarian = librarian;
    }

    /**
     * Probably does not work well with symlinks.
     */
    public boolean deleteRec(File path) {
        if (path.isDirectory()) {
            for (File f : path.listFiles()) {
                if (!deleteRec(f)) {
                    return false;
                }
            }
        }
        return path.delete();
    }

    public void runScriptAndCompareWithResult(File scriptFile, PrintWriter print, File expectedFile,
            File actualFile) throws Exception {
        System.err.println("Expected results file: " + expectedFile.toString());
        BufferedReader readerExpected = new BufferedReader(
                new InputStreamReader(new FileInputStream(expectedFile), "UTF-8"));
        BufferedReader readerActual = new BufferedReader(
                new InputStreamReader(new FileInputStream(actualFile), "UTF-8"));
        boolean regex = false;
        try {
            if (actualFile.toString().endsWith(".regex")) {
                runScriptAndCompareWithResultRegex(scriptFile, expectedFile, actualFile);
                return;
            } else if (actualFile.toString().endsWith(".regexadm")) {
                runScriptAndCompareWithResultRegexAdm(scriptFile, expectedFile, actualFile);
                return;
            }
            String lineExpected, lineActual;
            int num = 1;
            while ((lineExpected = readerExpected.readLine()) != null) {
                lineActual = readerActual.readLine();
                // Assert.assertEquals(lineExpected, lineActual);
                if (lineActual == null) {
                    if (lineExpected.isEmpty()) {
                        continue;
                    }
                    throwLineChanged(scriptFile, lineExpected, "<EOF>", num);
                }

                // Comparing result equality but ignore "Time"-prefixed fields. (for metadata tests.)
                String[] lineSplitsExpected = lineExpected.split("Time");
                String[] lineSplitsActual = lineActual.split("Time");
                if (lineSplitsExpected.length != lineSplitsActual.length) {
                    throwLineChanged(scriptFile, lineExpected, lineActual, num);
                }
                if (!equalStrings(lineSplitsExpected[0], lineSplitsActual[0], regex)) {
                    throwLineChanged(scriptFile, lineExpected, lineActual, num);
                }

                for (int i = 1; i < lineSplitsExpected.length; i++) {
                    String[] splitsByCommaExpected = lineSplitsExpected[i].split(",");
                    String[] splitsByCommaActual = lineSplitsActual[i].split(",");
                    if (splitsByCommaExpected.length != splitsByCommaActual.length) {
                        throwLineChanged(scriptFile, lineExpected, lineActual, num);
                    }
                    for (int j = 1; j < splitsByCommaExpected.length; j++) {
                        if (splitsByCommaExpected[j].indexOf("DatasetId") >= 0) {
                            // Ignore the field "DatasetId", which is different for different runs.
                            // (for metadata tests)
                            continue;
                        }
                        if (!equalStrings(splitsByCommaExpected[j], splitsByCommaActual[j], regex)) {
                            throwLineChanged(scriptFile, lineExpected, lineActual, num);
                        }
                    }
                }

                ++num;
            }
            lineActual = readerActual.readLine();
            if (lineActual != null) {
                throwLineChanged(scriptFile, "<EOF>", lineActual, num);
            }
        } catch (Exception e) {
            System.err.println("Actual results file: " + actualFile.toString());
            throw e;
        } finally {
            readerExpected.close();
            readerActual.close();
        }

    }

    private void throwLineChanged(File scriptFile, String lineExpected, String lineActual, int num)
            throws ComparisonException {
        throw new ComparisonException("Result for " + scriptFile + " changed at line " + num + ":\n< "
                + truncateIfLong(lineExpected) + "\n> " + truncateIfLong(lineActual));
    }

    private String truncateIfLong(String string) {
        if (string.length() < TRUNCATE_THRESHOLD) {
            return string;
        }
        final StringBuilder truncatedString = new StringBuilder(string);
        truncatedString.setLength(TRUNCATE_THRESHOLD);
        truncatedString.append("\n<truncated ")
                .append(StorageUtil.toHumanReadableSize(string.length() - TRUNCATE_THRESHOLD)).append("...>");
        return truncatedString.toString();
    }

    private boolean equalStrings(String expected, String actual, boolean regexMatch) {
        String[] rowsExpected = expected.split("\n");
        String[] rowsActual = actual.split("\n");

        for (int i = 0; i < rowsExpected.length; i++) {
            String expectedRow = rowsExpected[i];
            String actualRow = rowsActual[i];

            if (regexMatch) {
                if (actualRow.matches(expectedRow)) {
                    continue;
                }
            } else if (actualRow.equals(expectedRow)) {
                continue;
            }

            String[] expectedFields = expectedRow.split(" ");
            String[] actualFields = actualRow.split(" ");

            boolean bagEncountered = false;
            Set<String> expectedBagElements = new HashSet<>();
            Set<String> actualBagElements = new HashSet<>();

            for (int j = 0; j < expectedFields.length; j++) {
                if (j >= actualFields.length) {
                    return false;
                } else if (expectedFields[j].equals(actualFields[j])) {
                    bagEncountered = expectedFields[j].equals("{{");
                    if (expectedFields[j].startsWith("}}")) {
                        if (regexMatch) {
                            if (expectedBagElements.size() != actualBagElements.size()) {
                                return false;
                            }
                            int[] expectedHits = new int[expectedBagElements.size()];
                            int[] actualHits = new int[actualBagElements.size()];
                            int k = 0;
                            for (String expectedElement : expectedBagElements) {
                                int l = 0;
                                for (String actualElement : actualBagElements) {
                                    if (actualElement.matches(expectedElement)) {
                                        expectedHits[k]++;
                                        actualHits[l]++;
                                    }
                                    l++;
                                }
                                k++;
                            }
                            for (int m = 0; m < expectedHits.length; m++) {
                                if (expectedHits[m] == 0 || actualHits[m] == 0) {
                                    return false;
                                }
                            }
                        } else if (!expectedBagElements.equals(actualBagElements)) {
                            return false;
                        }
                        bagEncountered = false;
                        expectedBagElements.clear();
                        actualBagElements.clear();
                    }
                } else if (expectedFields[j].indexOf('.') < 0) {
                    if (bagEncountered) {
                        expectedBagElements.add(expectedFields[j].replaceAll(",$", ""));
                        actualBagElements.add(actualFields[j].replaceAll(",$", ""));
                        continue;
                    }
                    return false;
                } else {
                    // If the fields are floating-point numbers, test them
                    // for equality safely
                    expectedFields[j] = expectedFields[j].split(",")[0];
                    actualFields[j] = actualFields[j].split(",")[0];
                    try {
                        Double double1 = Double.parseDouble(expectedFields[j]);
                        Double double2 = Double.parseDouble(actualFields[j]);
                        float float1 = (float) double1.doubleValue();
                        float float2 = (float) double2.doubleValue();

                        if (Math.abs(float1 - float2) == 0) {
                            continue;
                        } else {
                            return false;
                        }
                    } catch (NumberFormatException ignored) {
                        // Guess they weren't numbers - must simply not be equal
                        return false;
                    }
                }
            }
        }
        return true;
    }

    public void runScriptAndCompareWithResultRegex(File scriptFile, File expectedFile, File actualFile)
            throws Exception {
        System.err.println("Expected results file: " + expectedFile.toString());
        String lineExpected, lineActual;
        int num = 1;
        try (BufferedReader readerExpected = new BufferedReader(
                new InputStreamReader(new FileInputStream(expectedFile), "UTF-8"));
                BufferedReader readerActual = new BufferedReader(
                        new InputStreamReader(new FileInputStream(actualFile), "UTF-8"))) {
            StringBuilder actual = new StringBuilder();
            while ((lineActual = readerActual.readLine()) != null) {
                actual.append(lineActual).append('\n');
            }
            while ((lineExpected = readerExpected.readLine()) != null) {
                if ("".equals(lineExpected.trim())) {
                    continue;
                }
                Matcher m = REGEX_LINES_PATTERN.matcher(lineExpected);
                if (!m.matches()) {
                    throw new IllegalArgumentException(
                            "Each line of regex file must conform to: [-]/regex/[flags]: " + expectedFile);
                }
                String negateStr = m.group(1);
                String expression = m.group(2);
                String flagStr = m.group(3);
                boolean negate = "-".equals(negateStr);
                int flags = Pattern.MULTILINE;
                if (flagStr.contains("m")) {
                    flags |= Pattern.DOTALL;
                }
                if (flagStr.contains("i")) {
                    flags |= Pattern.CASE_INSENSITIVE;
                }
                Pattern linePattern = Pattern.compile(expression, flags);
                boolean match = linePattern.matcher(actual).find();
                if (match && !negate || negate && !match) {
                    continue;
                }
                throw new Exception("Result for " + scriptFile + ": expected pattern '" + expression
                        + "' not found in result.");
            }
        } catch (Exception e) {
            System.err.println("Actual results file: " + actualFile.toString());
            throw e;
        }

    }

    public void runScriptAndCompareWithResultRegexAdm(File scriptFile, File expectedFile, File actualFile)
            throws Exception {
        StringWriter actual = new StringWriter();
        StringWriter expected = new StringWriter();
        IOUtils.copy(new FileInputStream(actualFile), actual, StandardCharsets.UTF_8);
        IOUtils.copy(new FileInputStream(expectedFile), expected, StandardCharsets.UTF_8);
        Pattern pattern = Pattern.compile(expected.toString(), Pattern.DOTALL | Pattern.MULTILINE);
        if (!pattern.matcher(actual.toString()).matches()) {
            throw new Exception("Result for " + scriptFile + ": actual file did not match expected result");
        }
    }

    // For tests where you simply want the byte-for-byte output.
    private static void writeOutputToFile(File actualFile, InputStream resultStream) throws Exception {
        if (!actualFile.getParentFile().mkdirs()) {
            LOGGER.warning("Unable to create actual file parent dir: " + actualFile.getParentFile());
        }
        try (FileOutputStream out = new FileOutputStream(actualFile)) {
            IOUtils.copy(resultStream, out);
        }
    }

    protected HttpResponse executeAndCheckHttpRequest(HttpUriRequest method) throws Exception {
        return checkResponse(executeHttpRequest(method));
    }

    protected HttpResponse executeHttpRequest(HttpUriRequest method) throws Exception {
        HttpClient client = HttpClients.custom().setRetryHandler(StandardHttpRequestRetryHandler.INSTANCE).build();
        try {
            return client.execute(method);
        } catch (Exception e) {
            GlobalConfig.ASTERIX_LOGGER.log(Level.SEVERE, e.getMessage(), e);
            e.printStackTrace();
            throw e;
        }
    }

    protected HttpResponse checkResponse(HttpResponse httpResponse) throws Exception {
        if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
            String errorBody = EntityUtils.toString(httpResponse.getEntity());
            String exceptionMsg;
            try {
                // First try to parse the response for a JSON error response.

                JSONObject result = new JSONObject(errorBody);
                String[] errors = { result.getJSONArray("error-code").getString(0), result.getString("summary"),
                        result.getString("stacktrace") };
                GlobalConfig.ASTERIX_LOGGER.log(Level.SEVERE, errors[2]);
                exceptionMsg = "HTTP operation failed: " + errors[0] + "\nSTATUS LINE: "
                        + httpResponse.getStatusLine() + "\nSUMMARY: " + errors[1] + "\nSTACKTRACE: " + errors[2];
            } catch (Exception e) {
                // whoops, not JSON (e.g. 404) - just include the body
                GlobalConfig.ASTERIX_LOGGER.log(Level.SEVERE, errorBody);
                exceptionMsg = "HTTP operation failed:" + "\nSTATUS LINE: " + httpResponse.getStatusLine()
                        + "\nERROR_BODY: " + errorBody;
            }
            throw new Exception(exceptionMsg);
        }
        return httpResponse;
    }

    public InputStream executeQuery(String str, OutputFormat fmt, String url,
            List<CompilationUnit.Parameter> params) throws Exception {
        HttpUriRequest method = constructHttpMethod(str, url, "query", false, params);
        // Set accepted output response type
        method.setHeader("Accept", fmt.mimeType());
        HttpResponse response = executeAndCheckHttpRequest(method);
        return response.getEntity().getContent();
    }

    public InputStream executeQueryService(String str, String url) throws Exception {
        return executeQueryService(str, OutputFormat.CLEAN_JSON, url, new ArrayList<>(), false);
    }

    public InputStream executeQueryService(String str, OutputFormat fmt, String url,
            List<CompilationUnit.Parameter> params, boolean jsonEncoded) throws Exception {
        setFormatParam(params, fmt);
        HttpUriRequest method = jsonEncoded ? constructPostMethodJson(str, url, "statement", params)
                : constructPostMethodUrl(str, url, "statement", params);
        // Set accepted output response type
        method.setHeader("Accept", OutputFormat.CLEAN_JSON.mimeType());
        HttpResponse response = executeHttpRequest(method);
        return response.getEntity().getContent();
    }

    protected void setFormatParam(List<CompilationUnit.Parameter> params, OutputFormat fmt) {
        for (CompilationUnit.Parameter param : params) {
            if ("format".equals(param.getName())) {
                param.setValue(fmt.mimeType());
                return;
            }
        }
        CompilationUnit.Parameter formatParam = new CompilationUnit.Parameter();
        formatParam.setName("format");
        formatParam.setValue(fmt.mimeType());
        params.add(formatParam);
    }

    private List<CompilationUnit.Parameter> injectStatement(String statement, String stmtParamName,
            List<CompilationUnit.Parameter> otherParams) {
        CompilationUnit.Parameter stmtParam = new CompilationUnit.Parameter();
        stmtParam.setName(stmtParamName);
        stmtParam.setValue(statement);
        List<CompilationUnit.Parameter> params = new ArrayList<>(otherParams);
        params.add(stmtParam);
        return params;
    }

    private HttpUriRequest constructHttpMethod(String statement, String endpoint, String stmtParam,
            boolean postStmtAsParam, List<CompilationUnit.Parameter> otherParams) {
        if (statement.length() + endpoint.length() < MAX_URL_LENGTH) {
            // Use GET for small-ish queries
            return constructGetMethod(endpoint, injectStatement(statement, stmtParam, otherParams));
        } else {
            // Use POST for bigger ones to avoid 413 FULL_HEAD
            String stmtParamName = (postStmtAsParam ? stmtParam : null);
            return constructPostMethodUrl(statement, endpoint, stmtParamName, otherParams);
        }
    }

    private HttpUriRequest constructGetMethod(String endpoint, List<CompilationUnit.Parameter> params) {
        RequestBuilder builder = RequestBuilder.get(endpoint);
        for (CompilationUnit.Parameter param : params) {
            builder.addParameter(param.getName(), param.getValue());
        }
        builder.setCharset(StandardCharsets.UTF_8);
        return builder.build();
    }

    private HttpUriRequest constructGetMethod(String endpoint, OutputFormat fmt,
            List<CompilationUnit.Parameter> params) {

        HttpUriRequest method = constructGetMethod(endpoint, params);
        // Set accepted output response type
        method.setHeader("Accept", fmt.mimeType());
        return method;
    }

    private HttpUriRequest constructPostMethod(String endpoint, List<CompilationUnit.Parameter> params) {
        RequestBuilder builder = RequestBuilder.post(endpoint);
        for (CompilationUnit.Parameter param : params) {
            builder.addParameter(param.getName(), param.getValue());
        }
        builder.setCharset(StandardCharsets.UTF_8);
        return builder.build();
    }

    private HttpUriRequest constructPostMethod(String endpoint, OutputFormat fmt,
            List<CompilationUnit.Parameter> params) {

        HttpUriRequest method = constructPostMethod(endpoint, params);
        // Set accepted output response type
        method.setHeader("Accept", fmt.mimeType());
        return method;
    }

    protected HttpUriRequest constructPostMethodUrl(String statement, String endpoint, String stmtParam,
            List<CompilationUnit.Parameter> otherParams) {
        RequestBuilder builder = RequestBuilder.post(endpoint);
        if (stmtParam != null) {
            for (CompilationUnit.Parameter param : injectStatement(statement, stmtParam, otherParams)) {
                builder.addParameter(param.getName(), param.getValue());
            }
            builder.addParameter(stmtParam, statement);
        } else {
            // this seems pretty bad - we should probably fix the API and not the client
            builder.setEntity(new StringEntity(statement, StandardCharsets.UTF_8));
        }
        builder.setCharset(StandardCharsets.UTF_8);
        return builder.build();
    }

    protected HttpUriRequest constructPostMethodJson(String statement, String endpoint, String stmtParam,
            List<CompilationUnit.Parameter> otherParams) {
        if (stmtParam == null) {
            throw new NullPointerException("Statement parameter required.");
        }
        RequestBuilder builder = RequestBuilder.post(endpoint);
        JSONObject content = new JSONObject();
        try {
            for (CompilationUnit.Parameter param : injectStatement(statement, stmtParam, otherParams)) {
                content.put(param.getName(), param.getValue());
            }
        } catch (JSONException e) {
            throw new IllegalArgumentException("Request object construction failed.", e);
        }
        builder.setEntity(new StringEntity(content.toString(), ContentType.APPLICATION_JSON));
        builder.setCharset(StandardCharsets.UTF_8);
        return builder.build();
    }

    public InputStream executeJSONGet(OutputFormat fmt, String url) throws Exception {
        HttpUriRequest request = constructGetMethod(url, fmt, new ArrayList<>());
        HttpResponse response = executeAndCheckHttpRequest(request);
        return response.getEntity().getContent();
    }

    public InputStream executeJSONPost(OutputFormat fmt, String url) throws Exception {
        HttpUriRequest request = constructPostMethod(url, fmt, new ArrayList<>());
        HttpResponse response = executeAndCheckHttpRequest(request);
        return response.getEntity().getContent();
    }

    // To execute Update statements
    // Insert and Delete statements are executed here
    public void executeUpdate(String str, String url) throws Exception {
        // Create a method instance.
        HttpUriRequest request = RequestBuilder.post(url).setEntity(new StringEntity(str, StandardCharsets.UTF_8))
                .build();

        // Execute the method.
        executeAndCheckHttpRequest(request);
    }

    // Executes AQL in either async or async-defer mode.
    public InputStream executeAnyAQLAsync(String str, boolean defer, OutputFormat fmt, String url)
            throws Exception {
        // Create a method instance.
        HttpUriRequest request = RequestBuilder.post(url)
                .addParameter("mode", defer ? "asynchronous-deferred" : "asynchronous")
                .setEntity(new StringEntity(str, StandardCharsets.UTF_8)).setHeader("Accept", fmt.mimeType())
                .build();

        HttpResponse response = executeAndCheckHttpRequest(request);
        InputStream resultStream = response.getEntity().getContent();

        String theHandle = IOUtils.toString(resultStream, "UTF-8");

        // take the handle and parse it so results can be retrieved
        return getHandleResult(theHandle, fmt);
    }

    private InputStream getHandleResult(String handle, OutputFormat fmt) throws Exception {
        final String url = getEndpoint(Servlets.QUERY_RESULT);

        // Create a method instance.
        HttpUriRequest request = RequestBuilder.get(url).addParameter("handle", handle)
                .setHeader("Accept", fmt.mimeType()).build();

        HttpResponse response = executeAndCheckHttpRequest(request);
        return response.getEntity().getContent();
    }

    // To execute DDL and Update statements
    // create type statement
    // create dataset statement
    // create index statement
    // create dataverse statement
    // create function statement
    public void executeDDL(String str, String url) throws Exception {
        // Create a method instance.
        HttpUriRequest request = RequestBuilder.post(url).setEntity(new StringEntity(str, StandardCharsets.UTF_8))
                .build();

        // Execute the method.
        executeAndCheckHttpRequest(request);
    }

    // Method that reads a DDL/Update/Query File
    // and returns the contents as a string
    // This string is later passed to REST API for execution.
    public String readTestFile(File testFile) throws Exception {
        BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(testFile), StandardCharsets.UTF_8));
        String line;
        StringBuilder stringBuilder = new StringBuilder();
        String ls = System.getProperty("line.separator");
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
            stringBuilder.append(ls);
        }
        reader.close();
        return stringBuilder.toString();
    }

    public static void executeManagixCommand(String command) throws ClassNotFoundException, NoSuchMethodException,
            SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if (managixExecuteMethod == null) {
            Class<?> clazz = Class.forName("org.apache.asterix.installer.test.AsterixInstallerIntegrationUtil");
            managixExecuteMethod = clazz.getMethod("executeCommand", String.class);
        }
        managixExecuteMethod.invoke(null, command);
    }

    public static String executeScript(ProcessBuilder pb, String scriptPath) throws Exception {
        pb.command(scriptPath);
        Process p = pb.start();
        p.waitFor();
        return getProcessOutput(p);
    }

    private static String executeVagrantScript(ProcessBuilder pb, String node, String scriptName) throws Exception {
        pb.command("vagrant", "ssh", node, "--", pb.environment().get("SCRIPT_HOME") + scriptName);
        Process p = pb.start();
        p.waitFor();
        InputStream input = p.getInputStream();
        return IOUtils.toString(input, StandardCharsets.UTF_8.name());
    }

    private static String executeVagrantManagix(ProcessBuilder pb, String command) throws Exception {
        pb.command("vagrant", "ssh", "cc", "--", pb.environment().get("MANAGIX_HOME") + command);
        Process p = pb.start();
        p.waitFor();
        InputStream input = p.getInputStream();
        return IOUtils.toString(input, StandardCharsets.UTF_8.name());
    }

    private static String getScriptPath(String queryPath, String scriptBasePath, String scriptFileName) {
        String targetWord = "queries" + File.separator;
        int targetWordSize = targetWord.lastIndexOf(File.separator);
        int beginIndex = queryPath.lastIndexOf(targetWord) + targetWordSize;
        int endIndex = queryPath.lastIndexOf(File.separator);
        String prefix = queryPath.substring(beginIndex, endIndex);
        return scriptBasePath + prefix + File.separator + scriptFileName;
    }

    private static String getProcessOutput(Process p) throws Exception {
        StringBuilder s = new StringBuilder();
        BufferedInputStream bisIn = new BufferedInputStream(p.getInputStream());
        StringWriter writerIn = new StringWriter();
        IOUtils.copy(bisIn, writerIn, "UTF-8");
        s.append(writerIn.toString());

        BufferedInputStream bisErr = new BufferedInputStream(p.getErrorStream());
        StringWriter writerErr = new StringWriter();
        IOUtils.copy(bisErr, writerErr, "UTF-8");
        s.append(writerErr.toString());
        if (writerErr.toString().length() > 0) {
            StringBuilder sbErr = new StringBuilder();
            sbErr.append("script execution failed - error message:\n");
            sbErr.append("-------------------------------------------\n");
            sbErr.append(s.toString());
            sbErr.append("-------------------------------------------\n");
            LOGGER.info(sbErr.toString().trim());
            throw new Exception(s.toString().trim());
        }
        return s.toString();
    }

    public void executeTest(String actualPath, TestCaseContext testCaseCtx, ProcessBuilder pb,
            boolean isDmlRecoveryTest) throws Exception {
        executeTest(actualPath, testCaseCtx, pb, isDmlRecoveryTest, null);
    }

    public void executeTest(TestCaseContext testCaseCtx, TestFileContext ctx, String statement,
            boolean isDmlRecoveryTest, ProcessBuilder pb, CompilationUnit cUnit, MutableInt queryCount,
            List<TestFileContext> expectedResultFileCtxs, File testFile, String actualPath) throws Exception {
        File qbcFile;
        boolean failed = false;
        File expectedResultFile;
        switch (ctx.getType()) {
        case "ddl":
            if (ctx.getFile().getName().endsWith("aql")) {
                executeDDL(statement, getEndpoint(Servlets.AQL_DDL));
            } else {
                InputStream resultStream = executeQueryService(statement, getEndpoint(Servlets.QUERY_SERVICE));
                ResultExtractor.extract(resultStream);
            }
            break;
        case "update":
            // isDmlRecoveryTest: set IP address
            if (isDmlRecoveryTest && statement.contains("nc1://")) {
                statement = statement.replaceAll("nc1://", "127.0.0.1://../../../../../../asterix-app/");
            }
            if (ctx.getFile().getName().endsWith("aql")) {
                executeUpdate(statement, getEndpoint(Servlets.AQL_UPDATE));
            } else {
                InputStream resultStream = executeQueryService(statement, getEndpoint(Servlets.QUERY_SERVICE));
                ResultExtractor.extract(resultStream);
            }
            break;
        case "pollquery":
            // polltimeoutsecs=nnn, polldelaysecs=nnn
            final Matcher timeoutMatcher = POLL_TIMEOUT_PATTERN.matcher(statement);
            int timeoutSecs;
            if (timeoutMatcher.find()) {
                timeoutSecs = Integer.parseInt(timeoutMatcher.group(1));
            } else {
                throw new IllegalArgumentException("ERROR: polltimeoutsecs=nnn must be present in poll file");
            }
            final Matcher retryDelayMatcher = POLL_DELAY_PATTERN.matcher(statement);
            int retryDelaySecs = retryDelayMatcher.find() ? Integer.parseInt(timeoutMatcher.group(1)) : 1;
            long startTime = System.currentTimeMillis();
            long limitTime = startTime + TimeUnit.SECONDS.toMillis(timeoutSecs);
            ctx.setType(ctx.getType().substring("poll".length()));
            Exception finalException;
            LOGGER.fine("polling for up to " + timeoutSecs + " seconds w/ " + retryDelaySecs + " second(s) delay");
            while (true) {
                try {
                    executeTest(testCaseCtx, ctx, statement, isDmlRecoveryTest, pb, cUnit, queryCount,
                            expectedResultFileCtxs, testFile, actualPath);
                    finalException = null;
                    break;
                } catch (Exception e) {
                    if ((System.currentTimeMillis() > limitTime)) {
                        finalException = e;
                        break;
                    }
                    LOGGER.fine("sleeping " + retryDelaySecs + " second(s) before polling again");
                    Thread.sleep(TimeUnit.SECONDS.toMillis(retryDelaySecs));
                }
            }
            if (finalException != null) {
                throw new Exception("Poll limit (" + timeoutSecs + "s) exceeded without obtaining expected result",
                        finalException);
            }
            break;
        case "query":
        case "async":
        case "asyncdefer":
            // isDmlRecoveryTest: insert Crash and Recovery
            if (isDmlRecoveryTest) {
                executeScript(pb, pb.environment().get("SCRIPT_HOME") + File.separator + "dml_recovery"
                        + File.separator + "kill_cc_and_nc.sh");
                executeScript(pb, pb.environment().get("SCRIPT_HOME") + File.separator + "dml_recovery"
                        + File.separator + "stop_and_start.sh");
            }
            InputStream resultStream = null;
            OutputFormat fmt = OutputFormat.forCompilationUnit(cUnit);
            if (ctx.getFile().getName().endsWith("aql")) {
                if (ctx.getType().equalsIgnoreCase("query")) {
                    resultStream = executeQuery(statement, fmt, getEndpoint(Servlets.AQL_QUERY),
                            cUnit.getParameter());
                } else if (ctx.getType().equalsIgnoreCase("async")) {
                    resultStream = executeAnyAQLAsync(statement, false, fmt, getEndpoint(Servlets.AQL));
                } else if (ctx.getType().equalsIgnoreCase("asyncdefer")) {
                    resultStream = executeAnyAQLAsync(statement, true, fmt, getEndpoint(Servlets.AQL));
                }
            } else {
                if (ctx.getType().equalsIgnoreCase("query")) {
                    resultStream = executeQueryService(statement, fmt, getEndpoint(Servlets.QUERY_SERVICE),
                            cUnit.getParameter(), true);
                    resultStream = ResultExtractor.extract(resultStream);
                } else if (ctx.getType().equalsIgnoreCase("async")) {
                    resultStream = executeAnyAQLAsync(statement, false, fmt, getEndpoint(Servlets.SQLPP));
                } else if (ctx.getType().equalsIgnoreCase("asyncdefer")) {
                    resultStream = executeAnyAQLAsync(statement, true, fmt, getEndpoint(Servlets.SQLPP));
                }
            }
            if (queryCount.intValue() >= expectedResultFileCtxs.size()) {
                throw new IllegalStateException("no result file for " + testFile.toString() + "; queryCount: "
                        + queryCount + ", filectxs.size: " + expectedResultFileCtxs.size());
            }
            expectedResultFile = expectedResultFileCtxs.get(queryCount.intValue()).getFile();

            File actualResultFile = testCaseCtx.getActualResultFile(cUnit, expectedResultFile,
                    new File(actualPath));
            writeOutputToFile(actualResultFile, resultStream);

            runScriptAndCompareWithResult(testFile, new PrintWriter(System.err), expectedResultFile,
                    actualResultFile);
            queryCount.increment();

            // Deletes the matched result file.
            actualResultFile.getParentFile().delete();
            break;
        case "mgx":
            executeManagixCommand(statement);
            break;
        case "txnqbc": // qbc represents query before crash
            resultStream = executeQuery(statement, OutputFormat.forCompilationUnit(cUnit),
                    getEndpoint(Servlets.AQL_QUERY), cUnit.getParameter());
            qbcFile = getTestCaseQueryBeforeCrashFile(actualPath, testCaseCtx, cUnit);
            writeOutputToFile(qbcFile, resultStream);
            break;
        case "txnqar": // qar represents query after recovery
            resultStream = executeQuery(statement, OutputFormat.forCompilationUnit(cUnit),
                    getEndpoint(Servlets.AQL_QUERY), cUnit.getParameter());
            File qarFile = new File(actualPath + File.separator
                    + testCaseCtx.getTestCase().getFilePath().replace(File.separator, "_") + "_" + cUnit.getName()
                    + "_qar.adm");
            writeOutputToFile(qarFile, resultStream);
            qbcFile = getTestCaseQueryBeforeCrashFile(actualPath, testCaseCtx, cUnit);
            runScriptAndCompareWithResult(testFile, new PrintWriter(System.err), qbcFile, qarFile);
            break;
        case "txneu": // eu represents erroneous update
            try {
                executeUpdate(statement, getEndpoint(Servlets.AQL_UPDATE));
            } catch (Exception e) {
                // An exception is expected.
                failed = true;
                e.printStackTrace();
            }
            if (!failed) {
                throw new Exception("Test \"" + testFile + "\" FAILED!\n  An exception" + "is expected.");
            }
            System.err.println("...but that was expected.");
            break;
        case "script":
            try {
                String output = executeScript(pb, getScriptPath(testFile.getAbsolutePath(),
                        pb.environment().get("SCRIPT_HOME"), statement.trim()));
                if (output.contains("ERROR")) {
                    throw new Exception(output);
                }
            } catch (Exception e) {
                throw new Exception("Test \"" + testFile + "\" FAILED!\n", e);
            }
            break;
        case "sleep":
            String[] lines = statement.split("\n");
            Thread.sleep(Long.parseLong(lines[lines.length - 1].trim()));
            break;
        case "errddl": // a ddlquery that expects error
            try {
                executeDDL(statement, getEndpoint(Servlets.AQL_DDL));
            } catch (Exception e) {
                // expected error happens
                failed = true;
                e.printStackTrace();
            }
            if (!failed) {
                throw new Exception("Test \"" + testFile + "\" FAILED!\n  An exception is expected.");
            }
            System.err.println("...but that was expected.");
            break;
        case "vscript": // a script that will be executed on a vagrant virtual node
            try {
                String[] command = statement.trim().split(" ");
                if (command.length != 2) {
                    throw new Exception("invalid vagrant script format");
                }
                String nodeId = command[0];
                String scriptName = command[1];
                String output = executeVagrantScript(pb, nodeId, scriptName);
                if (output.contains("ERROR")) {
                    throw new Exception(output);
                }
            } catch (Exception e) {
                throw new Exception("Test \"" + testFile + "\" FAILED!\n", e);
            }
            break;
        case "vmgx": // a managix command that will be executed on vagrant cc node
            String output = executeVagrantManagix(pb, statement);
            if (output.contains("ERROR")) {
                throw new Exception(output);
            }
            break;
        case "get":
        case "post":
            if (!"http".equals(ctx.extension())) {
                throw new IllegalArgumentException(
                        "Unexpected format for method " + ctx.getType() + ": " + ctx.extension());
            }
            fmt = OutputFormat.forCompilationUnit(cUnit);
            String endpoint = stripJavaComments(statement).trim();
            switch (ctx.getType()) {
            case "get":
                resultStream = executeJSONGet(fmt, "http://" + host + ":" + port + endpoint);
                break;
            case "post":
                resultStream = executeJSONPost(fmt, "http://" + host + ":" + port + endpoint);
                break;
            default:
                throw new IllegalStateException("NYI: " + ctx.getType());
            }
            expectedResultFile = expectedResultFileCtxs.get(queryCount.intValue()).getFile();
            actualResultFile = testCaseCtx.getActualResultFile(cUnit, expectedResultFile, new File(actualPath));
            writeOutputToFile(actualResultFile, resultStream);
            runScriptAndCompareWithResult(testFile, new PrintWriter(System.err), expectedResultFile,
                    actualResultFile);
            queryCount.increment();
            break;
        case "server": // (start <test server name> <port>
                       // [<arg1>][<arg2>][<arg3>]...|stop (<port>|all))
            try {
                lines = statement.trim().split("\n");
                String[] command = lines[lines.length - 1].trim().split(" ");
                if (command.length < 2) {
                    throw new Exception("invalid server command format. expected format ="
                            + " (start <test server name> <port> [<arg1>][<arg2>][<arg3>]"
                            + "...|stop (<port>|all))");
                }
                String action = command[0];
                if (action.equals("start")) {
                    if (command.length < 3) {
                        throw new Exception("invalid server start command. expected format ="
                                + " (start <test server name> <port> [<arg1>][<arg2>][<arg3>]...");
                    }
                    String name = command[1];
                    Integer port = new Integer(command[2]);
                    if (runningTestServers.containsKey(port)) {
                        throw new Exception("server with port " + port + " is already running");
                    }
                    ITestServer server = TestServerProvider.createTestServer(name, port);
                    server.configure(Arrays.copyOfRange(command, 3, command.length));
                    server.start();
                    runningTestServers.put(port, server);
                } else if (action.equals("stop")) {
                    String target = command[1];
                    if (target.equals("all")) {
                        for (ITestServer server : runningTestServers.values()) {
                            server.stop();
                        }
                        runningTestServers.clear();
                    } else {
                        Integer port = new Integer(command[1]);
                        ITestServer server = runningTestServers.get(port);
                        if (server == null) {
                            throw new Exception("no server is listening to port " + port);
                        }
                        server.stop();
                        runningTestServers.remove(port);
                    }
                } else {
                    throw new Exception("unknown server action");
                }
            } catch (Exception e) {
                throw new Exception("Test \"" + testFile + "\" FAILED!\n", e);
            }
            break;
        case "lib": // expected format <dataverse-name> <library-name>
                    // <library-directory>
                    // TODO: make this case work well with entity names containing spaces by
                    // looking for \"
            lines = statement.split("\n");
            String lastLine = lines[lines.length - 1];
            String[] command = lastLine.trim().split(" ");
            if (command.length < 3) {
                throw new Exception("invalid library format");
            }
            String dataverse = command[1];
            String library = command[2];
            switch (command[0]) {
            case "install":
                if (command.length != 4) {
                    throw new Exception("invalid library format");
                }
                String libPath = command[3];
                librarian.install(dataverse, library, libPath);
                break;
            case "uninstall":
                if (command.length != 3) {
                    throw new Exception("invalid library format");
                }
                librarian.uninstall(dataverse, library);
                break;
            default:
                throw new Exception("invalid library format");
            }
            break;
        default:
            throw new IllegalArgumentException("No statements of type " + ctx.getType());
        }
    }

    public void executeTest(String actualPath, TestCaseContext testCaseCtx, ProcessBuilder pb,
            boolean isDmlRecoveryTest, TestGroup failedGroup) throws Exception {
        File testFile;
        String statement;
        List<TestFileContext> expectedResultFileCtxs;
        List<TestFileContext> testFileCtxs;
        MutableInt queryCount = new MutableInt(0);
        int numOfErrors = 0;
        int numOfFiles = 0;
        List<CompilationUnit> cUnits = testCaseCtx.getTestCase().getCompilationUnit();
        for (CompilationUnit cUnit : cUnits) {
            LOGGER.info("Starting [TEST]: " + testCaseCtx.getTestCase().getFilePath() + "/" + cUnit.getName()
                    + " ... ");
            testFileCtxs = testCaseCtx.getTestFiles(cUnit);
            expectedResultFileCtxs = testCaseCtx.getExpectedResultFiles(cUnit);
            for (TestFileContext ctx : testFileCtxs) {
                numOfFiles++;
                testFile = ctx.getFile();
                statement = readTestFile(testFile);
                try {
                    executeTest(testCaseCtx, ctx, statement, isDmlRecoveryTest, pb, cUnit, queryCount,
                            expectedResultFileCtxs, testFile, actualPath);
                } catch (Exception e) {
                    System.err.println("testFile " + testFile.toString() + " raised an exception: " + e);
                    boolean unExpectedFailure = false;
                    numOfErrors++;
                    String expectedError = null;
                    if (cUnit.getExpectedError().size() < numOfErrors) {
                        unExpectedFailure = true;
                    } else {
                        // Get the expected exception
                        expectedError = cUnit.getExpectedError().get(numOfErrors - 1);
                        if (e.toString().contains(expectedError)) {
                            System.err.println("...but that was expected.");
                        } else {
                            unExpectedFailure = true;
                        }
                    }
                    if (unExpectedFailure) {
                        e.printStackTrace();
                        System.err.println("...Unexpected!");
                        if (expectedError != null) {
                            System.err.println("Expected to find the following in error text:\n+++++\n"
                                    + expectedError + "\n+++++");
                        }
                        if (failedGroup != null) {
                            failedGroup.getTestCase().add(testCaseCtx.getTestCase());
                        }
                        throw new Exception("Test \"" + testFile + "\" FAILED!", e);
                    }
                } finally {
                    if (numOfFiles == testFileCtxs.size() && numOfErrors < cUnit.getExpectedError().size()) {
                        System.err.println("...Unexpected!");
                        Exception e = new Exception(
                                "Test \"" + cUnit.getName() + "\" FAILED!\nExpected error was not thrown...");
                        e.printStackTrace();
                        throw e;
                    } else if (numOfFiles == testFileCtxs.size()) {
                        LOGGER.info("[TEST]: " + testCaseCtx.getTestCase().getFilePath() + "/" + cUnit.getName()
                                + " PASSED ");
                    }
                }
            }
        }
    }

    private static File getTestCaseQueryBeforeCrashFile(String actualPath, TestCaseContext testCaseCtx,
            CompilationUnit cUnit) {
        return new File(
                actualPath + File.separator + testCaseCtx.getTestCase().getFilePath().replace(File.separator, "_")
                        + "_" + cUnit.getName() + "_qbc.adm");
    }

    protected String getPath(Servlets servlet) {
        return servlet.getPath();
    }

    protected String getEndpoint(Servlets servlet) {
        return "http://" + host + ":" + port + getPath(servlet).replaceAll("/\\*$", "");
    }

    public static String stripJavaComments(String text) {
        return JAVA_BLOCK_COMMENT_PATTERN.matcher(text).replaceAll("");
    }

    public void cleanup(String testCase, List<String> badtestcases) throws Exception {
        try {
            ArrayList<String> toBeDropped = new ArrayList<>();
            InputStream resultStream = executeQueryService(
                    "select dv.DataverseName from Metadata.`Dataverse` as dv;",
                    getEndpoint(Servlets.QUERY_SERVICE));
            resultStream = ResultExtractor.extract(resultStream);
            StringWriter sw = new StringWriter();
            IOUtils.copy(resultStream, sw, StandardCharsets.UTF_8.name());
            JSONArray result = new JSONArray(sw.toString());
            for (int i = 0; i < result.length(); ++i) {
                JSONObject json = result.getJSONObject(i);
                String dvName = json.getString("DataverseName");
                if (!dvName.equals("Metadata") && !dvName.equals("Default")) {
                    toBeDropped.add(dvName);
                }
            }
            if (!toBeDropped.isEmpty()) {
                badtestcases.add(testCase);
                LOGGER.warning(
                        "Last test left some garbage. Dropping dataverses: " + StringUtils.join(toBeDropped, ','));
                StringBuilder dropStatement = new StringBuilder();
                for (String dv : toBeDropped) {
                    dropStatement.append("drop dataverse ");
                    dropStatement.append(dv);
                    dropStatement.append(";\n");
                }
                resultStream = executeQueryService(dropStatement.toString(), getEndpoint(Servlets.QUERY_SERVICE));
                ResultExtractor.extract(resultStream);
            }
        } catch (Throwable th) {
            th.printStackTrace();
            throw th;
        }
    }
}