+= PollQuery Test File Type

pollquery test type files will run the query every polldelaysecs
[default:1] seconds for up to polltimeoutsecs seconds or until the
correct result is found.  This avoids unnecessarily long or dangerously
short sleeps to wait for some asynchronous operation to complete.

Usage: polltimeoutsecs=nnn must be present somewhere in your file,
otherwise an error is thrown.  Optionally, polldelaysecs=nnn can be also
present to override the default poll frequency of 1s.

Change-Id: I7e4c67c74debf8253479257a1c54d6426a9531d8
Reviewed-on: https://asterix-gerrit.ics.uci.edu/1267
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Michael Blow <mblow@apache.org>
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-external-function/feed-with-external-function.4.sleep.aql b/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-external-function/feed-with-external-function.4.sleep.aql
deleted file mode 100644
index af2f691..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-external-function/feed-with-external-function.4.sleep.aql
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * 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.
- */
-10000
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-external-function/feed-with-external-function.5.query.aql b/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-external-function/feed-with-external-function.5.pollquery.aql
similarity index 98%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-external-function/feed-with-external-function.5.query.aql
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-external-function/feed-with-external-function.5.pollquery.aql
index 8879fa8..f095804 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-external-function/feed-with-external-function.5.query.aql
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries/feeds/feed-with-external-function/feed-with-external-function.5.pollquery.aql
@@ -25,6 +25,7 @@
  * Expected Res : Success
  * Date         : 23rd Apr 2013
  */
+// polltimeoutsecs=30
 use dataverse externallibtest;
 
 for $x in dataset TweetsFeedIngest
diff --git a/asterixdb/asterix-common/src/test/java/org/apache/asterix/test/aql/TestExecutor.java b/asterixdb/asterix-common/src/test/java/org/apache/asterix/test/aql/TestExecutor.java
index 08a0342..29c0afd 100644
--- a/asterixdb/asterix-common/src/test/java/org/apache/asterix/test/aql/TestExecutor.java
+++ b/asterixdb/asterix-common/src/test/java/org/apache/asterix/test/aql/TestExecutor.java
@@ -37,6 +37,7 @@
 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;
@@ -81,6 +82,10 @@
     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<>();
 
@@ -658,6 +663,42 @@
                     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":