Repository: calcite
Updated Branches:
  refs/heads/master 37a6f9f4f -> d9486e6c3


[CALCITE-2504] RexProgrammFuzzyTest: fuzzer for RexNodes


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/d9486e6c
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/d9486e6c
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/d9486e6c

Branch: refs/heads/master
Commit: d9486e6c31d8c86a5fb3f703351958d633bc55fd
Parents: 37a6f9f
Author: Vladimir Sitnikov <[email protected]>
Authored: Tue Aug 7 11:03:49 2018 +0300
Committer: Vladimir Sitnikov <[email protected]>
Committed: Mon Sep 17 23:10:20 2018 +0300

----------------------------------------------------------------------
 .../org/apache/calcite/test/CalciteSuite.java   |   2 +
 .../apache/calcite/test/fuzzer/RexFuzzer.java   | 216 ++++++++++++
 .../test/fuzzer/RexProgramFuzzyTest.java        | 342 +++++++++++++++++++
 .../test/fuzzer/RexToTestCodeShuttle.java       | 148 ++++++++
 .../calcite/test/fuzzer/SimplifyTask.java       |  46 +++
 5 files changed, 754 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/d9486e6c/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java 
b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
index 4896411..2c16620 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
@@ -50,6 +50,7 @@ import org.apache.calcite.sql.type.SqlTypeUtilTest;
 import org.apache.calcite.sql.validate.LexCaseSensitiveTest;
 import org.apache.calcite.sql.validate.SqlValidatorUtilTest;
 import org.apache.calcite.test.enumerable.EnumerableCorrelateTest;
+import org.apache.calcite.test.fuzzer.RexProgramFuzzyTest;
 import org.apache.calcite.tools.FrameworksTest;
 import org.apache.calcite.tools.PlannerTest;
 import org.apache.calcite.util.BitSetsTest;
@@ -163,6 +164,7 @@ import org.junit.runners.Suite;
     LinqFrontJdbcBackTest.class,
     JdbcFrontJdbcBackLinqMiddleTest.class,
     CalciteSqlOperatorTest.class,
+    RexProgramFuzzyTest.class,
     ProfilerTest.class,
     LatticeTest.class,
     ReflectiveSchemaTest.class,

http://git-wip-us.apache.org/repos/asf/calcite/blob/d9486e6c/core/src/test/java/org/apache/calcite/test/fuzzer/RexFuzzer.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/fuzzer/RexFuzzer.java 
b/core/src/test/java/org/apache/calcite/test/fuzzer/RexFuzzer.java
new file mode 100644
index 0000000..7851db2
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/test/fuzzer/RexFuzzer.java
@@ -0,0 +1,216 @@
+/*
+ * 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.calcite.test.fuzzer;
+
+import org.apache.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.test.RexProgramBuilderBase;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.function.Function;
+
+/**
+ * Generates random {@link RexNode} instances for tests.
+ */
+public class RexFuzzer extends RexProgramBuilderBase {
+  private static final int MAX_VARS = 2;
+
+  private static final SqlOperator[] BOOL_TO_BOOL = {
+      SqlStdOperatorTable.NOT,
+      SqlStdOperatorTable.IS_TRUE,
+      SqlStdOperatorTable.IS_FALSE,
+      SqlStdOperatorTable.IS_NOT_TRUE,
+      SqlStdOperatorTable.IS_NOT_FALSE,
+  };
+
+  private static final SqlOperator[] ANY_TO_BOOL = {
+      SqlStdOperatorTable.IS_NULL,
+      SqlStdOperatorTable.IS_NOT_NULL,
+      SqlStdOperatorTable.IS_UNKNOWN,
+      SqlStdOperatorTable.IS_NOT_UNKNOWN,
+  };
+
+  private static final SqlOperator[] COMPARABLE_TO_BOOL = {
+      SqlStdOperatorTable.EQUALS,
+      SqlStdOperatorTable.NOT_EQUALS,
+      SqlStdOperatorTable.GREATER_THAN,
+      SqlStdOperatorTable.GREATER_THAN_OR_EQUAL,
+      SqlStdOperatorTable.LESS_THAN,
+      SqlStdOperatorTable.LESS_THAN_OR_EQUAL,
+      SqlStdOperatorTable.IS_DISTINCT_FROM,
+      SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
+  };
+
+  private static final SqlOperator[] BOOL_TO_BOOL_MULTI_ARG = {
+      SqlStdOperatorTable.OR,
+      SqlStdOperatorTable.AND,
+      SqlStdOperatorTable.COALESCE,
+  };
+
+  private static final SqlOperator[] ANY_SAME_TYPE_MULTI_ARG = {
+      SqlStdOperatorTable.COALESCE,
+  };
+
+  private static final SqlOperator[] NUMERIC_TO_NUMERIC = {
+      SqlStdOperatorTable.PLUS,
+      SqlStdOperatorTable.MINUS,
+      SqlStdOperatorTable.MULTIPLY,
+      // Divide by zero is not allowed, so we do not generate divide
+//      SqlStdOperatorTable.DIVIDE,
+//      SqlStdOperatorTable.DIVIDE_INTEGER,
+  };
+
+  private static final SqlOperator[] UNARY_NUMERIC = {
+      SqlStdOperatorTable.UNARY_MINUS,
+      SqlStdOperatorTable.UNARY_PLUS,
+  };
+
+
+  private static final int[] INT_VALUES = {-1, 0, 1, 100500};
+
+  private final RelDataType intType;
+  private final RelDataType nullableIntType;
+
+  /**
+   * Generates randomized {@link RexNode}.
+   *
+   * @param rexBuilder  builder to be used to create nodes
+   * @param typeFactory type factory
+   */
+  public RexFuzzer(RexBuilder rexBuilder, JavaTypeFactory typeFactory) {
+    setUp();
+    this.rexBuilder = rexBuilder;
+    this.typeFactory = typeFactory;
+
+    intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
+    nullableIntType = typeFactory.createTypeWithNullability(intType, true);
+  }
+
+  private int getInputRefId(int base, boolean nullable) {
+    return base + (nullable ? 100 : 0);
+  }
+
+  public RexNode getExpression(Random r, int depth) {
+    return getComparableExpression(r, depth);
+  }
+
+  private RexNode fuzzOperator(Random r, SqlOperator[] operators, RexNode... 
args) {
+    return rexBuilder.makeCall(operators[r.nextInt(operators.length)], args);
+  }
+
+  private RexNode fuzzOperator(Random r, SqlOperator[] operators, int length,
+      Function<Random, RexNode> factory) {
+    List<RexNode> args = new ArrayList<>(length);
+    for (int i = 0; i < length; i++) {
+      args.add(factory.apply(r));
+    }
+    return rexBuilder.makeCall(operators[r.nextInt(operators.length)], args);
+  }
+
+  public RexNode getComparableExpression(Random r, int depth) {
+    int v = r.nextInt(2);
+    switch (v) {
+    case 0:
+      return getBoolExpression(r, depth);
+    case 1:
+      return getIntExpression(r, depth);
+    }
+    throw new AssertionError("should not reach here");
+  }
+
+  public RexNode getSimpleBool(Random r) {
+    int v = r.nextInt(2);
+    switch (v) {
+    case 0:
+      boolean nullable = r.nextBoolean();
+      int field = r.nextInt(MAX_VARS);
+      return nullable ? vBool(field) : vBoolNotNull(field);
+    case 1:
+      return r.nextBoolean() ? trueLiteral : falseLiteral;
+    case 2:
+      return nullBool;
+    }
+    throw new AssertionError("should not reach here");
+  }
+
+  public RexNode getBoolExpression(Random r, int depth) {
+    int v = depth <= 0 ? 0 : r.nextInt(6);
+    switch (v) {
+    case 0:
+      return getSimpleBool(r);
+    case 1:
+      return fuzzOperator(r, ANY_TO_BOOL, getExpression(r, depth - 1));
+    case 2:
+      return fuzzOperator(r, BOOL_TO_BOOL, getBoolExpression(r, depth - 1));
+    case 3:
+      return fuzzOperator(r, COMPARABLE_TO_BOOL, getBoolExpression(r, depth - 
1),
+          getBoolExpression(r, depth - 1));
+    case 4:
+      return fuzzOperator(r, COMPARABLE_TO_BOOL, getIntExpression(r, depth - 
1),
+          getIntExpression(r, depth - 1));
+    case 5:
+      return fuzzOperator(r, BOOL_TO_BOOL_MULTI_ARG, r.nextInt(3) + 2,
+          x -> getBoolExpression(x, depth - 1));
+    }
+    throw new AssertionError("should not reach here");
+  }
+
+  public RexNode getSimpleInt(Random r) {
+    int v = r.nextInt(3);
+    switch (v) {
+    case 0:
+      boolean nullable = r.nextBoolean();
+      int field = r.nextInt(MAX_VARS);
+      return nullable ? vInt(field) : vIntNotNull(field);
+    case 1: {
+      int i = r.nextInt(INT_VALUES.length + 1);
+      int val = i < INT_VALUES.length ? INT_VALUES[i] : r.nextInt();
+      return rexBuilder.makeLiteral(val, r.nextBoolean() ? intType : 
nullableIntType, false);
+    }
+    case 2:
+      return nullInt;
+    }
+    throw new AssertionError("should not reach here");
+  }
+
+  public RexNode getIntExpression(Random r, int depth) {
+    int v = depth <= 0 ? 0 : r.nextInt(4);
+    switch (v) {
+    case 0:
+      return getSimpleInt(r);
+    case 1:
+      return fuzzOperator(r, UNARY_NUMERIC, getIntExpression(r, depth - 1));
+    case 2:
+      return fuzzOperator(r, NUMERIC_TO_NUMERIC, getIntExpression(r, depth - 
1),
+          getIntExpression(r, depth - 1));
+    case 3:
+      return fuzzOperator(r, ANY_SAME_TYPE_MULTI_ARG, r.nextInt(3) + 2,
+          x -> getIntExpression(x, depth - 1));
+    }
+    throw new AssertionError("should not reach here");
+  }
+
+}
+
+// End RexFuzzer.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/d9486e6c/core/src/test/java/org/apache/calcite/test/fuzzer/RexProgramFuzzyTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/calcite/test/fuzzer/RexProgramFuzzyTest.java 
b/core/src/test/java/org/apache/calcite/test/fuzzer/RexProgramFuzzyTest.java
new file mode 100644
index 0000000..159e88d
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/test/fuzzer/RexProgramFuzzyTest.java
@@ -0,0 +1,342 @@
+/*
+ * 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.calcite.test.fuzzer;
+
+import org.apache.calcite.plan.Strong;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.test.RexProgramBuilderBase;
+import org.apache.calcite.util.ImmutableBitSet;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Validates that {@link org.apache.calcite.rex.RexSimplify} is able to deal 
with
+ * randomized {@link RexNode}.
+ * Note: the default fuzzing time is 5 seconds to keep overall test duration 
reasonable.
+ * The test starts from a random point every time, so the longer it runs the 
more errors it detects.
+ *
+ * <p>Note: The test is not included to {@link 
org.apache.calcite.test.CalciteSuite} since it would
+ * fail every build (there are lots of issues with {@link 
org.apache.calcite.rex.RexSimplify})
+ */
+public class RexProgramFuzzyTest extends RexProgramBuilderBase {
+  protected static final Logger LOGGER =
+      LoggerFactory.getLogger(RexProgramFuzzyTest.class);
+
+  private static final int TEST_DURATION = 
Integer.getInteger("rex.fuzzing.duration", 5);
+  private static final long TEST_ITERATIONS = 
Long.getLong("rex.fuzzing.iterations", 5);
+  // Stop fuzzing after detecting MAX_FAILURES errors
+  private static final int MAX_FAILURES =
+      Integer.getInteger("rex.fuzzing.max.failures", 1);
+  // Number of slowest to simplify expressions to show
+  private static final int TOPN_SLOWEST =
+      Integer.getInteger("rex.fuzzing.max.slowest", 0);
+  // 0 means use random seed
+  // 42 is used to make sure tests pass in CI
+  private static final long SEED =
+      Long.getLong("rex.fuzzing.seed", 43);
+
+  private PriorityQueue<SimplifyTask> slowestTasks;
+
+  private long currentSeed = 0;
+
+  private static final Strong STRONG = Strong.of(ImmutableBitSet.of());
+
+  /**
+   * A bounded variation of {@link PriorityQueue}
+   *
+   * @param <E> the type of elements held in this collection
+   */
+  private static class TopN<E extends Comparable<E>> extends PriorityQueue<E> {
+    private final int n;
+
+    private TopN(int n) {
+      this.n = n;
+    }
+
+    @Override public boolean offer(E o) {
+      if (size() == n) {
+        E peek = peek();
+        if (peek != null && peek.compareTo(o) > 0) {
+          // If the smallest element in the queue exceeds the added one
+          // then just ignore the offer
+          return false;
+        }
+        // otherwise extract the smallest element, and offer a new one
+        poll();
+      }
+      return super.offer(o);
+    }
+
+    @Override public Iterator<E> iterator() {
+      throw new UnsupportedOperationException("Order of elements is not 
defined, please use .peek");
+    }
+  }
+
+  @Before public void setUp() {
+    super.setUp();
+  }
+
+  /**
+   * Verifies {@code IS TRUE(IS NULL(null))} kind of expressions up to 4 level 
deep.
+   */
+  @Ignore("[CALCITE-2556] RexSimplify: not(trueLiteral) could be simplified to 
false")
+  @Test public void testNestedCalls() {
+    nestedCalls(trueLiteral);
+    nestedCalls(falseLiteral);
+    nestedCalls(nullBool);
+    nestedCalls(vBool());
+    nestedCalls(vBoolNotNull());
+  }
+
+  private void nestedCalls(RexNode arg) {
+    SqlOperator[] operators = {
+        SqlStdOperatorTable.NOT,
+        SqlStdOperatorTable.IS_FALSE,
+        SqlStdOperatorTable.IS_NOT_FALSE,
+        SqlStdOperatorTable.IS_TRUE,
+        SqlStdOperatorTable.IS_NOT_TRUE,
+        SqlStdOperatorTable.IS_NULL,
+        SqlStdOperatorTable.IS_NOT_NULL,
+        SqlStdOperatorTable.IS_UNKNOWN,
+        SqlStdOperatorTable.IS_NOT_UNKNOWN
+    };
+    for (SqlOperator op1 : operators) {
+      RexNode n1 = rexBuilder.makeCall(op1, arg);
+      checkTrueFalse(n1);
+      for (SqlOperator op2 : operators) {
+        RexNode n2 = rexBuilder.makeCall(op2, n1);
+        checkTrueFalse(n2);
+        for (SqlOperator op3 : operators) {
+          RexNode n3 = rexBuilder.makeCall(op3, n2);
+          checkTrueFalse(n3);
+          for (SqlOperator op4 : operators) {
+            RexNode n4 = rexBuilder.makeCall(op4, n3);
+            checkTrueFalse(n4);
+          }
+        }
+      }
+    }
+  }
+
+
+  private void checkTrueFalse(RexNode node) {
+    checkTrueFalse(node, true);
+    checkTrueFalse(node, false);
+  }
+
+  private void checkTrueFalse(RexNode node, boolean unknownAsFalse) {
+    RexNode opt;
+    String uaf = unknownAsFalse ? "unknownAsFalse " : "";
+    try {
+      long start = System.nanoTime();
+      opt = this.simplify.withUnknownAsFalse(unknownAsFalse).simplify(node);
+      long end = System.nanoTime();
+      if (end - start > 1000 && slowestTasks != null) {
+        slowestTasks.add(new SimplifyTask(node, currentSeed, opt, end - 
start));
+      }
+    } catch (AssertionError a) {
+      String message = a.getMessage();
+      if (message != null && message.startsWith("result mismatch")) {
+        throw a;
+      }
+      throw new IllegalStateException("Unable to simplify " + uaf + 
nodeToString(node), a);
+    } catch (Throwable t) {
+      throw new IllegalStateException("Unable to simplify " + uaf + 
nodeToString(node), t);
+    }
+    if (trueLiteral.equals(opt) && node.isAlwaysFalse()) {
+      String msg = nodeToString(node);
+      fail(msg + " optimizes to TRUE, isAlwaysFalse MUST not be true " + uaf);
+//      This is a missing optimization, not a bug
+//      assertFalse(msg + " optimizes to TRUE, isAlwaysTrue MUST be true",
+//          !node.isAlwaysTrue());
+    }
+    if (falseLiteral.equals(opt) && node.isAlwaysTrue()) {
+      String msg = nodeToString(node);
+      fail(msg + " optimizes to FALSE, isAlwaysTrue MUST not be true " + uaf);
+//      This is a missing optimization, not a bug
+//      assertFalse(msg + " optimizes to FALSE, isAlwaysFalse MUST be true",
+//          !node.isAlwaysFalse());
+    }
+    if (STRONG.isNull(opt)) {
+      if (node.isAlwaysTrue()) {
+        fail(nodeToString(node) + " optimizes to NULL: " + nodeToString(opt)
+            + ", isAlwaysTrue MUST be FALSE " + uaf);
+      }
+      if (node.isAlwaysFalse()) {
+        fail(nodeToString(node) + " optimizes to NULL: " + nodeToString(opt)
+            + ", isAlwaysFalse MUST be FALSE " + uaf);
+      }
+    }
+    if (node.isAlwaysTrue()) {
+      if (!trueLiteral.equals(opt)) {
+        assertEquals(nodeToString(node) + " isAlwaysTrue, so it should 
simplify to TRUE "
+                + uaf,
+            trueLiteral, opt);
+      }
+    }
+    if (node.isAlwaysFalse()) {
+      if (!falseLiteral.equals(opt)) {
+        assertEquals(nodeToString(node) + " isAlwaysFalse, so it should 
simplify to FALSE "
+                + uaf,
+            falseLiteral, opt);
+      }
+    }
+    if (STRONG.isNull(node)) {
+      if (unknownAsFalse) {
+        if (!falseLiteral.equals(opt)) {
+          assertEquals(nodeToString(node)
+                  + " is always null, so it should simplify to FALSE " + uaf,
+              falseLiteral, opt);
+        }
+      } else {
+        if (!RexUtil.isNull(opt)) {
+          assertEquals(nodeToString(node)
+                  + " is always null, so it should simplify to NULL " + uaf,
+              nullBool, opt);
+        }
+      }
+    }
+  }
+
+  private static String nodeToString(RexNode node) {
+    return node + "\n"
+        + node.accept(new RexToTestCodeShuttle());
+  }
+
+  @Test public void testFuzzy() {
+    if (TEST_DURATION == 0) {
+      return;
+    }
+    slowestTasks = new TopN<>(TOPN_SLOWEST);
+    Random r = new Random();
+    if (SEED != 0) {
+      LOGGER.info("Using seed {} for rex fuzzing", SEED);
+      r.setSeed(SEED);
+    }
+    long start = System.currentTimeMillis();
+    long deadline = start + TimeUnit.SECONDS.toMillis(TEST_DURATION);
+    List<Throwable> exceptions = new ArrayList<>();
+    Set<String> duplicates = new HashSet<>();
+    long total = 0;
+    int dup = 0;
+    int fail = 0;
+    RexFuzzer fuzzer = new RexFuzzer(rexBuilder, typeFactory);
+    while (System.currentTimeMillis() < deadline && exceptions.size() < 
MAX_FAILURES
+        && (TEST_ITERATIONS == 0 || total < TEST_ITERATIONS)) {
+      long seed = r.nextLong();
+      this.currentSeed = seed;
+      r.setSeed(seed);
+      try {
+        total++;
+        generateRexAndCheckTrueFalse(fuzzer, r);
+      } catch (Throwable e) {
+        if (!duplicates.add(e.getMessage())) {
+          dup++;
+          // known exception, nothing to see here
+          continue;
+        }
+        fail++;
+        StackTraceElement[] stackTrace = e.getStackTrace();
+        for (int j = 0; j < stackTrace.length; j++) {
+          if (stackTrace[j].getClassName().endsWith("RexProgramTest")) {
+            e.setStackTrace(Arrays.copyOf(stackTrace, j + 1));
+            break;
+          }
+        }
+        e.addSuppressed(new Throwable("seed " + seed) {
+          @Override public synchronized Throwable fillInStackTrace() {
+            return this;
+          }
+        });
+        exceptions.add(e);
+      }
+    }
+    long rate = total * 1000 / (System.currentTimeMillis() - start);
+    LOGGER.info(
+        "Rex fuzzing results: number of cases tested={}, failed cases={}, 
duplicate failures={}, fuzz rate={} per second",
+        total, fail, dup, rate);
+
+    if (TOPN_SLOWEST > 0) {
+      LOGGER.info("The 5 slowest to simplify nodes were");
+      SimplifyTask task;
+      RexToTestCodeShuttle v = new RexToTestCodeShuttle();
+      while ((task = slowestTasks.poll()) != null) {
+        LOGGER.info(task.duration / 1000 + " us (" + task.seed + ")");
+        LOGGER.info("      " + task.node.toString());
+        LOGGER.info("      " + task.node.accept(v));
+        LOGGER.info("    =>" + task.result.toString());
+      }
+    }
+
+    if (exceptions.isEmpty()) {
+      return;
+    }
+
+    // Print the shortest fails first
+    exceptions.sort(
+        Comparator.
+            <Throwable>comparingInt(t -> t.getMessage() == null ? -1 : 
t.getMessage().length())
+            .thenComparing(Throwable::getMessage));
+
+    for (int i = 0; i < exceptions.size() && i < 100; i++) {
+      Throwable exception = exceptions.get(i);
+      exception.printStackTrace();
+    }
+
+    Throwable ex = exceptions.get(0);
+    if (ex instanceof Error) {
+      throw (Error) ex;
+    }
+    throw new RuntimeException("Exception in testFuzzy", ex);
+  }
+
+  private void generateRexAndCheckTrueFalse(RexFuzzer fuzzer, Random r) {
+    RexNode expression = fuzzer.getExpression(r, r.nextInt(10));
+    checkTrueFalse(expression);
+  }
+
+  @Ignore("This is just a scaffold for quick investigation of a single fuzz 
test")
+  @Test public void singleFuzzyTest() {
+    Random r = new Random();
+    r.setSeed(-179916965778405462L);
+    RexFuzzer fuzzer = new RexFuzzer(rexBuilder, typeFactory);
+    generateRexAndCheckTrueFalse(fuzzer, r);
+  }
+}
+
+// End RexProgramFuzzyTest.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/d9486e6c/core/src/test/java/org/apache/calcite/test/fuzzer/RexToTestCodeShuttle.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/calcite/test/fuzzer/RexToTestCodeShuttle.java 
b/core/src/test/java/org/apache/calcite/test/fuzzer/RexToTestCodeShuttle.java
new file mode 100644
index 0000000..ad60eae
--- /dev/null
+++ 
b/core/src/test/java/org/apache/calcite/test/fuzzer/RexToTestCodeShuttle.java
@@ -0,0 +1,148 @@
+/*
+ * 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.calcite.test.fuzzer;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexFieldAccess;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexVisitorImpl;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlTypeName;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Converts {@link RexNode} into a string form usable for inclusion into
+ * {@link RexProgramFuzzyTest}.
+ * For instance, it converts {@code AND(=(?0.bool0, true), =(?0.bool1, true))} 
to
+ * {@code isTrue(and(eq(vBool(0), trueLiteral), eq(vBool(1), trueLiteral)))}.
+ */
+public class RexToTestCodeShuttle extends RexVisitorImpl<String> {
+  private static final Map<SqlOperator, String> OP_METHODS =
+      ImmutableMap.<SqlOperator, String>builder()
+          .put(SqlStdOperatorTable.AND, "and")
+          .put(SqlStdOperatorTable.OR, "or")
+          .put(SqlStdOperatorTable.COALESCE, "coalesce")
+          .put(SqlStdOperatorTable.IS_NULL, "isNull")
+          .put(SqlStdOperatorTable.IS_NOT_NULL, "isNotNull")
+          .put(SqlStdOperatorTable.IS_UNKNOWN, "isUnknown")
+          .put(SqlStdOperatorTable.IS_TRUE, "isTrue")
+          .put(SqlStdOperatorTable.IS_NOT_TRUE, "isNotTrue")
+          .put(SqlStdOperatorTable.IS_FALSE, "isFalse")
+          .put(SqlStdOperatorTable.IS_NOT_FALSE, "isNotFalse")
+          .put(SqlStdOperatorTable.IS_DISTINCT_FROM, "isDistinctFrom")
+          .put(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM, "isNotDistinctFrom")
+          .put(SqlStdOperatorTable.NULLIF, "nullIf")
+          .put(SqlStdOperatorTable.NOT, "not")
+          .put(SqlStdOperatorTable.GREATER_THAN, "gt")
+          .put(SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, "ge")
+          .put(SqlStdOperatorTable.LESS_THAN, "lt")
+          .put(SqlStdOperatorTable.LESS_THAN_OR_EQUAL, "le")
+          .put(SqlStdOperatorTable.EQUALS, "eq")
+          .put(SqlStdOperatorTable.NOT_EQUALS, "ne")
+          .put(SqlStdOperatorTable.PLUS, "plus")
+          .put(SqlStdOperatorTable.UNARY_PLUS, "unaryPlus")
+          .put(SqlStdOperatorTable.MINUS, "sub")
+          .put(SqlStdOperatorTable.UNARY_MINUS, "unaryMinus")
+          .build();
+
+
+  protected RexToTestCodeShuttle() {
+    super(true);
+  }
+
+  @Override public String visitCall(RexCall call) {
+    SqlOperator operator = call.getOperator();
+    String method = OP_METHODS.get(operator);
+
+    StringBuilder sb = new StringBuilder();
+    if (method != null) {
+      sb.append(method);
+      sb.append('(');
+    } else {
+      sb.append("rexBuilder.makeCall(");
+      sb.append("SqlStdOperatorTable.");
+      sb.append(operator.getName().replace(' ', '_'));
+      sb.append(", ");
+    }
+    List<RexNode> operands = call.getOperands();
+    for (int i = 0; i < operands.size(); i++) {
+      RexNode operand = operands.get(i);
+      if (i > 0) {
+        sb.append(", ");
+      }
+      sb.append(operand.accept(this));
+    }
+    sb.append(')');
+    return sb.toString();
+  }
+
+  @Override public String visitLiteral(RexLiteral literal) {
+    RelDataType type = literal.getType();
+
+    if (type.getSqlTypeName() == SqlTypeName.BOOLEAN) {
+      if (literal.isNull()) {
+        return "nullBool";
+      }
+      return literal.toString() + "Literal";
+    }
+    if (type.getSqlTypeName() == SqlTypeName.INTEGER) {
+      if (literal.isNull()) {
+        return "nullInt";
+      }
+      return "literal(" + literal.getValue() + ")";
+    }
+    if (type.getSqlTypeName() == SqlTypeName.VARCHAR) {
+      if (literal.isNull()) {
+        return "nullVarchar";
+      }
+    }
+    return "/*" + literal.getTypeName().getName() + "*/" + literal.toString();
+  }
+
+  @Override public String visitFieldAccess(RexFieldAccess fieldAccess) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("v");
+    RelDataType type = fieldAccess.getType();
+    switch (type.getSqlTypeName()) {
+    case BOOLEAN:
+      sb.append("Bool");
+      break;
+    case INTEGER:
+      sb.append("Int");
+      break;
+    case VARCHAR:
+      sb.append("Varchar");
+      break;
+    }
+    if (!type.isNullable()) {
+      sb.append("NotNull");
+    }
+    sb.append("(");
+    sb.append(fieldAccess.getField().getIndex() % 10);
+    sb.append(")");
+    return sb.toString();
+  }
+}
+
+// End RexToTestCodeShuttle.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/d9486e6c/core/src/test/java/org/apache/calcite/test/fuzzer/SimplifyTask.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/calcite/test/fuzzer/SimplifyTask.java 
b/core/src/test/java/org/apache/calcite/test/fuzzer/SimplifyTask.java
new file mode 100644
index 0000000..ed3ae89
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/test/fuzzer/SimplifyTask.java
@@ -0,0 +1,46 @@
+/*
+ * 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.calcite.test.fuzzer;
+
+import org.apache.calcite.rex.RexNode;
+
+/**
+ * Tracks rex nodes used in {@link RexProgramFuzzyTest} to identify the ones
+ * which take most time to simplify.
+ */
+class SimplifyTask implements Comparable<SimplifyTask> {
+  public final RexNode node;
+  public final long seed;
+  public final RexNode result;
+  public final long duration;
+
+  SimplifyTask(RexNode node, long seed, RexNode result, long duration) {
+    this.node = node;
+    this.seed = seed;
+    this.result = result;
+    this.duration = duration;
+  }
+
+  @Override public int compareTo(SimplifyTask o) {
+    if (duration != o.duration) {
+      return Long.compare(duration, o.duration);
+    }
+    return Integer.compare(node.toString().length(), 
o.node.toString().length());
+  }
+}
+
+// End SimplifyTask.java

Reply via email to