| /* |
| * 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.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); |
| |
| 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")) { |
| regex = true; |
| } |
| 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; |
| } |
| throw new ComparisonException( |
| "Result for " + scriptFile + " changed at line " + num + ":\n< " + lineExpected + "\n> "); |
| } |
| |
| // 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) { |
| throw new ComparisonException( |
| "Result for " + scriptFile + " changed at line " + num + ":\n< " + lineExpected |
| + "\n> " + lineActual); |
| } |
| if (!equalStrings(lineSplitsExpected[0], lineSplitsActual[0], regex)) { |
| throw new ComparisonException( |
| "Result for " + scriptFile + " changed at line " + num + ":\n< " + lineExpected |
| + "\n> " + lineActual); |
| } |
| |
| for (int i = 1; i < lineSplitsExpected.length; i++) { |
| String[] splitsByCommaExpected = lineSplitsExpected[i].split(","); |
| String[] splitsByCommaActual = lineSplitsActual[i].split(","); |
| if (splitsByCommaExpected.length != splitsByCommaActual.length) { |
| throw new ComparisonException("Result for " + scriptFile + " changed at line " + num + ":\n< " |
| + lineExpected + "\n> " + lineActual); |
| } |
| 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)) { |
| throw new ComparisonException( |
| "Result for " + scriptFile + " changed at line " + num + ":\n< " |
| + lineExpected + "\n> " + lineActual); |
| } |
| } |
| } |
| |
| ++num; |
| } |
| lineActual = readerActual.readLine(); |
| if (lineActual != null) { |
| throw new ComparisonException( |
| "Result for " + scriptFile + " changed at line " + num + ":\n< \n> " + lineActual); |
| } |
| } catch (Exception e) { |
| System.err.println("Actual results file: " + actualFile.toString()); |
| throw e; |
| } finally { |
| readerExpected.close(); |
| readerActual.close(); |
| } |
| |
| } |
| |
| 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; |
| } |
| |
| } |
| |
| // For tests where you simply want the byte-for-byte output. |
| private static void writeOutputToFile(File actualFile, InputStream resultStream) throws Exception { |
| 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 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(statement, endpoint, 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 statement, String endpoint, String stmtParam, |
| List<CompilationUnit.Parameter> otherParams) { |
| RequestBuilder builder = RequestBuilder.get(endpoint).addParameter(stmtParam, statement); |
| for (CompilationUnit.Parameter param : otherParams) { |
| builder.addParameter(param.getName(), param.getValue()); |
| } |
| builder.setCharset(StandardCharsets.UTF_8); |
| return builder.build(); |
| } |
| |
| 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 : 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 { |
| content.put(stmtParam, statement); |
| for (CompilationUnit.Parameter param : 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 executeClusterStateQuery(OutputFormat fmt, String url) throws Exception { |
| HttpUriRequest request = RequestBuilder.get(url).setHeader("Accept", fmt.mimeType()).build(); |
| |
| 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)); |
| actualResultFile.getParentFile().mkdirs(); |
| 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); |
| qbcFile.getParentFile().mkdirs(); |
| 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"); |
| qarFile.getParentFile().mkdirs(); |
| 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 "cstate": // cluster state query |
| fmt = OutputFormat.forCompilationUnit(cUnit); |
| String extra = stripJavaComments(statement).trim(); |
| resultStream = executeClusterStateQuery(fmt, getEndpoint(Servlets.CLUSTER_STATE) + extra); |
| expectedResultFile = expectedResultFileCtxs.get(queryCount.intValue()).getFile(); |
| actualResultFile = testCaseCtx.getActualResultFile(cUnit, expectedResultFile, new File(actualPath)); |
| actualResultFile.getParentFile().mkdirs(); |
| writeOutputToFile(actualResultFile, resultStream); |
| runScriptAndCompareWithResult(testFile, new PrintWriter(System.err), expectedResultFile, |
| actualResultFile); |
| queryCount.increment(); |
| break; |
| case "version": // version servlet |
| fmt = OutputFormat.forCompilationUnit(cUnit); |
| resultStream = executeClusterStateQuery(fmt, getEndpoint(Servlets.VERSION)); |
| expectedResultFile = expectedResultFileCtxs.get(queryCount.intValue()).getFile(); |
| actualResultFile = testCaseCtx.getActualResultFile(cUnit, expectedResultFile, new File(actualPath)); |
| actualResultFile.getParentFile().mkdirs(); |
| 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); |
| } |
| |
| 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; |
| } |
| } |
| } |