Repository: calcite
Updated Branches:
  refs/heads/master fe3b65f50 -> fb8ebd315


[CALCITE-2197] Fix test failures on Windows due to line endings

Add several new matchers in Matchers: isLinux, containsStringLinux,
hasTree, compose. Change tests that generate platform-specific strings
to use them.


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

Branch: refs/heads/master
Commit: fb8ebd31526cd2e203fa3526ca8de7a22ee0d139
Parents: fe3b65f
Author: Julian Hyde <jh...@apache.org>
Authored: Wed Feb 28 18:10:35 2018 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Mar 12 00:16:13 2018 -0700

----------------------------------------------------------------------
 .../apache/calcite/interpreter/SortNode.java    |   4 +-
 .../org/apache/calcite/rel/core/Project.java    |   4 +-
 .../calcite/rel/metadata/RelMdCollation.java    |   2 +-
 .../sql/type/CompositeOperandTypeChecker.java   |  12 +-
 .../org/apache/calcite/plan/RelWriterTest.java  |   8 +-
 .../rel/rel2sql/RelToSqlConverterTest.java      |   7 +-
 .../org/apache/calcite/test/CalciteAssert.java  |  18 +-
 .../java/org/apache/calcite/test/JdbcTest.java  |   9 +-
 .../org/apache/calcite/test/LatticeTest.java    |   8 +-
 .../java/org/apache/calcite/test/Matchers.java  | 105 +++++++
 .../org/apache/calcite/test/RelBuilderTest.java | 300 ++++++++++---------
 .../java/org/apache/calcite/util/UtilTest.java  |  55 ++++
 .../org/apache/calcite/test/ServerTest.java     |   6 +-
 13 files changed, 356 insertions(+), 182 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/core/src/main/java/org/apache/calcite/interpreter/SortNode.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/interpreter/SortNode.java 
b/core/src/main/java/org/apache/calcite/interpreter/SortNode.java
index dff97a6..04c95bf 100644
--- a/core/src/main/java/org/apache/calcite/interpreter/SortNode.java
+++ b/core/src/main/java/org/apache/calcite/interpreter/SortNode.java
@@ -89,8 +89,8 @@ public class SortNode extends AbstractSingleNode<Sort> {
     }
     return Ordering.compound(
         Iterables.transform(rel.getCollation().getFieldCollations(),
-            new Function<RelFieldCollation, Comparator<? super Row>>() {
-              public Comparator<? super Row> apply(RelFieldCollation input) {
+            new Function<RelFieldCollation, Comparator<Row>>() {
+              public Comparator<Row> apply(RelFieldCollation input) {
                 return comparator(input);
               }
             }));

http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/core/src/main/java/org/apache/calcite/rel/core/Project.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Project.java 
b/core/src/main/java/org/apache/calcite/rel/core/Project.java
index c3a2c2b..3119959 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Project.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Project.java
@@ -279,7 +279,7 @@ public abstract class Project extends SingleRel {
     Mappings.TargetMapping mapping =
         Mappings.create(MappingType.INVERSE_SURJECTION,
             inputFieldCount, projects.size());
-    for (Ord<RexNode> exp : Ord.zip(projects)) {
+    for (Ord<RexNode> exp : Ord.<RexNode>zip(projects)) {
       if (!(exp.e instanceof RexInputRef)) {
         return null;
       }
@@ -306,7 +306,7 @@ public abstract class Project extends SingleRel {
     Mappings.TargetMapping mapping =
         Mappings.create(MappingType.INVERSE_FUNCTION,
             inputFieldCount, projects.size());
-    for (Ord<RexNode> exp : Ord.zip(projects)) {
+    for (Ord<RexNode> exp : Ord.<RexNode>zip(projects)) {
       if (exp.e instanceof RexInputRef) {
         mapping.set(((RexInputRef) exp.e).getIndex(), exp.i);
       }

http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
index b48e763..8fed9bb 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
@@ -217,7 +217,7 @@ public class RelMdCollation
     final Multimap<Integer, Integer> targets = LinkedListMultimap.create();
     final Map<Integer, SqlMonotonicity> targetsWithMonotonicity =
         new HashMap<>();
-    for (Ord<RexNode> project : Ord.zip(projects)) {
+    for (Ord<RexNode> project : Ord.<RexNode>zip(projects)) {
       if (project.e instanceof RexInputRef) {
         targets.put(((RexInputRef) project.e).getIndex(), project.i);
       } else if (project.e instanceof RexCall) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/core/src/main/java/org/apache/calcite/sql/type/CompositeOperandTypeChecker.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql/type/CompositeOperandTypeChecker.java
 
b/core/src/main/java/org/apache/calcite/sql/type/CompositeOperandTypeChecker.java
index c8b7f13..36d47cb 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/type/CompositeOperandTypeChecker.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/type/CompositeOperandTypeChecker.java
@@ -129,7 +129,8 @@ public class CompositeOperandTypeChecker implements 
SqlOperandTypeChecker {
           "specify allowedSignatures or override getAllowedSignatures");
     }
     StringBuilder ret = new StringBuilder();
-    for (Ord<SqlOperandTypeChecker> ord : Ord.zip(allowedRules)) {
+    for (Ord<SqlOperandTypeChecker> ord
+        : Ord.<SqlOperandTypeChecker>zip(allowedRules)) {
       if (ord.i > 0) {
         ret.append(SqlOperator.NL);
       }
@@ -272,7 +273,8 @@ public class CompositeOperandTypeChecker implements 
SqlOperandTypeChecker {
       if (callBinding.getOperandCount() != allowedRules.size()) {
         return false;
       }
-      for (Ord<SqlOperandTypeChecker> ord : Ord.zip(allowedRules)) {
+      for (Ord<SqlOperandTypeChecker> ord
+          : Ord.<SqlOperandTypeChecker>zip(allowedRules)) {
         SqlOperandTypeChecker rule = ord.e;
         if (!((SqlSingleOperandTypeChecker) rule).checkSingleOperandType(
             callBinding,
@@ -285,7 +287,8 @@ public class CompositeOperandTypeChecker implements 
SqlOperandTypeChecker {
       return true;
 
     case AND:
-      for (Ord<SqlOperandTypeChecker> ord : Ord.zip(allowedRules)) {
+      for (Ord<SqlOperandTypeChecker> ord
+          : Ord.<SqlOperandTypeChecker>zip(allowedRules)) {
         SqlOperandTypeChecker rule = ord.e;
         if (!rule.checkOperandTypes(callBinding, false)) {
           // Avoid trying other rules in AND if the first one fails.
@@ -295,7 +298,8 @@ public class CompositeOperandTypeChecker implements 
SqlOperandTypeChecker {
       return true;
 
     case OR:
-      for (Ord<SqlOperandTypeChecker> ord : Ord.zip(allowedRules)) {
+      for (Ord<SqlOperandTypeChecker> ord
+          : Ord.<SqlOperandTypeChecker>zip(allowedRules)) {
         SqlOperandTypeChecker rule = ord.e;
         if (rule.checkOperandTypes(callBinding, false)) {
           return true;

http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java 
b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
index 4f4829b..2ceea23 100644
--- a/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
+++ b/core/src/test/java/org/apache/calcite/plan/RelWriterTest.java
@@ -34,7 +34,6 @@ import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.test.JdbcTest;
 import org.apache.calcite.tools.Frameworks;
 import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
 
@@ -44,6 +43,8 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.Arrays;
 
+import static org.apache.calcite.test.Matchers.isLinux;
+
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
@@ -178,9 +179,8 @@ public class RelWriterTest {
               }
             });
 
-    assertThat(Util.toLinux(s),
-        is(
-            "LogicalAggregate(group=[{0}], agg#0=[COUNT(DISTINCT $1)], 
agg#1=[COUNT()])\n"
+    assertThat(s,
+        isLinux("LogicalAggregate(group=[{0}], agg#0=[COUNT(DISTINCT $1)], 
agg#1=[COUNT()])\n"
             + "  LogicalFilter(condition=[=($1, 10)])\n"
             + "    LogicalTableScan(table=[[hr, emps]])\n"));
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java 
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 2cdda74..afec0c2 100644
--- 
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ 
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -45,7 +45,6 @@ import org.apache.calcite.tools.Program;
 import org.apache.calcite.tools.Programs;
 import org.apache.calcite.tools.RuleSet;
 import org.apache.calcite.tools.RuleSets;
-import org.apache.calcite.util.Util;
 
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
@@ -56,6 +55,8 @@ import java.util.List;
 
 import junit.framework.AssertionFailedError;
 
+import static org.apache.calcite.test.Matchers.isLinux;
+
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
@@ -2406,7 +2407,7 @@ public class RelToSqlConverterTest {
     }
 
     Sql ok(String expectedQuery) {
-      assertThat(exec(), is(expectedQuery));
+      assertThat(exec(), isLinux(expectedQuery));
       return this;
     }
 
@@ -2434,7 +2435,7 @@ public class RelToSqlConverterTest {
         final RelToSqlConverter converter =
             new RelToSqlConverter(dialect);
         final SqlNode sqlNode = converter.visitChild(0, rel).asStatement();
-        return Util.toLinux(sqlNode.toSqlString(dialect).getSql());
+        return sqlNode.toSqlString(dialect).getSql();
       } catch (RuntimeException e) {
         throw e;
       } catch (Exception e) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java 
b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
index 49a9c31..2a30093 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
@@ -101,9 +101,13 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.sql.DataSource;
 
+import static org.apache.calcite.test.Matchers.containsStringLinux;
+import static org.apache.calcite.test.Matchers.isLinux;
+
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
@@ -242,8 +246,8 @@ public class CalciteAssert {
         if (counter != null) {
           counter.incrementAndGet();
         }
-        String s = Util.toLinux(RelOptUtil.toString(relNode));
-        assertThat(s, containsString(expected));
+        String s = RelOptUtil.toString(relNode);
+        assertThat(s, containsStringLinux(expected));
         return null;
       }
     };
@@ -313,7 +317,7 @@ public class CalciteAssert {
       public Void apply(ResultSet resultSet) {
         try {
           resultSetFormatter.resultSet(resultSet);
-          assertEquals(expected, Util.toLinux(resultSetFormatter.string()));
+          assertThat(resultSetFormatter.string(), isLinux(expected));
           return null;
         } catch (SQLException e) {
           throw new RuntimeException(e);
@@ -333,8 +337,8 @@ public class CalciteAssert {
             throw new AssertionError("expected 1 column");
           }
           final String resultString = resultSet.getString(1);
-          assertEquals(expected,
-              resultString == null ? null : Util.toLinux(resultString));
+          assertThat(resultString,
+              expected == null ? nullValue(String.class) : isLinux(expected));
           return null;
         } catch (SQLException e) {
           throw new RuntimeException(e);
@@ -451,9 +455,9 @@ public class CalciteAssert {
     return new Function<ResultSet, Void>() {
       public Void apply(ResultSet s) {
         try {
-          final String actual = Util.toLinux(CalciteAssert.toString(s));
+          final String actual = CalciteAssert.toString(s);
           for (String st : expected) {
-            assertThat(actual, containsString(st));
+            assertThat(actual, containsStringLinux(st));
           }
           return null;
         } catch (SQLException e) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/core/src/test/java/org/apache/calcite/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java 
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index bf2bd81..37651a3 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -141,6 +141,7 @@ import java.util.TimeZone;
 import java.util.regex.Pattern;
 import javax.sql.DataSource;
 
+import static org.apache.calcite.test.Matchers.isLinux;
 import static org.apache.calcite.util.Static.RESOURCE;
 
 import static org.hamcrest.CoreMatchers.containsString;
@@ -267,8 +268,8 @@ public class JdbcTest {
                         + "insert into \"adhoc\".V\n"
                         + "values ('Fred', 56, 123.4)");
                 assertThat(resultSet.next(), is(true));
-                assertThat(Util.toLinux(resultSet.getString(1)),
-                    is(
+                assertThat(resultSet.getString(1),
+                    isLinux(
                         "EnumerableTableModify(table=[[adhoc, 
MUTABLE_EMPLOYEES]], operation=[INSERT], flattened=[false])\n"
                         + "  EnumerableCalc(expr#0..2=[{inputs}], 
expr#3=[CAST($t1):JavaType(int) NOT NULL], expr#4=[10], 
expr#5=[CAST($t0):JavaType(class java.lang.String)], 
expr#6=[CAST($t2):JavaType(float) NOT NULL], expr#7=[null], empid=[$t3], 
deptno=[$t4], name=[$t5], salary=[$t6], commission=[$t7])\n"
                         + "    EnumerableValues(tuples=[[{ 'Fred', 56, 123.4 
}]])\n"));
@@ -6278,8 +6279,8 @@ public class JdbcTest {
         .returns("C=0\n");
     switch (CalciteAssert.DB) {
     case HSQLDB:
-      assertThat(Util.toLinux(sqls[0]),
-          equalTo("SELECT COUNT(*) AS \"C\"\n"
+      assertThat(sqls[0],
+          isLinux("SELECT COUNT(*) AS \"C\"\n"
               + "FROM \"foodmart\".\"employee\"\n"
               + "WHERE \"first_name\" = 'abcde' AND \"gender\" = 'F'"));
       break;

http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/core/src/test/java/org/apache/calcite/test/LatticeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/LatticeTest.java 
b/core/src/test/java/org/apache/calcite/test/LatticeTest.java
index 8dc0589..539a2cc 100644
--- a/core/src/test/java/org/apache/calcite/test/LatticeTest.java
+++ b/core/src/test/java/org/apache/calcite/test/LatticeTest.java
@@ -27,7 +27,6 @@ import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.TestUtil;
-import org.apache.calcite.util.Util;
 
 import com.google.common.base.Function;
 import com.google.common.base.Throwables;
@@ -48,6 +47,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import static org.apache.calcite.test.Matchers.containsStringLinux;
 import static org.apache.calcite.test.Matchers.within;
 
 import static org.hamcrest.CoreMatchers.anyOf;
@@ -313,14 +313,14 @@ public class LatticeTest {
             new Function<RelNode, Void>() {
               public Void apply(RelNode relNode) {
                 counter.incrementAndGet();
-                String s = Util.toLinux(RelOptUtil.toString(relNode));
+                String s = RelOptUtil.toString(relNode);
                 assertThat(s,
                     anyOf(
-                        containsString(
+                        containsStringLinux(
                             "LogicalProject(brand_name=[$1], 
customer_id=[$0])\n"
                             + "  LogicalAggregate(group=[{2, 10}])\n"
                             + "    LogicalTableScan(table=[[adhoc, star]])\n"),
-                        containsString(
+                        containsStringLinux(
                             "LogicalAggregate(group=[{2, 10}])\n"
                             + "  LogicalTableScan(table=[[adhoc, star]])\n")));
                 return null;

http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/core/src/test/java/org/apache/calcite/test/Matchers.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/Matchers.java 
b/core/src/test/java/org/apache/calcite/test/Matchers.java
index 76d994f..5ea3a4e 100644
--- a/core/src/test/java/org/apache/calcite/test/Matchers.java
+++ b/core/src/test/java/org/apache/calcite/test/Matchers.java
@@ -16,18 +16,24 @@
  */
 package org.apache.calcite.test;
 
+import org.apache.calcite.plan.RelOptUtil;
+import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.util.Util;
 
+import com.google.common.base.Function;
 import com.google.common.base.Functions;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
 import org.hamcrest.BaseMatcher;
+import org.hamcrest.CoreMatchers;
 import org.hamcrest.CustomTypeSafeMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Factory;
 import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.hamcrest.core.Is;
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -125,6 +131,81 @@ public class Matchers {
     return new IsWithin<T>(value, epsilon);
   }
 
+  /** Creates a matcher by applying a function to a value before calling
+   * another matcher. */
+  public static <F, T> Matcher<F> compose(Matcher<T> matcher,
+      Function<F, T> f) {
+    return new ComposingMatcher<>(matcher, f);
+  }
+
+  /**
+   * Creates a Matcher that matches when the examined string is equal to the
+   * specified {@code value} when all Windows-style line endings ("\r\n")
+   * have been converted to Unix-style line endings ("\n").
+   *
+   * <p>Thus, if {@code foo()} is a function that returns "hello{newline}world"
+   * in the current operating system's line endings, then
+   *
+   * <blockquote>
+   *   assertThat(foo(), isLinux("hello\nworld"));
+   * </blockquote>
+   *
+   * <p>will succeed on all platforms.
+   *
+   * @see Util#toLinux(String)
+   */
+  @Factory
+  public static Matcher<String> isLinux(final String value) {
+    return compose(Is.is(value),
+        new Function<String, String>() {
+          public String apply(String input) {
+            return input == null ? null : Util.toLinux(input);
+          }
+        });
+  }
+
+  /**
+   * Creates a Matcher that matches a {@link RelNode} its string 
representation,
+   * after converting Windows-style line endings ("\r\n")
+   * to Unix-style line endings ("\n"), is equal to the given {@code value}.
+   */
+  @Factory
+  public static Matcher<RelNode> hasTree(final String value) {
+    return compose(Is.is(value),
+        new Function<RelNode, String>() {
+          public String apply(RelNode input) {
+            // Convert RelNode to a string with Linux line-endings
+            return Util.toLinux(RelOptUtil.toString(input));
+          }
+        });
+  }
+
+  /**
+   * Creates a matcher that matches when the examined string is equal to the
+   * specified <code>operand</code> when all Windows-style line endings 
("\r\n")
+   * have been converted to Unix-style line endings ("\n").
+   *
+   * <p>Thus, if {@code foo()} is a function that returns "hello{newline}world"
+   * in the current operating system's line endings, then
+   *
+   * <blockquote>
+   *   assertThat(foo(), isLinux("hello\nworld"));
+   * </blockquote>
+   *
+   * <p>will succeed on all platforms.
+   *
+   * @see Util#toLinux(String)
+   */
+  @Factory
+  public static Matcher<String> containsStringLinux(String value) {
+    return compose(CoreMatchers.containsString(value),
+        new Function<String, String>() {
+          public String apply(String input) {
+            return Util.toLinux(input);
+          }
+        });
+  }
+
   /**
    * Is the numeric value within a given difference another value?
    *
@@ -162,6 +243,30 @@ public class Matchers {
       return min <= a && a <= max;
     }
   }
+
+  /** Matcher that transforms the input value using a function before
+   * passing to another matcher.
+   *
+   * @param <F> From type: the type of value to be matched
+   * @param <T> To type: type returned by function, and the resulting matcher
+   */
+  private static class ComposingMatcher<F, T> extends TypeSafeMatcher<F> {
+    private final Matcher<T> matcher;
+    private final Function<F, T> f;
+
+    ComposingMatcher(Matcher<T> matcher, Function<F, T> f) {
+      this.matcher = matcher;
+      this.f = f;
+    }
+
+    protected boolean matchesSafely(F item) {
+      return matcher.matches(f.apply(item));
+    }
+
+    public void describeTo(Description description) {
+      matcher.describeTo(description);
+    }
+  }
 }
 
 // End Matchers.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java 
b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index d04397f..1290191 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.calcite.test;
 
-import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelTraitDef;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.AggregateCall;
@@ -44,7 +43,6 @@ import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.tools.RelRunners;
 import org.apache.calcite.util.Holder;
 import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.calcite.util.Util;
 import org.apache.calcite.util.mapping.Mappings;
 
 import com.google.common.collect.ImmutableList;
@@ -59,6 +57,8 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.TreeSet;
 
+import static org.apache.calcite.test.Matchers.hasTree;
+
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
@@ -109,11 +109,6 @@ public class RelBuilderTest {
         .programs(Programs.heuristicJoinOrder(Programs.RULE_SET, true, 2));
   }
 
-  /** Converts a relational expression to a string with linux line-endings. */
-  private String str(RelNode r) {
-    return Util.toLinux(RelOptUtil.toString(r));
-  }
-
   @Test public void testScan() {
     // Equivalent SQL:
     //   SELECT *
@@ -122,8 +117,8 @@ public class RelBuilderTest {
         RelBuilder.create(config().build())
             .scan("EMP")
             .build();
-    assertThat(str(root),
-        is("LogicalTableScan(table=[[scott, EMP]])\n"));
+    assertThat(root,
+        hasTree("LogicalTableScan(table=[[scott, EMP]])\n"));
   }
 
   @Test public void testScanQualifiedTable() {
@@ -134,8 +129,8 @@ public class RelBuilderTest {
         RelBuilder.create(config().build())
             .scan("scott", "EMP")
             .build();
-    assertThat(str(root),
-        is("LogicalTableScan(table=[[scott, EMP]])\n"));
+    assertThat(root,
+        hasTree("LogicalTableScan(table=[[scott, EMP]])\n"));
   }
 
   @Test public void testScanInvalidTable() {
@@ -208,8 +203,8 @@ public class RelBuilderTest {
         builder.scan("EMP")
             .filter(builder.literal(true))
             .build();
-    assertThat(str(root),
-        is("LogicalTableScan(table=[[scott, EMP]])\n"));
+    assertThat(root,
+        hasTree("LogicalTableScan(table=[[scott, EMP]])\n"));
   }
 
   @Test public void testScanFilterTriviallyFalse() {
@@ -222,8 +217,8 @@ public class RelBuilderTest {
         builder.scan("EMP")
             .filter(builder.equals(builder.literal(1), builder.literal(2)))
             .build();
-    assertThat(str(root),
-        is("LogicalValues(tuples=[[]])\n"));
+    assertThat(root,
+        hasTree("LogicalValues(tuples=[[]])\n"));
   }
 
   @Test public void testScanFilterEquals() {
@@ -237,9 +232,9 @@ public class RelBuilderTest {
             .filter(
                 builder.equals(builder.field("DEPTNO"), builder.literal(20)))
             .build();
-    assertThat(str(root),
-        is("LogicalFilter(condition=[=($7, 20)])\n"
-            + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = "LogicalFilter(condition=[=($7, 20)])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testScanFilterOr() {
@@ -258,9 +253,10 @@ public class RelBuilderTest {
                     builder.isNull(builder.field(6))),
                 builder.isNotNull(builder.field(3)))
             .build();
-    assertThat(str(root),
-        is("LogicalFilter(condition=[AND(OR(=($7, 20), IS NULL($6)), IS NOT 
NULL($3))])\n"
-            + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = ""
+        + "LogicalFilter(condition=[AND(OR(=($7, 20), IS NULL($6)), IS NOT 
NULL($3))])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testScanFilterOr2() {
@@ -284,9 +280,9 @@ public class RelBuilderTest {
                         builder.field("DEPTNO"),
                         builder.literal(20))))
             .build();
-    assertThat(str(root),
-        is("LogicalFilter(condition=[>($7, 20)])\n"
-            + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = "LogicalFilter(condition=[>($7, 20)])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testScanFilterAndFalse() {
@@ -305,8 +301,8 @@ public class RelBuilderTest {
                     builder.literal(20)),
                 builder.literal(false))
             .build();
-    final String plan = "LogicalValues(tuples=[[]])\n";
-    assertThat(str(root), is(plan));
+    final String expected = "LogicalValues(tuples=[[]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testScanFilterAndTrue() {
@@ -323,9 +319,9 @@ public class RelBuilderTest {
                     builder.literal(20)),
                 builder.literal(true))
             .build();
-    final String plan = "LogicalFilter(condition=[>($7, 20)])\n"
+    final String expected = "LogicalFilter(condition=[>($7, 20)])\n"
         + "  LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(plan));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testBadFieldName() {
@@ -383,10 +379,10 @@ public class RelBuilderTest {
             .build();
     // Note: CAST(COMM) gets the COMM alias because it occurs first
     // Note: AS(COMM, C) becomes just $6
-    assertThat(str(root),
-        is(
-            "LogicalProject(DEPTNO=[$7], COMM=[CAST($6):SMALLINT NOT NULL], 
$f2=[20], COMM0=[$6], C=[$6])\n"
-            + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = ""
+        + "LogicalProject(DEPTNO=[$7], COMM=[CAST($6):SMALLINT NOT NULL], 
$f2=[20], COMM0=[$6], C=[$6])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   /** Tests each method that creates a scalar expression. */
@@ -414,12 +410,13 @@ public class RelBuilderTest {
                 builder.field(6),
                 builder.alias(builder.field(6), "C"))
             .build();
-    assertThat(str(root),
-        is("LogicalProject(DEPTNO=[$7], COMM=[CAST($6):SMALLINT NOT NULL],"
-                + " $f2=[OR(=($7, 20), AND(null, =($7, 10), IS NULL($6),"
-                + " IS NULL($7)), =($7, 30))], n2=[IS NULL($2)],"
-                + " nn2=[IS NOT NULL($3)], $f5=[20], COMM0=[$6], C=[$6])\n"
-                + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = ""
+        + "LogicalProject(DEPTNO=[$7], COMM=[CAST($6):SMALLINT NOT NULL],"
+        + " $f2=[OR(=($7, 20), AND(null, =($7, 10), IS NULL($6),"
+        + " IS NULL($7)), =($7, 30))], n2=[IS NULL($2)],"
+        + " nn2=[IS NOT NULL($3)], $f5=[20], COMM0=[$6], C=[$6])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testProjectIdentity() {
@@ -429,7 +426,7 @@ public class RelBuilderTest {
             .project(builder.fields(Mappings.bijection(Arrays.asList(0, 1, 
2))))
             .build();
     final String expected = "LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   /** Test case for
@@ -449,7 +446,7 @@ public class RelBuilderTest {
             .build();
     final String expected = "LogicalProject(a=[$0], c=[$2])\n"
         + "  LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   /** Variation on {@link #testProjectIdentityWithFieldsRename}: don't use a
@@ -477,7 +474,7 @@ public class RelBuilderTest {
         + "  LogicalAggregate(group=[{0, 1, 2}], agg#0=[SUM($0)])\n"
         + "    LogicalFilter(condition=[=($0, 20)])\n"
         + "      LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testProjectLeadingEdge() {
@@ -488,7 +485,7 @@ public class RelBuilderTest {
             .build();
     final String expected = "LogicalProject(EMPNO=[$0], ENAME=[$1], 
JOB=[$2])\n"
         + "  LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testRename() {
@@ -500,14 +497,14 @@ public class RelBuilderTest {
             .rename(Arrays.asList("DEPTNO", null))
             .build();
     final String expected = "LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
 
     // No rename necessary (prefix matches)
     root =
         builder.scan("DEPT")
             .rename(ImmutableList.of("DEPTNO"))
             .build();
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
 
     // Add project to rename fields
     root =
@@ -517,7 +514,7 @@ public class RelBuilderTest {
     final String expected2 = ""
         + "LogicalProject(NAME=[$0], DNAME=[$1], DEPTNO=[$2])\n"
         + "  LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected2));
+    assertThat(root, hasTree(expected2));
 
     // If our requested list has non-unique names, we might get the same field
     // names we started with. Don't add a useless project.
@@ -528,14 +525,14 @@ public class RelBuilderTest {
     final String expected3 = ""
         + "LogicalProject(DEPTNO=[$0], DNAME=[$1], DEPTNO0=[$2])\n"
         + "  LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected3));
+    assertThat(root, hasTree(expected3));
     root =
         builder.scan("DEPT")
             .rename(Arrays.asList("DEPTNO", null, "DEPTNO"))
             .rename(Arrays.asList("DEPTNO", null, "DEPTNO"))
             .build();
     // No extra Project
-    assertThat(str(root), is(expected3));
+    assertThat(root, hasTree(expected3));
 
     // Name list too long
     try {
@@ -556,14 +553,14 @@ public class RelBuilderTest {
             .build();
     final String expected =
         "LogicalValues(tuples=[[{ true, 1 }, { false, -50 }]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
 
     // When you rename Values, you get a Values with a new row type, no Project
     root =
         builder.push(root)
             .rename(ImmutableList.of("x", "y z"))
             .build();
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
     assertThat(root.getRowType().getFieldNames().toString(), is("[x, y z]"));
   }
 
@@ -575,7 +572,7 @@ public class RelBuilderTest {
             .build();
     final String expected = "LogicalProject(JOB=[$2], EMPNO=[$0], 
ENAME=[$1])\n"
         + "  LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testConvert() {
@@ -593,7 +590,7 @@ public class RelBuilderTest {
     final String expected = ""
         + "LogicalProject(DEPTNO=[CAST($0):BIGINT NOT NULL], 
DNAME=[CAST($1):VARCHAR(10) CHARACTER SET \"ISO-8859-1\" COLLATE 
\"ISO-8859-1$en_US$primary\" NOT NULL], LOC=[CAST($2):VARCHAR(10) CHARACTER SET 
\"ISO-8859-1\" COLLATE \"ISO-8859-1$en_US$primary\" NOT NULL])\n"
         + "  LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testConvertRename() {
@@ -611,7 +608,7 @@ public class RelBuilderTest {
     final String expected = ""
         + "LogicalProject(a=[CAST($0):BIGINT NOT NULL], 
b=[CAST($1):VARCHAR(10) CHARACTER SET \"ISO-8859-1\" COLLATE 
\"ISO-8859-1$en_US$primary\" NOT NULL], c=[CAST($2):VARCHAR(10) CHARACTER SET 
\"ISO-8859-1\" COLLATE \"ISO-8859-1$en_US$primary\" NOT NULL])\n"
         + "  LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testAggregate() {
@@ -626,9 +623,10 @@ public class RelBuilderTest {
                 builder.aggregateCall(SqlStdOperatorTable.COUNT, true, false,
                     null, "C", builder.field("DEPTNO")))
             .build();
-    assertThat(str(root),
-        is("LogicalAggregate(group=[{}], C=[COUNT(DISTINCT $7)])\n"
-            + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = ""
+        + "LogicalAggregate(group=[{}], C=[COUNT(DISTINCT $7)])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testAggregate2() {
@@ -652,11 +650,11 @@ public class RelBuilderTest {
                     builder.call(SqlStdOperatorTable.PLUS, builder.field(3),
                         builder.literal(1))))
             .build();
-    assertThat(str(root),
-        is(""
-            + "LogicalAggregate(group=[{1, 8}], C=[COUNT()], S=[SUM($9)])\n"
-            + "  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], 
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], $f8=[+($4, $3)], $f9=[+($3, 
1)])\n"
-            + "    LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = ""
+        + "LogicalAggregate(group=[{1, 8}], C=[COUNT()], S=[SUM($9)])\n"
+        + "  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], 
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], $f8=[+($4, $3)], $f9=[+($3, 
1)])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   /** Test case for
@@ -683,7 +681,7 @@ public class RelBuilderTest {
         + "LogicalProject(ENAME=[$0])\n"
         + "  LogicalAggregate(group=[{1}], C=[COUNT()])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   /** As {@link #testAggregate3()} but with Filter. */
@@ -712,7 +710,7 @@ public class RelBuilderTest {
         + "  LogicalFilter(condition=[>($1, 3)])\n"
         + "    LogicalAggregate(group=[{1}], C=[COUNT()])\n"
         + "      LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testAggregateFilter() {
@@ -735,7 +733,7 @@ public class RelBuilderTest {
         + "LogicalAggregate(group=[{7}], groups=[[{7}, {}]], C=[COUNT() FILTER 
$8])\n"
         + "  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], 
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], $f8=[>($0, 100)])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testAggregateFilterFails() {
@@ -778,7 +776,7 @@ public class RelBuilderTest {
         + "LogicalAggregate(group=[{7}], C=[SUM($5) FILTER $8])\n"
         + "  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], 
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], $f8=[IS TRUE(<($6, 100))])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   /** Test case for
@@ -801,7 +799,7 @@ public class RelBuilderTest {
         + "  LogicalProject(departmentNo=[$0])\n"
         + "    LogicalProject(DEPTNO=[$7])\n"
         + "      LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testAggregateProjectWithExpression() {
@@ -822,7 +820,7 @@ public class RelBuilderTest {
         + "    LogicalProject(DEPTNO=[$0], $f1=[+($0, 3)])\n"
         + "      LogicalProject(DEPTNO=[$7])\n"
         + "        LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testAggregateGroupingKeyOutOfRangeFails() {
@@ -868,7 +866,7 @@ public class RelBuilderTest {
     final String expected = ""
         + "LogicalAggregate(group=[{6, 7}], groups=[[{6}, {7}]])\n"
         + "  LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testAggregateGrouping() {
@@ -882,7 +880,7 @@ public class RelBuilderTest {
     final String expected = ""
         + "LogicalAggregate(group=[{6, 7}], g=[GROUPING($7)])\n"
         + "  LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testAggregateGroupingWithDistinctFails() {
@@ -929,8 +927,7 @@ public class RelBuilderTest {
     final String expected = "LogicalAggregate(group=[{0}])\n"
         + "  LogicalProject(DEPTNO=[$7])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root),
-        is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testDistinctAlready() {
@@ -940,8 +937,8 @@ public class RelBuilderTest {
         builder.scan("DEPT")
             .distinct()
             .build();
-    assertThat(str(root),
-        is("LogicalTableScan(table=[[scott, DEPT]])\n"));
+    final String expected = "LogicalTableScan(table=[[scott, DEPT]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testDistinctEmpty() {
@@ -964,7 +961,7 @@ public class RelBuilderTest {
         + "  LogicalProject\n"
         + "    LogicalFilter(condition=[IS NULL($6)])\n"
         + "      LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testUnion() {
@@ -984,13 +981,14 @@ public class RelBuilderTest {
             .project(builder.field("EMPNO"))
             .union(true)
             .build();
-    assertThat(str(root),
-        is("LogicalUnion(all=[true])\n"
-            + "  LogicalProject(DEPTNO=[$0])\n"
-            + "    LogicalTableScan(table=[[scott, DEPT]])\n"
-            + "  LogicalProject(EMPNO=[$0])\n"
-            + "    LogicalFilter(condition=[=($7, 20)])\n"
-            + "      LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = ""
+        + "LogicalUnion(all=[true])\n"
+        + "  LogicalProject(DEPTNO=[$0])\n"
+        + "    LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "  LogicalProject(EMPNO=[$0])\n"
+        + "    LogicalFilter(condition=[=($7, 20)])\n"
+        + "      LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   /** Test case for
@@ -1036,14 +1034,15 @@ public class RelBuilderTest {
             .project(builder.field("DEPTNO"))
             .union(true, 3)
             .build();
-    assertThat(str(root),
-        is("LogicalUnion(all=[true])\n"
-            + "  LogicalProject(DEPTNO=[$0])\n"
-            + "    LogicalTableScan(table=[[scott, DEPT]])\n"
-            + "  LogicalProject(EMPNO=[$0])\n"
-            + "    LogicalTableScan(table=[[scott, EMP]])\n"
-            + "  LogicalProject(DEPTNO=[$7])\n"
-            + "    LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = ""
+        + "LogicalUnion(all=[true])\n"
+        + "  LogicalProject(DEPTNO=[$0])\n"
+        + "    LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "  LogicalProject(EMPNO=[$0])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n"
+        + "  LogicalProject(DEPTNO=[$7])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testUnion1() {
@@ -1063,9 +1062,9 @@ public class RelBuilderTest {
             .project(builder.field("DEPTNO"))
             .union(true, 1)
             .build();
-    assertThat(str(root),
-        is("LogicalProject(DEPTNO=[$7])\n"
-            + "  LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = "LogicalProject(DEPTNO=[$7])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testIntersect() {
@@ -1086,13 +1085,14 @@ public class RelBuilderTest {
             .project(builder.field("EMPNO"))
             .intersect(false)
             .build();
-    assertThat(str(root),
-        is("LogicalIntersect(all=[false])\n"
-            + "  LogicalProject(DEPTNO=[$0])\n"
-            + "    LogicalTableScan(table=[[scott, DEPT]])\n"
-            + "  LogicalProject(EMPNO=[$0])\n"
-            + "    LogicalFilter(condition=[=($7, 20)])\n"
-            + "      LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = ""
+        + "LogicalIntersect(all=[false])\n"
+        + "  LogicalProject(DEPTNO=[$0])\n"
+        + "    LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "  LogicalProject(EMPNO=[$0])\n"
+        + "    LogicalFilter(condition=[=($7, 20)])\n"
+        + "      LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testIntersect3() {
@@ -1112,14 +1112,15 @@ public class RelBuilderTest {
             .project(builder.field("DEPTNO"))
             .intersect(true, 3)
             .build();
-    assertThat(str(root),
-        is("LogicalIntersect(all=[true])\n"
-            + "  LogicalProject(DEPTNO=[$0])\n"
-            + "    LogicalTableScan(table=[[scott, DEPT]])\n"
-            + "  LogicalProject(EMPNO=[$0])\n"
-            + "    LogicalTableScan(table=[[scott, EMP]])\n"
-            + "  LogicalProject(DEPTNO=[$7])\n"
-            + "    LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = ""
+        + "LogicalIntersect(all=[true])\n"
+        + "  LogicalProject(DEPTNO=[$0])\n"
+        + "    LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "  LogicalProject(EMPNO=[$0])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n"
+        + "  LogicalProject(DEPTNO=[$7])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testExcept() {
@@ -1140,13 +1141,14 @@ public class RelBuilderTest {
             .project(builder.field("EMPNO"))
             .minus(false)
             .build();
-    assertThat(str(root),
-        is("LogicalMinus(all=[false])\n"
-            + "  LogicalProject(DEPTNO=[$0])\n"
-            + "    LogicalTableScan(table=[[scott, DEPT]])\n"
-            + "  LogicalProject(EMPNO=[$0])\n"
-            + "    LogicalFilter(condition=[=($7, 20)])\n"
-            + "      LogicalTableScan(table=[[scott, EMP]])\n"));
+    final String expected = ""
+        + "LogicalMinus(all=[false])\n"
+        + "  LogicalProject(DEPTNO=[$0])\n"
+        + "    LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "  LogicalProject(EMPNO=[$0])\n"
+        + "    LogicalFilter(condition=[=($7, 20)])\n"
+        + "      LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testJoin() {
@@ -1171,7 +1173,7 @@ public class RelBuilderTest {
         + "  LogicalFilter(condition=[IS NULL($6)])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n"
         + "  LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   /** Same as {@link #testJoin} using USING. */
@@ -1190,7 +1192,7 @@ public class RelBuilderTest {
         + "  LogicalFilter(condition=[IS NULL($6)])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n"
         + "  LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root2), is(expected));
+    assertThat(root2, hasTree(expected));
   }
 
   @Test public void testJoin2() {
@@ -1219,7 +1221,7 @@ public class RelBuilderTest {
         + "LogicalJoin(condition=[AND(=($7, $8), =($0, 123))], 
joinType=[left])\n"
         + "  LogicalTableScan(table=[[scott, EMP]])\n"
         + "  LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testJoinCartesian() {
@@ -1235,7 +1237,7 @@ public class RelBuilderTest {
         "LogicalJoin(condition=[true], joinType=[inner])\n"
             + "  LogicalTableScan(table=[[scott, EMP]])\n"
             + "  LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testCorrelationFails() {
@@ -1277,7 +1279,7 @@ public class RelBuilderTest {
         + "  LogicalFilter(condition=[=($cor0.SAL, 1000)])\n"
         + "    LogicalFilter(condition=[=($0, $cor0.DEPTNO)])\n"
         + "      LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testAlias() {
@@ -1302,7 +1304,7 @@ public class RelBuilderTest {
         + "    LogicalJoin(condition=[true], joinType=[left])\n"
         + "      LogicalTableScan(table=[[scott, EMP]])\n"
         + "      LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
     final RelDataTypeField field = root.getRowType().getFieldList().get(1);
     assertThat(field.getName(), is("DNAME"));
     assertThat(field.getType().isNullable(), is(true));
@@ -1336,7 +1338,7 @@ public class RelBuilderTest {
         + "    LogicalJoin(condition=[true], joinType=[inner])\n"
         + "      LogicalTableScan(table=[[scott, EMP]])\n"
         + "      LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testAliasSort() {
@@ -1351,7 +1353,7 @@ public class RelBuilderTest {
         + "LogicalProject(EMPNO=[$0])\n"
         + "  LogicalSort(sort0=[$0], dir0=[ASC])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testAliasLimit() {
@@ -1367,7 +1369,7 @@ public class RelBuilderTest {
         + "LogicalProject(EMPNO=[$0])\n"
         + "  LogicalSort(sort0=[$1], dir0=[ASC], offset=[10], fetch=[20])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   /** Test case for
@@ -1386,7 +1388,7 @@ public class RelBuilderTest {
         + "LogicalProject(DEPTNO=[$0])\n"
         + "  LogicalProject(DEPTNO=[$7], $f1=[20])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testAliasAggregate() {
@@ -1407,7 +1409,7 @@ public class RelBuilderTest {
         + "  LogicalAggregate(group=[{0}], agg#0=[SUM($1)])\n"
         + "    LogicalProject(DEPTNO=[$7], $f1=[20])\n"
         + "      LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   /** Tests that a projection retains field names after a join. */
@@ -1432,7 +1434,7 @@ public class RelBuilderTest {
         + "  LogicalJoin(condition=[true], joinType=[inner])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n"
         + "    LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testMultiLevelAlias() {
@@ -1466,7 +1468,7 @@ public class RelBuilderTest {
         + "        LogicalJoin(condition=[true], joinType=[inner])\n"
         + "          LogicalTableScan(table=[[scott, EMP]])\n"
         + "          LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testUnionAlias() {
@@ -1494,7 +1496,7 @@ public class RelBuilderTest {
         + "      LogicalTableScan(table=[[scott, EMP]])\n"
         + "    LogicalProject(EMPNO=[$0], $f1=[||($1, '-2')])\n"
         + "      LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   /** Test case for
@@ -1525,7 +1527,7 @@ public class RelBuilderTest {
         + "LogicalJoin(condition=[AND(=($7, $8), =($0, 123))], 
joinType=[left])\n"
         + "  LogicalTableScan(table=[[scott, EMP]])\n"
         + "  LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   /** As {@link #testAliasPastTop()}. */
@@ -1561,7 +1563,7 @@ public class RelBuilderTest {
         + "    LogicalTableScan(table=[[scott, EMP]])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n"
         + "  LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testEmpty() {
@@ -1577,7 +1579,7 @@ public class RelBuilderTest {
             .build();
     final String expected =
         "LogicalValues(tuples=[[]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
     final String expectedType =
         "RecordType(TINYINT NOT NULL DEPTNO, BOOLEAN NOT NULL $f1) NOT NULL";
     assertThat(root.getRowType().getFullTypeString(), is(expectedType));
@@ -1592,7 +1594,7 @@ public class RelBuilderTest {
             .build();
     final String expected =
         "LogicalValues(tuples=[[{ true, 1 }, { false, -50 }]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
     final String expectedType =
         "RecordType(BOOLEAN NOT NULL a, INTEGER NOT NULL b) NOT NULL";
     assertThat(root.getRowType().getFullTypeString(), is(expectedType));
@@ -1609,7 +1611,7 @@ public class RelBuilderTest {
             false, null, "longer string").build();
     final String expected =
         "LogicalValues(tuples=[[{ null, 1, 'abc' }, { false, null, 'longer 
string' }]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
     final String expectedType =
         "RecordType(BOOLEAN a, INTEGER expr$1, CHAR(13) CHARACTER SET 
\"ISO-8859-1\" COLLATE \"ISO-8859-1$en_US$primary\" NOT NULL c) NOT NULL";
     assertThat(root.getRowType().getFullTypeString(), is(expectedType));
@@ -1682,7 +1684,7 @@ public class RelBuilderTest {
         builder.values(rowType, null, null, 1, null).build();
     final String expected =
         "LogicalValues(tuples=[[{ null, null }, { 1, null }]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
     final String expectedType =
         "RecordType(BIGINT NOT NULL a, VARCHAR(10) CHARACTER SET 
\"ISO-8859-1\" COLLATE \"ISO-8859-1$en_US$primary\" NOT NULL a) NOT NULL";
     assertThat(root.getRowType().getFullTypeString(), is(expectedType));
@@ -1701,14 +1703,14 @@ public class RelBuilderTest {
     final String expected =
         "LogicalSort(sort0=[$2], sort1=[$0], dir0=[ASC], dir1=[DESC])\n"
             + "  LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
 
     // same result using ordinals
     final RelNode root2 =
         builder.scan("EMP")
             .sort(2, -1)
             .build();
-    assertThat(str(root2), is(expected));
+    assertThat(root2, hasTree(expected));
   }
 
   /** Test case for
@@ -1725,7 +1727,7 @@ public class RelBuilderTest {
             .sortLimit(0, -1, ImmutableList.<RexNode>of())
             .build();
     final String expected = "LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testSortDuplicate() {
@@ -1746,7 +1748,7 @@ public class RelBuilderTest {
     final String expected = "LogicalSort(sort0=[$0], sort1=[$7], sort2=[$4], "
         + "dir0=[DESC], dir1=[ASC], dir2=[ASC])\n"
         + "  LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testSortByExpression() {
@@ -1767,7 +1769,7 @@ public class RelBuilderTest {
             + "  LogicalSort(sort0=[$1], sort1=[$8], dir0=[DESC-nulls-last], 
dir1=[ASC-nulls-first])\n"
             + "    LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], 
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], $f8=[+($4, $3)])\n"
             + "      LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testLimit() {
@@ -1783,7 +1785,7 @@ public class RelBuilderTest {
     final String expected =
         "LogicalSort(offset=[2], fetch=[10])\n"
             + "  LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testSortLimit() {
@@ -1799,7 +1801,7 @@ public class RelBuilderTest {
     final String expected =
         "LogicalSort(sort0=[$7], dir0=[DESC], fetch=[10])\n"
             + "  LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testSortLimit0() {
@@ -1813,7 +1815,7 @@ public class RelBuilderTest {
             .sortLimit(-1, 0, builder.desc(builder.field("DEPTNO")))
             .build();
     final String expected = "LogicalValues(tuples=[[]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   /** Test case for
@@ -1828,7 +1830,7 @@ public class RelBuilderTest {
         // inner sort node
         .limit(0, 1)
         .build();
-    RelNode r = builder.scan("EMP")
+    RelNode root = builder.scan("EMP")
         .sort(0)
         .project(Lists.newArrayList(builder.field(1)),
             Lists.newArrayList("F1"))
@@ -1839,7 +1841,7 @@ public class RelBuilderTest {
     String expected = "LogicalProject(F1=[$1])\n"
         + "  LogicalSort(sort0=[$0], dir0=[ASC], fetch=[1])\n"
         + "    LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(r), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   /** Tests that a sort on a field followed by a limit gives the same
@@ -1858,13 +1860,13 @@ public class RelBuilderTest {
     final String expected = ""
         + "LogicalSort(sort0=[$7], dir0=[DESC], fetch=[10])\n"
         + "  LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
 
     final RelNode root2 =
         builder.scan("EMP")
             .sortLimit(-1, 10, builder.desc(builder.field("DEPTNO")))
             .build();
-    assertThat(str(root2), is(expected));
+    assertThat(root2, hasTree(expected));
   }
 
   /** Tests that a sort on an expression followed by a limit gives the same
@@ -1884,7 +1886,7 @@ public class RelBuilderTest {
         + "  LogicalSort(sort0=[$3], dir0=[DESC], offset=[3], fetch=[10])\n"
         + "    LogicalProject(DEPTNO=[$0], DNAME=[$1], LOC=[$2], $f3=[+($0, 
1)])\n"
         + "      LogicalTableScan(table=[[scott, DEPT]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
 
     final RelNode root2 =
         builder.scan("DEPT")
@@ -1893,7 +1895,7 @@ public class RelBuilderTest {
                     builder.call(SqlStdOperatorTable.PLUS,
                         builder.field("DEPTNO"), builder.literal(1))))
             .build();
-    assertThat(str(root2), is(expected));
+    assertThat(root2, hasTree(expected));
   }
 
   /** Tests {@link org.apache.calcite.tools.RelRunner} for a VALUES query. */
@@ -2046,7 +2048,7 @@ public class RelBuilderTest {
         + ">(PREV(UP.$3, 0), PREV(UP.$3, 1))]], "
         + "inputFields=[[EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, 
DEPTNO]])\n"
         + "  LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 
   @Test public void testFilterCastAny() {
@@ -2063,7 +2065,7 @@ public class RelBuilderTest {
     final String expected = ""
         + "LogicalFilter(condition=[CAST($0):BOOLEAN NOT NULL])\n"
         + "  LogicalTableScan(table=[[scott, EMP]])\n";
-    assertThat(str(root), is(expected));
+    assertThat(root, hasTree(expected));
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/core/src/test/java/org/apache/calcite/util/UtilTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/util/UtilTest.java 
b/core/src/test/java/org/apache/calcite/util/UtilTest.java
index dc3b6f8..c7cb9f9 100644
--- a/core/src/test/java/org/apache/calcite/util/UtilTest.java
+++ b/core/src/test/java/org/apache/calcite/util/UtilTest.java
@@ -35,6 +35,7 @@ import org.apache.calcite.sql.dialect.CalciteSqlDialect;
 import org.apache.calcite.sql.util.SqlBuilder;
 import org.apache.calcite.sql.util.SqlString;
 import org.apache.calcite.test.DiffTestCase;
+import org.apache.calcite.test.Matchers;
 
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
@@ -44,6 +45,8 @@ import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
 
+import org.hamcrest.Matcher;
+import org.hamcrest.StringDescription;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -79,6 +82,8 @@ import java.util.TimeZone;
 import java.util.TreeSet;
 import javax.annotation.Nullable;
 
+import static org.apache.calcite.test.Matchers.isLinux;
+
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
@@ -2075,6 +2080,56 @@ public class UtilTest {
         + "</root>\n";
     assertThat(Util.toLinux(s), is(expected));
   }
+
+  /** Unit test for {@link Matchers#compose}. */
+  @Test public void testComposeMatcher() {
+    assertThat("x", is("x"));
+    assertThat(is("x").matches("x"), is(true));
+    assertThat(is("X").matches("x"), is(false));
+    final Function<String, String> toUpper =
+        new Function<String, String>() {
+          public String apply(String input) {
+            return input.toUpperCase(Locale.ROOT);
+          }
+        };
+    assertThat(Matchers.compose(is("A"), toUpper).matches("a"), is(true));
+    assertThat(Matchers.compose(is("A"), toUpper).matches("A"), is(true));
+    assertThat(Matchers.compose(is("a"), toUpper).matches("A"), is(false));
+    assertThat(describe(Matchers.compose(is("a"), toUpper)), is("is \"a\""));
+    assertThat(mismatchDescription(Matchers.compose(is("a"), toUpper), "A"),
+        is("was \"A\""));
+  }
+
+  /** Unit test for {@link Matchers#isLinux}. */
+  @Test public void testIsLinux() {
+    assertThat("xy", isLinux("xy"));
+    assertThat("x\ny", isLinux("x\ny"));
+    assertThat("x\r\ny", isLinux("x\ny"));
+    assertThat(isLinux("x").matches("x"), is(true));
+    assertThat(isLinux("X").matches("x"), is(false));
+    assertThat(mismatchDescription(isLinux("X"), "x"), is("was \"x\""));
+    assertThat(describe(isLinux("X")), is("is \"X\""));
+    assertThat(isLinux("x\ny").matches("x\ny"), is(true));
+    assertThat(isLinux("x\ny").matches("x\r\ny"), is(true));
+    // \n\r is not a valid windows line ending
+    assertThat(isLinux("x\ny").matches("x\n\ry"), is(false));
+    assertThat(isLinux("x\ny").matches("x\n\ryz"), is(false));
+    // left-hand side must be linux or will never match
+    assertThat(isLinux("x\r\ny").matches("x\r\ny"), is(false));
+    assertThat(isLinux("x\r\ny").matches("x\ny"), is(false));
+  }
+
+  static String mismatchDescription(Matcher m, Object item) {
+    final StringDescription d = new StringDescription();
+    m.describeMismatch(item, d);
+    return d.toString();
+  }
+
+  static String describe(Matcher m) {
+    final StringDescription d = new StringDescription();
+    m.describeTo(d);
+    return d.toString();
+  }
 }
 
 // End UtilTest.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/fb8ebd31/server/src/test/java/org/apache/calcite/test/ServerTest.java
----------------------------------------------------------------------
diff --git a/server/src/test/java/org/apache/calcite/test/ServerTest.java 
b/server/src/test/java/org/apache/calcite/test/ServerTest.java
index 3a97412..92885ff 100644
--- a/server/src/test/java/org/apache/calcite/test/ServerTest.java
+++ b/server/src/test/java/org/apache/calcite/test/ServerTest.java
@@ -28,6 +28,8 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
 
+import static org.apache.calcite.test.Matchers.isLinux;
+
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -120,7 +122,7 @@ public class ServerTest {
             + "EnumerableTableModify(table=[[T]], operation=[INSERT], 
flattened=[false])\n"
             + "  EnumerableCalc(expr#0..1=[{inputs}], expr#2=[1], 
expr#3=[+($t1, $t2)], proj#0..1=[{exprs}], J=[$t3])\n"
             + "    EnumerableValues(tuples=[[{ 3, 4 }]])\n";
-        assertThat(r.getString(1), is(plan));
+        assertThat(r.getString(1), isLinux(plan));
         assertThat(r.next(), is(false));
       }
 
@@ -265,7 +267,7 @@ public class ServerTest {
           + "  EnumerableTableScan(table=[[T]])\n";
       try (ResultSet r = s.executeQuery("explain plan for " + sql)) {
         assertThat(r.next(), is(true));
-        assertThat(r.getString(1), is(plan));
+        assertThat(r.getString(1), isLinux(plan));
       }
     }
   }

Reply via email to