You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by vl...@apache.org on 2018/09/18 08:04:57 UTC
calcite git commit: [CALCITE-2504] RexProgrammFuzzyTest: fuzzer for
RexNodes
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 <si...@gmail.com>
Authored: Tue Aug 7 11:03:49 2018 +0300
Committer: Vladimir Sitnikov <si...@gmail.com>
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