[ASTERIXDB-2614][TEST] Make type computer test argument count aware

- user model changes: no
- storage format changes: no
- interface changes: no

Details:
- Obtained the number of arguments from each function's arity to
  know how many arguments to pass for each type computer instead
  of constantly passing only 2 arguments.
- Limited the number of arguments to be passed in case of VARARGS
  or functions with arguments more than 3, this could lead to the
  test taking too long and timing out and failing.

Change-Id: I7188d92101990a6b232c0915f127fbfbf3f6f841
Reviewed-on: https://asterix-gerrit.ics.uci.edu/3500
Sonar-Qube: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Hussain Towaileb <hussainht@gmail.com>
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
diff --git a/asterixdb/asterix-om/src/test/java/org/apache/asterix/test/om/typecomputer/ExceptionTest.java b/asterixdb/asterix-om/src/test/java/org/apache/asterix/test/om/typecomputer/ExceptionTest.java
index 95e669f..91752f7 100644
--- a/asterixdb/asterix-om/src/test/java/org/apache/asterix/test/om/typecomputer/ExceptionTest.java
+++ b/asterixdb/asterix-om/src/test/java/org/apache/asterix/test/om/typecomputer/ExceptionTest.java
@@ -26,9 +26,12 @@
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
+import org.apache.asterix.om.functions.BuiltinFunctions;
 import org.apache.asterix.om.typecomputer.base.IResultTypeComputer;
 import org.apache.asterix.om.types.ATypeTag;
 import org.apache.asterix.om.types.BuiltinType;
@@ -40,6 +43,7 @@
 import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IVariableTypeEnvironment;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.core.algebra.functions.IFunctionInfo;
 import org.apache.hyracks.algebricks.core.algebra.metadata.IMetadataProvider;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
@@ -59,18 +63,25 @@
  *
  * Care needs to be taken when deciding the number of arguments, below is how the tests are calculated:
  * The number of combinations is = n ^ m
- * n = number of arguments
- * m = number of types available
+ * n = number of types available
+ * m = number of arguments to pass
  *
  * Example:
- * 2 arguments, 40 types = 1600 combinations per type computer
- * 3 arguments, 40 types = 64000 combinations per type computer
+ * 2 arguments, 40 types = 40 ^ 2 = 1,600 combinations per type computer
+ * 3 arguments, 40 types = 40 ^ 3 = 64,000 combinations per type computer
+ * 4 arguments, 40 types = 40 ^ 4 = 2,560,000 combinations per type computer
  *
- * TODO There are 2 functions that expect a minimum arguments of 3, so passing them 2 arguments will cause them
- * to return an exception, so they're really not being tested properly now. Probably need to add an exception list
- * for such functions.
- * Ideally, making the arguments in the test to be 3 will solve the issue, but that would push the tests to 64000 each
- * and that takes too long and times out the test apparently.
+ * Choice of number of arguments:
+ * 1- To know the proper number of arguments for each type computer, we are going over all the available functions,
+ *    getting their arity and their type computer and comparing them as follows:
+ *     - If a function has VARARGS, that type computer is marked as VARARGS.
+ *     - If multiple functions use the same type computer, the highest arity of all functions is used for that computer.
+ * 2- In case of VARARGS or type computers that handle more than 3 arguments, we will use only 3 arguments, for the
+ *    following reasons:
+ *     - Going over 3 arguments will result in over 2,500,000 test cases, which will time out and fail the test.
+ *     - (As of now) all the type computers using VARARGS need a minimum of 3 arguments or less to function properly,
+ *       so passing 3 arguments will suffice to include all cases.
+ *     - (As of now) all the type computers that handle more than 3 arguments return a constant type, nothing to test.
  */
 
 @RunWith(Parameterized.class)
@@ -78,9 +89,11 @@
 
     private static final Logger LOGGER = LogManager.getLogger();
 
-    // Number of arguments to be passed. Number of combinations depends on number of arguments
-    private static int numberOfArguments = 2;
-    private static int numberOfCombinations = (int) Math.pow(ATypeTag.values().length, numberOfArguments);
+    // Caution: Using more than 3 arguments will generate millions of combinations and the test will timeout and fail
+    private static final int MAXIMUM_ARGUMENTS_ALLOWED = 3;
+
+    // This map will hold each used type computer and its highest arity, which represents arguments number to pass
+    private static Map<String, Integer> typeComputerToArgsCountMap = new HashMap<>();
 
     // Test parameters
     @Parameter
@@ -95,6 +108,55 @@
         // Prepare tests
         List<Object[]> tests = new ArrayList<>();
 
+        // First, we will go over all the functions available, get their arity and type computers to know the number
+        // of arguments to pass for each type computer
+        Map<IFunctionInfo, IResultTypeComputer> funTypeComputerMap;
+        try {
+            Field field = BuiltinFunctions.class.getDeclaredField("funTypeComputer");
+            field.setAccessible(true);
+            funTypeComputerMap = (HashMap<IFunctionInfo, IResultTypeComputer>) field.get(null);
+        } catch (Exception ex) {
+            // this should never fail
+            throw new IllegalStateException();
+        }
+
+        if (funTypeComputerMap != null) {
+            // Note: If a type computer handles both known and VARARGS, VARARGS will be used
+            for (HashMap.Entry<IFunctionInfo, IResultTypeComputer> entry : funTypeComputerMap.entrySet()) {
+                // Some functions have a null type computer for some reason, ignore them
+                if (entry.getValue() == null) {
+                    continue;
+                }
+
+                // Type computer does not exist, add it with its arity to the map
+                if (typeComputerToArgsCountMap.get(entry.getValue().getClass().getSimpleName()) == null) {
+                    typeComputerToArgsCountMap.put(entry.getValue().getClass().getSimpleName(),
+                            entry.getKey().getFunctionIdentifier().getArity());
+                    continue;
+                }
+
+                // VARARGS functions, these are kept as is, put/update, no need for any comparison
+                if (entry.getKey().getFunctionIdentifier().getArity() == FunctionIdentifier.VARARGS) {
+                    typeComputerToArgsCountMap.put(entry.getValue().getClass().getSimpleName(),
+                            entry.getKey().getFunctionIdentifier().getArity());
+                    continue;
+                }
+
+                // We have it already, and it is of type VARARGS, we are not going to change it
+                if (typeComputerToArgsCountMap
+                        .get(entry.getValue().getClass().getSimpleName()) == FunctionIdentifier.VARARGS) {
+                    continue;
+                }
+
+                // We have it already, if it has larger arity than our existing one, we will update it
+                if (typeComputerToArgsCountMap.get(entry.getValue().getClass().getSimpleName()) < entry.getKey()
+                        .getFunctionIdentifier().getArity()) {
+                    typeComputerToArgsCountMap.put(entry.getValue().getClass().getSimpleName(),
+                            entry.getKey().getFunctionIdentifier().getArity());
+                }
+            }
+        }
+
         // Tests all usual type computers.
         Reflections reflections = new Reflections("org.apache.asterix.om.typecomputer", new SubTypesScanner(false));
         Set<Class<? extends IResultTypeComputer>> classes = reflections.getSubTypesOf(IResultTypeComputer.class);
@@ -113,8 +175,27 @@
 
     @Test
     public void test() throws Exception {
+        // If a type computer is not found in the map, then it is not used by any function
+        if (typeComputerToArgsCountMap.get(clazz.getSimpleName()) == null) {
+            LOGGER.log(Level.INFO, "TypeComputer " + clazz.getSimpleName() + " is not used by any functions");
+            return;
+        }
 
+        // Refer to documentation on the top for the details of choosing the arguments count
         // Arguments list
+        int typeComputerMaxArgs = typeComputerToArgsCountMap.get(clazz.getSimpleName());
+
+        // If type computer takes zero arguments, there is nothing to do
+        if (typeComputerMaxArgs == 0) {
+            return;
+        }
+
+        // Calculate number of arguments and number of combinations
+        int numberOfArguments =
+                (typeComputerMaxArgs > MAXIMUM_ARGUMENTS_ALLOWED || typeComputerMaxArgs == FunctionIdentifier.VARARGS)
+                        ? MAXIMUM_ARGUMENTS_ALLOWED : typeComputerMaxArgs;
+        int numberOfCombinations = (int) Math.pow(ATypeTag.values().length, numberOfArguments);
+
         Object[] opaqueParameters = new Object[numberOfArguments];
         List<Mutable<ILogicalExpression>> argumentsList = new ArrayList<>(numberOfArguments);
         for (int i = 0; i < numberOfArguments; i++) {