You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by za...@apache.org on 2019/11/27 06:34:28 UTC

[calcite] 02/02: [CALCITE-3141] Slow tests are not run in continuous integration

This is an automated email from the ASF dual-hosted git repository.

zabetak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit eb71946c843b3dc9618a2c128dcaa9bf52afe9c7
Author: Stamatis Zampetakis <za...@gmail.com>
AuthorDate: Thu Jul 18 20:18:57 2019 +0200

    [CALCITE-3141] Slow tests are not run in continuous integration
    
    1. Replace @Category(SlowTests.class) with @Tag("slow").
    2. Replace junit4 APIs with junit5 APIs when @Tag annotation is used since mixed usages do not work well together.
    3. Use exclusively the @Tag("slow") annotation for tagging slow tests.
    4. Remove usages of calcite.test.slow system property for annotating slow tests.
    5. Replace @Ignore("slow") with @Tag("slow") enabling a few more slow tests.
    6. Add testSlow task in Gradle for running slow tests.
    7. Exclude slow test execution from normal Gradle build and test.
    8. Add GitHub Action for running slow tests on demand (by adding
    'slow-tests-needed' label) in a PR and on every push to the master.
    9. Update site instructions for running slow tests.
    10. Silence logger in SqlOperatorBaseTest since it generates huge logs.
    11. Remove execution of slow test from Travis.
    12. Refactor FoodmartTest based on the needs of the new @ParameterizedTest API.
    13. Add header and description in main.yml file.
---
 .github/workflows/main.yml                         |  44 +++++++-
 .travis.yml                                        |   7 +-
 build.gradle.kts                                   |  15 ++-
 .../calcite/config/CalciteSystemProperty.java      |   6 --
 .../calcite/materialize/LatticeSuggesterTest.java  |   7 +-
 .../org/apache/calcite/profile/ProfilerTest.java   |  13 ++-
 .../calcite/sql/test/SqlOperatorBaseTest.java      | 120 ++++++++++++++-------
 .../java/org/apache/calcite/test/FoodmartTest.java |  97 ++++++-----------
 .../java/org/apache/calcite/test/LatticeTest.java  |  12 +--
 .../apache/calcite/test/MaterializationTest.java   |  34 +++---
 .../org/apache/calcite/test/RelMetadataTest.java   |  33 +++---
 .../org/apache/calcite/test/RexProgramTest.java    |   3 +-
 .../java/org/apache/calcite/test/SlowTests.java    |  25 -----
 .../java/org/apache/calcite/tools/PlannerTest.java |  56 ++++++----
 .../calcite/util/PartiallyOrderedSetTest.java      |  17 +--
 core/src/test/resources/log4j.properties           |   2 +-
 .../apache/calcite/adapter/tpcds/TpcdsTest.java    |  26 ++---
 .../org/apache/calcite/adapter/tpch/TpchTest.java  |  40 +++----
 site/_docs/howto.md                                |  17 ++-
 19 files changed, 301 insertions(+), 273 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c0bdd09..7c44e14 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,3 +1,24 @@
+# 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.
+
+# The default workflow for GitHub Actions that is used for continuous
+# integration. A configuration file that is used to control when, where,
+# and how different CI jobs are executed.
+# For more information on how to modify this file check the following link:
+# https://help.github.com/en/actions/automating-your-workflow-with-github-actions
+
 name: CI
 
 on:
@@ -7,15 +28,15 @@ on:
     branches:
       - '*'
   pull_request:
+    types: [opened, synchronize, reopened, labeled]
     paths-ignore:
       - 'site/**'
     branches:
       - '*'
 
-# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/software-installed-on-github-hosted-runners
-
 jobs:
   windows:
+    if: github.event.action != 'labeled'
     name: 'Windows (JDK 8)'
     runs-on: windows-latest
     steps:
@@ -32,6 +53,7 @@ jobs:
         ./gradlew --no-parallel --no-daemon build javadoc
 
   linux-avatica:
+    if: github.event.action != 'labeled'
     name: 'Linux (JDK 11), Avatica master'
     runs-on: ubuntu-latest
     steps:
@@ -52,6 +74,7 @@ jobs:
         ./gradlew --no-parallel --no-daemon build javadoc -Pcalcite.avatica.version=1.0.0-dev-master-SNAPSHOT -PenableMavenLocal
 
   mac:
+    if: github.event.action != 'labeled'
     name: 'macOS (JDK 13)'
     runs-on: macos-latest
     steps:
@@ -66,3 +89,20 @@ jobs:
         run: |
           ./gradlew --no-parallel --no-daemon build javadoc
 
+  linux-slow:
+    # Run slow tests when the commit is on master or it is requested explicitly by adding an
+    # appropriate label in the PR
+    if: github.ref == 'refs/heads/master' || contains(github.event.pull_request.labels.*.name, 'slow-tests-needed')
+    name: 'Linux (JDK 8) Slow Tests'
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@master
+        with:
+          fetch-depth: 50
+      - name: 'Set up JDK 8'
+        uses: actions/setup-java@v1
+        with:
+          java-version: 8
+      - name: 'Test'
+        run: |
+          ./gradlew --no-parallel --no-daemon testSlow
diff --git a/.travis.yml b/.travis.yml
index ec94f6f..dc73493 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,9 +22,6 @@ matrix:
   include:
     - jdk: openjdk8
     - jdk: openjdk11
-    - jdk: openjdk11
-      env:
-        - SLOW_TESTS=Y
 branches:
   only:
     - master
@@ -34,9 +31,7 @@ branches:
     - /^[0-9]+-.*$/
 install: true
 script:
-  # Print surefire output to the console instead of files
-  - if [ $SLOW_TESTS = "Y" ]; then export TEST_TAGS=-PincludeTestTags=org.apache.calcite.test.SlowTests; fi
-  - ./gradlew --no-daemon build $TEST_TAGS
+  - ./gradlew --no-daemon build
 git:
   depth: 100
 cache:
diff --git a/build.gradle.kts b/build.gradle.kts
index c72a975..26f9aba 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -65,7 +65,6 @@ val skipSpotless by props()
 val skipJavadoc by props()
 val enableMavenLocal by props()
 val enableGradleMetadata by props()
-val includeTestTags by props("!org.apache.calcite.test.SlowTests")
 // By default use Java implementation to sign artifacts
 // When useGpgCmd=true, then gpg command line tool is used for signing
 val useGpgCmd by props()
@@ -503,9 +502,7 @@ allprojects {
             }
             withType<Test>().configureEach {
                 useJUnitPlatform {
-                    if (includeTestTags.isNotBlank()) {
-                        includeTags.add(includeTestTags)
-                    }
+                    excludeTags("slow")
                 }
                 testLogging {
                     exceptionFormat = TestExceptionFormat.FULL
@@ -577,6 +574,16 @@ allprojects {
                     }
                 }))
             }
+            // Cannot be moved above otherwise configure each will override
+            // also the specific configurations below.
+            register<Test>("testSlow") {
+                group = LifecycleBasePlugin.VERIFICATION_GROUP
+                description = "Runs the slow unit tests."
+                useJUnitPlatform() {
+                    includeTags("slow")
+                }
+                jvmArgs("-Xmx6g")
+            }
             withType<SpotBugsTask>().configureEach {
                 group = LifecycleBasePlugin.VERIFICATION_GROUP
                 if (enableSpotBugs) {
diff --git a/core/src/main/java/org/apache/calcite/config/CalciteSystemProperty.java b/core/src/main/java/org/apache/calcite/config/CalciteSystemProperty.java
index fe7a7a1..4f23649 100644
--- a/core/src/main/java/org/apache/calcite/config/CalciteSystemProperty.java
+++ b/core/src/main/java/org/apache/calcite/config/CalciteSystemProperty.java
@@ -171,12 +171,6 @@ public final class CalciteSystemProperty<T> {
       });
 
   /**
-   * Whether to run slow tests.
-   */
-  public static final CalciteSystemProperty<Boolean> TEST_SLOW =
-      booleanProperty("calcite.test.slow", false);
-
-  /**
    * Whether to run MongoDB tests.
    */
   public static final CalciteSystemProperty<Boolean> TEST_MONGODB =
diff --git a/core/src/test/java/org/apache/calcite/materialize/LatticeSuggesterTest.java b/core/src/test/java/org/apache/calcite/materialize/LatticeSuggesterTest.java
index a7163ce..785732b 100644
--- a/core/src/test/java/org/apache/calcite/materialize/LatticeSuggesterTest.java
+++ b/core/src/test/java/org/apache/calcite/materialize/LatticeSuggesterTest.java
@@ -30,7 +30,6 @@ import org.apache.calcite.statistic.MapSqlStatisticProvider;
 import org.apache.calcite.statistic.QuerySqlStatisticProvider;
 import org.apache.calcite.test.CalciteAssert;
 import org.apache.calcite.test.FoodMartQuerySet;
-import org.apache.calcite.test.SlowTests;
 import org.apache.calcite.tools.FrameworkConfig;
 import org.apache.calcite.tools.Frameworks;
 import org.apache.calcite.tools.Planner;
@@ -45,8 +44,8 @@ import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -63,7 +62,7 @@ import static org.junit.Assert.assertThat;
 /**
  * Unit tests for {@link LatticeSuggester}.
  */
-@Category(SlowTests.class)
+@Tag("slow")
 public class LatticeSuggesterTest {
 
   /** Some basic query patterns on the Scott schema with "EMP" and "DEPT"
diff --git a/core/src/test/java/org/apache/calcite/profile/ProfilerTest.java b/core/src/test/java/org/apache/calcite/profile/ProfilerTest.java
index 8e82431..b797bee 100644
--- a/core/src/test/java/org/apache/calcite/profile/ProfilerTest.java
+++ b/core/src/test/java/org/apache/calcite/profile/ProfilerTest.java
@@ -22,7 +22,6 @@ import org.apache.calcite.linq4j.Enumerator;
 import org.apache.calcite.rel.metadata.NullSentinel;
 import org.apache.calcite.test.CalciteAssert;
 import org.apache.calcite.test.Matchers;
-import org.apache.calcite.test.SlowTests;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.JsonBuilder;
 import org.apache.calcite.util.TestUtil;
@@ -34,9 +33,9 @@ import com.google.common.collect.Multimap;
 import com.google.common.collect.Ordering;
 
 import org.hamcrest.Matcher;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
@@ -60,7 +59,7 @@ import static org.junit.Assert.assertThat;
 /**
  * Unit tests for {@link Profiler}.
  */
-@Category(SlowTests.class)
+@Tag("slow")
 public class ProfilerTest {
   @Test public void testProfileZeroRows() throws Exception {
     final String sql = "select * from \"scott\".dept where false";
@@ -261,7 +260,7 @@ public class ProfilerTest {
   }
 
   /** As {@link #testProfileScott3()}, but uses the breadth-first profiler. */
-  @Ignore
+  @Disabled
   @Test public void testProfileScott5() throws Exception {
     scott().factory(Fluid.PROFILER_FACTORY).unordered(
         "{type:distribution,columns:[COMM],values:[0.00,300.00,500.00,1400.00],cardinality:5,nullCount:10,expectedCardinality:14.0,surprise:0.473}",
@@ -285,7 +284,7 @@ public class ProfilerTest {
 
   /** Profiles a star-join query on the Foodmart schema using the breadth-first
    * profiler. */
-  @Ignore
+  @Disabled
   @Test public void testProfileFoodmart() throws Exception {
     foodmart().factory(Fluid.PROFILER_FACTORY).unordered(
         "{type:distribution,columns:[brand_name],cardinality:111,expectedCardinality:86837.0,surprise:0.997}",
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
index ef28246..3632339 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
@@ -18,7 +18,6 @@ package org.apache.calcite.sql.test;
 
 import org.apache.calcite.avatica.util.DateTimeUtils;
 import org.apache.calcite.config.CalciteConnectionProperty;
-import org.apache.calcite.config.CalciteSystemProperty;
 import org.apache.calcite.linq4j.Linq4j;
 import org.apache.calcite.plan.Strong;
 import org.apache.calcite.rel.type.RelDataType;
@@ -68,8 +67,9 @@ import org.apache.calcite.util.trace.CalciteTrace;
 import com.google.common.base.Throwables;
 
 import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 
 import java.math.BigDecimal;
@@ -1504,6 +1504,18 @@ public abstract class SqlOperatorBaseTest {
     tester.checkNull("cast(cast(null as timestamp) as time)");
   }
 
+  private static Calendar getFixedCalendar() {
+    Calendar calendar = Util.calendar();
+    calendar.set(Calendar.YEAR, 2014);
+    calendar.set(Calendar.MONTH, 8);
+    calendar.set(Calendar.DATE, 7);
+    calendar.set(Calendar.HOUR_OF_DAY, 17);
+    calendar.set(Calendar.MINUTE, 8);
+    calendar.set(Calendar.SECOND, 48);
+    calendar.set(Calendar.MILLISECOND, 15);
+    return calendar;
+  }
+
   /**
    * Returns a Calendar that is the current time, pausing if we are within 2
    * minutes of midnight or the top of the hour.
@@ -3798,7 +3810,7 @@ public abstract class SqlOperatorBaseTest {
     tester.checkBoolean("'abbc' like 'a\\%c' escape '\\'", Boolean.FALSE);
   }
 
-  @Ignore("[CALCITE-525] Exception-handling in built-in functions")
+  @Disabled("[CALCITE-525] Exception-handling in built-in functions")
   @Test public void testLikeEscape2() {
     tester.checkBoolean("'x' not like 'x' escape 'x'", Boolean.TRUE);
     tester.checkBoolean("'xyz' not like 'xyz' escape 'xyz'", Boolean.TRUE);
@@ -5930,7 +5942,16 @@ public abstract class SqlOperatorBaseTest {
     tester.checkString("CURRENT_CATALOG", "", "VARCHAR(2000) NOT NULL");
   }
 
-  @Test public void testLocalTimeFunc() {
+  @Tag("slow")
+  @Test public void testLocalTimeFuncWithCurrentTime() {
+    testLocalTimeFunc(currentTimeString(LOCAL_TZ));
+  }
+
+  @Test public void testLocalTimeFuncWithFixedTime() {
+    testLocalTimeFunc(fixedTimeString(LOCAL_TZ));
+  }
+
+  private void testLocalTimeFunc(Pair<String, Hook.Closeable> pair) {
     tester.setFor(SqlStdOperatorTable.LOCALTIME);
     tester.checkScalar("LOCALTIME", TIME_PATTERN, "TIME(0) NOT NULL");
     tester.checkFails(
@@ -5941,7 +5962,6 @@ public abstract class SqlOperatorBaseTest {
         "LOCALTIME(1)", TIME_PATTERN,
         "TIME(1) NOT NULL");
 
-    final Pair<String, Hook.Closeable> pair = currentTimeString(LOCAL_TZ);
     tester.checkScalar(
         "CAST(LOCALTIME AS VARCHAR(30))",
         Pattern.compile(
@@ -5955,7 +5975,16 @@ public abstract class SqlOperatorBaseTest {
     pair.right.close();
   }
 
-  @Test public void testLocalTimestampFunc() {
+  @Tag("slow")
+  @Test public void testLocalTimestampFuncWithCurrentTime() {
+    testLocalTimestampFunc(currentTimeString(LOCAL_TZ));
+  }
+
+  @Test public void testLocalTimestampFuncWithFixedTime() {
+    testLocalTimestampFunc(fixedTimeString(LOCAL_TZ));
+  }
+
+  private void testLocalTimestampFunc(Pair<String, Hook.Closeable> pair) {
     tester.setFor(SqlStdOperatorTable.LOCALTIMESTAMP);
     tester.checkScalar(
         "LOCALTIMESTAMP", TIMESTAMP_PATTERN,
@@ -5975,8 +6004,6 @@ public abstract class SqlOperatorBaseTest {
 
     // Check that timestamp is being generated in the right timezone by
     // generating a specific timestamp.
-    final Pair<String, Hook.Closeable> pair = currentTimeString(
-        LOCAL_TZ);
     tester.checkScalar(
         "CAST(LOCALTIMESTAMP AS VARCHAR(30))",
         Pattern.compile(pair.left + "[0-9][0-9]:[0-9][0-9]"),
@@ -5988,7 +6015,16 @@ public abstract class SqlOperatorBaseTest {
     pair.right.close();
   }
 
-  @Test public void testCurrentTimeFunc() {
+  @Tag("slow")
+  @Test public void testCurrentTimeFuncWithCurrentTime() {
+    testCurrentTimeFunc(currentTimeString(CURRENT_TZ));
+  }
+
+  @Test public void testCurrentTimeFuncWithFixedTime() {
+    testCurrentTimeFunc(fixedTimeString(CURRENT_TZ));
+  }
+
+  private void testCurrentTimeFunc(Pair<String, Hook.Closeable> pair) {
     tester.setFor(SqlStdOperatorTable.CURRENT_TIME);
     tester.checkScalar(
         "CURRENT_TIME", TIME_PATTERN,
@@ -6000,7 +6036,6 @@ public abstract class SqlOperatorBaseTest {
     tester.checkScalar(
         "CURRENT_TIME(1)", TIME_PATTERN, "TIME(1) NOT NULL");
 
-    final Pair<String, Hook.Closeable> pair = currentTimeString(CURRENT_TZ);
     tester.checkScalar(
         "CAST(CURRENT_TIME AS VARCHAR(30))",
         Pattern.compile(pair.left.substring(11) + "[0-9][0-9]:[0-9][0-9]"),
@@ -6012,7 +6047,16 @@ public abstract class SqlOperatorBaseTest {
     pair.right.close();
   }
 
-  @Test public void testCurrentTimestampFunc() {
+  @Tag("slow")
+  @Test public void testCurrentTimestampFuncWithCurrentTime() {
+    testCurrentTimestampFunc(currentTimeString(CURRENT_TZ));
+  }
+
+  @Test public void testCurrentTimestampFuncWithFixedTime() {
+    testCurrentTimestampFunc(fixedTimeString(CURRENT_TZ));
+  }
+
+  private void testCurrentTimestampFunc(Pair<String, Hook.Closeable> pair) {
     tester.setFor(SqlStdOperatorTable.CURRENT_TIMESTAMP);
     tester.checkScalar(
         "CURRENT_TIMESTAMP", TIMESTAMP_PATTERN,
@@ -6027,8 +6071,6 @@ public abstract class SqlOperatorBaseTest {
         "CURRENT_TIMESTAMP(1)", TIMESTAMP_PATTERN,
         "TIMESTAMP(1) NOT NULL");
 
-    final Pair<String, Hook.Closeable> pair = currentTimeString(
-        CURRENT_TZ);
     tester.checkScalar(
         "CAST(CURRENT_TIMESTAMP AS VARCHAR(30))",
         Pattern.compile(pair.left + "[0-9][0-9]:[0-9][0-9]"),
@@ -6051,31 +6093,35 @@ public abstract class SqlOperatorBaseTest {
    * @return Time string
    */
   protected static Pair<String, Hook.Closeable> currentTimeString(TimeZone tz) {
-    final Calendar calendar;
-    final Hook.Closeable closeable;
-    if (CalciteSystemProperty.TEST_SLOW.value()) {
-      calendar = getCalendarNotTooNear(Calendar.HOUR_OF_DAY);
-      closeable = () -> { };
-    } else {
-      calendar = Util.calendar();
-      calendar.set(Calendar.YEAR, 2014);
-      calendar.set(Calendar.MONTH, 8);
-      calendar.set(Calendar.DATE, 7);
-      calendar.set(Calendar.HOUR_OF_DAY, 17);
-      calendar.set(Calendar.MINUTE, 8);
-      calendar.set(Calendar.SECOND, 48);
-      calendar.set(Calendar.MILLISECOND, 15);
-      final long timeInMillis = calendar.getTimeInMillis();
-      closeable = Hook.CURRENT_TIME.addThread(
-          (Consumer<Holder<Long>>) o -> o.set(timeInMillis));
-    }
+    final Calendar calendar = getCalendarNotTooNear(Calendar.HOUR_OF_DAY);
+    final Hook.Closeable closeable = () -> { };
+    return Pair.of(toTimeString(tz, calendar), closeable);
+  }
+
+  private static Pair<String, Hook.Closeable> fixedTimeString(TimeZone tz) {
+    final Calendar calendar = getFixedCalendar();
+    final long timeInMillis = calendar.getTimeInMillis();
+    final Hook.Closeable closeable = Hook.CURRENT_TIME.addThread(
+        (Consumer<Holder<Long>>) o -> o.set(timeInMillis));
+    return Pair.of(toTimeString(tz, calendar), closeable);
+  }
 
+  private static String toTimeString(TimeZone tz, Calendar cal) {
     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:", Locale.ROOT);
     sdf.setTimeZone(tz);
-    return Pair.of(sdf.format(calendar.getTime()), closeable);
+    return sdf.format(cal.getTime());
+  }
+
+  @Tag("slow")
+  @Test public void testCurrentDateFuncWithCurrentTime() {
+    testCurrentDateFunc(currentTimeString(LOCAL_TZ));
   }
 
-  @Test public void testCurrentDateFunc() {
+  @Test public void testCurrentDateFuncWithFixedTime() {
+    testCurrentDateFunc(fixedTimeString(LOCAL_TZ));
+  }
+
+  private void testCurrentDateFunc(Pair<String, Hook.Closeable> pair) {
     tester.setFor(SqlStdOperatorTable.CURRENT_DATE, VM_FENNEL);
 
     // A tester with a lenient conformance that allows parentheses.
@@ -6104,7 +6150,6 @@ public abstract class SqlOperatorBaseTest {
     tester1.checkType("CURRENT_TIME()", "TIME(0) NOT NULL");
 
     // Check the actual value.
-    final Pair<String, Hook.Closeable> pair = currentTimeString(LOCAL_TZ);
     final String dateString = pair.left;
     try (Hook.Closeable ignore = pair.right) {
       tester.checkScalar("CAST(CURRENT_DATE AS VARCHAR(30))",
@@ -8810,10 +8855,9 @@ public abstract class SqlOperatorBaseTest {
    * to be fixed especially cases where the query passes from the validation stage
    * and fails at runtime.
    * */
+  @Disabled("Too slow and not really a unit test")
+  @Tag("slow")
   @Test public void testArgumentBounds() {
-    if (!CalciteSystemProperty.TEST_SLOW.value()) {
-      return;
-    }
     final SqlValidatorImpl validator = (SqlValidatorImpl) tester.getValidator();
     final SqlValidatorScope scope = validator.getEmptyScope();
     final RelDataTypeFactory typeFactory = validator.getTypeFactory();
diff --git a/core/src/test/java/org/apache/calcite/test/FoodmartTest.java b/core/src/test/java/org/apache/calcite/test/FoodmartTest.java
index d9165f3..8efd2b2 100644
--- a/core/src/test/java/org/apache/calcite/test/FoodmartTest.java
+++ b/core/src/test/java/org/apache/calcite/test/FoodmartTest.java
@@ -20,23 +20,22 @@ import org.apache.calcite.config.CalciteSystemProperty;
 import org.apache.calcite.linq4j.tree.Primitive;
 import org.apache.calcite.util.IntegerIntervalSet;
 
-import com.google.common.collect.ImmutableList;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
 
 import java.io.IOException;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Stream;
 
 /**
  * Test case that runs the FoodMart reference queries.
  */
-@Category(SlowTests.class)
-@RunWith(Parameterized.class)
+@Tag("slow")
 public class FoodmartTest {
 
   private static final int[] DISABLED_IDS = {
@@ -97,19 +96,10 @@ public class FoodmartTest {
   //
 
   // 202 and others: strip away "CAST(the_year AS UNSIGNED) = 1997"
-
-  private final FoodMartQuerySet.FoodmartQuery query;
-
-  @Parameterized.Parameters(name = "{index}: foodmart({0})={1}")
-  public static List<Object[]> getSqls() throws IOException {
+  public static Stream<FoodMartQuerySet.FoodmartQuery> queries() throws IOException {
     String idList = CalciteSystemProperty.TEST_FOODMART_QUERY_IDS.value();
-    if (!CalciteSystemProperty.TEST_SLOW.value() && idList == null) {
-      // Avoid loading the query set in a regular test suite run. It burns too
-      // much memory.
-      return ImmutableList.of();
-    }
     final FoodMartQuerySet set = FoodMartQuerySet.instance();
-    final List<Object[]> list = new ArrayList<Object[]>();
+    final List<FoodMartQuerySet.FoodmartQuery> list = new ArrayList<>();
     if (idList != null) {
       if (idList.endsWith(",-disabled")) {
         StringBuilder buf = new StringBuilder(idList);
@@ -122,64 +112,45 @@ public class FoodmartTest {
       for (Integer id : IntegerIntervalSet.of(idList)) {
         final FoodMartQuerySet.FoodmartQuery query1 = set.queries.get(id);
         if (query1 != null) {
-          list.add(new Object[] {id /*, query1.sql */});
+          list.add(query1);
         }
       }
     } else {
       for (FoodMartQuerySet.FoodmartQuery query1 : set.queries.values()) {
-        if (!CalciteSystemProperty.TEST_SLOW.value() && query1.id != 2) {
-          // If slow queries are not enabled, only run query #2.
-          continue;
-        }
         if (Primitive.asList(DISABLED_IDS).contains(query1.id)) {
           continue;
         }
-        list.add(new Object[]{query1.id /*, query1.sql */});
+        list.add(query1);
       }
     }
-    return list;
+    return list.stream();
   }
 
-  public FoodmartTest(int id) throws IOException {
-    if (id < 0) {
-      this.query = new FoodMartQuerySet.FoodmartQuery();
-      query.id = id;
-      query.sql = "select * from (values 1) as t(c)";
-    } else {
-      this.query = FoodMartQuerySet.instance().queries.get(id);
-    }
-    assert query.id == id : id + ":" + query.id;
-  }
-
-  @Test(timeout = 120000)
-  public void test() {
-    try {
+  @ParameterizedTest
+  @MethodSource("queries")
+  public void test(FoodMartQuerySet.FoodmartQuery query) {
+    Assertions.assertTimeoutPreemptively(Duration.ofMinutes(2), () -> {
       CalciteAssert.that()
-          .with(CalciteAssert.Config.FOODMART_CLONE)
-          .pooled()
-          .query(query.sql)
-          .runs();
-    } catch (Throwable e) {
-      throw new RuntimeException("Test failed, id=" + query.id + ", sql="
-          + query.sql, e);
-    }
+        .with(CalciteAssert.Config.FOODMART_CLONE)
+        .pooled()
+        .query(query.sql)
+        .runs();
+    });
   }
 
-  @Test(timeout = 60000)
-  @Ignore
-  public void testWithLattice() {
-    try {
+  @ParameterizedTest
+  @Disabled
+  @MethodSource("queries")
+  public void testWithLattice(FoodMartQuerySet.FoodmartQuery query) {
+    Assertions.assertTimeoutPreemptively(Duration.ofMinutes(2), () -> {
       CalciteAssert.that()
-          .with(CalciteAssert.Config.JDBC_FOODMART_WITH_LATTICE)
-          .pooled()
-          .withDefaultSchema("foodmart")
-          .query(query.sql)
-          .enableMaterializations(true)
-          .runs();
-    } catch (Throwable e) {
-      throw new RuntimeException("Test failed, id=" + query.id + ", sql="
-          + query.sql, e);
-    }
+        .with(CalciteAssert.Config.JDBC_FOODMART_WITH_LATTICE)
+        .pooled()
+        .withDefaultSchema("foodmart")
+        .query(query.sql)
+        .enableMaterializations(true)
+        .runs();
+    });
   }
 
 }
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 a5595a7..7ccff90 100644
--- a/core/src/test/java/org/apache/calcite/test/LatticeTest.java
+++ b/core/src/test/java/org/apache/calcite/test/LatticeTest.java
@@ -33,9 +33,9 @@ import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 
 import org.junit.Assume;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
 import java.sql.Connection;
@@ -62,7 +62,7 @@ import static org.junit.Assert.assertThat;
 /**
  * Unit test for lattices.
  */
-@Category(SlowTests.class)
+@Tag("slow")
 public class LatticeTest {
   private static final String SALES_LATTICE = "{\n"
       + "  name: 'star',\n"
@@ -683,7 +683,7 @@ public class LatticeTest {
   /** Runs all queries against the Foodmart schema, using a lattice.
    *
    * <p>Disabled for normal runs, because it is slow. */
-  @Ignore
+  @Disabled
   @Test public void testAllFoodmartQueries() throws IOException {
     // Test ids that had bugs in them until recently. Useful for a sanity check.
     final List<Integer> fixed = ImmutableList.of(13, 24, 28, 30, 61, 76, 79, 81,
@@ -885,7 +885,7 @@ public class LatticeTest {
   /** Test case for
    * <a href="https://issues.apache.org/jira/browse/CALCITE-760">[CALCITE-760]
    * Aggregate recommender blows up if row count estimate is too high</a>. */
-  @Ignore
+  @Disabled
   @Test public void testLatticeWithBadRowCountEstimate() {
     final String lattice =
         INVENTORY_LATTICE.replace("rowCountEstimate: 4070,",
diff --git a/core/src/test/java/org/apache/calcite/test/MaterializationTest.java b/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
index 1d38d8b..8484945 100644
--- a/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
+++ b/core/src/test/java/org/apache/calcite/test/MaterializationTest.java
@@ -58,9 +58,9 @@ import org.apache.calcite.util.mapping.IntPair;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Ordering;
 
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 
 import java.math.BigDecimal;
 import java.sql.ResultSet;
@@ -85,7 +85,7 @@ import static org.junit.Assert.assertTrue;
  * query and one or more materializations (what Oracle calls materialized views)
  * and checks that the materialization is used.
  */
-@Category(SlowTests.class)
+@Tag("slow")
 public class MaterializationTest {
   private static final Consumer<ResultSet> CONTAINS_M0 =
       CalciteAssert.checkResultContains(
@@ -1064,33 +1064,33 @@ public class MaterializationTest {
         CalciteAssert.checkResultContains("EnumerableTableScan(table=[[mat, m0]])"));
   }
 
-  @Ignore
+  @Disabled
   @Test public void testOrderByQueryOnProjectView() {
     checkMaterialize(
         "select \"deptno\", \"empid\" from \"emps\"",
         "select \"empid\" from \"emps\" order by \"deptno\"");
   }
 
-  @Ignore
+  @Disabled
   @Test public void testOrderByQueryOnOrderByView() {
     checkMaterialize(
         "select \"deptno\", \"empid\" from \"emps\" order by \"deptno\"",
         "select \"empid\" from \"emps\" order by \"deptno\"");
   }
 
-  @Ignore
+  @Disabled
   @Test public void testDifferentColumnNames() {}
 
-  @Ignore
+  @Disabled
   @Test public void testDifferentType() {}
 
-  @Ignore
+  @Disabled
   @Test public void testPartialUnion() {}
 
-  @Ignore
+  @Disabled
   @Test public void testNonDisjointUnion() {}
 
-  @Ignore
+  @Disabled
   @Test public void testMaterializationReferencesTableInOtherSchema() {}
 
   /** Unit test for logic functions
@@ -1413,7 +1413,7 @@ public class MaterializationTest {
    * <li>query has a condition on one of the materialization's grouping columns.
    * </ol>
    */
-  @Ignore
+  @Disabled
   @Test public void testFilterGroupQueryOnStar() {
     checkMaterialize("select p.\"product_name\", t.\"the_year\",\n"
             + "  sum(f.\"unit_sales\") as \"sum_unit_sales\", count(*) as \"c\"\n"
@@ -1446,7 +1446,7 @@ public class MaterializationTest {
 
   /** Simpler than {@link #testFilterGroupQueryOnStar()}, tests a query on a
    * materialization that is just a join. */
-  @Ignore
+  @Disabled
   @Test public void testQueryOnStar() {
     String q = "select *\n"
         + "from \"foodmart\".\"sales_fact_1997\" as f\n"
@@ -1461,7 +1461,7 @@ public class MaterializationTest {
   /** A materialization that is a join of a union cannot at present be converted
    * to a star table and therefore cannot be recognized. This test checks that
    * nothing unpleasant happens. */
-  @Ignore
+  @Disabled
   @Test public void testJoinOnUnionMaterialization() {
     String q = "select *\n"
         + "from (select * from \"emps\" union all select * from \"emps\")\n"
@@ -1675,7 +1675,7 @@ public class MaterializationTest {
                 + "      EnumerableTableScan(table=[[hr, m0]])"));
   }
 
-  @Ignore
+  @Disabled
   @Test public void testAggregateMaterializationAggregateFuncs8() {
     // TODO: It should work, but top project in the query is not matched by the planner.
     // It needs further checking.
@@ -2036,7 +2036,7 @@ public class MaterializationTest {
                 + "      EnumerableTableScan(table=[[hr, m0]])"));
   }
 
-  @Ignore
+  @Disabled
   @Test public void testJoinAggregateMaterializationAggregateFuncs6() {
     // This rewriting would be possible if planner generates a pre-aggregation,
     // since the materialized view would match the sub-query.
@@ -2622,7 +2622,7 @@ public class MaterializationTest {
     }
   }
 
-  @Ignore("Creating mv for depts considering all its column throws exception")
+  @Disabled("Creating mv for depts considering all its column throws exception")
   @Test public void testMultiMaterializationOnJoinQuery() {
     final String q = "select *\n"
         + "from \"emps\"\n"
diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
index df4e0ed..2341f0a 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -104,12 +104,11 @@ import com.google.common.collect.Sets;
 import org.hamcrest.CoreMatchers;
 import org.hamcrest.CustomTypeSafeMatcher;
 import org.hamcrest.Matcher;
-import org.hamcrest.core.Is;
 import org.junit.Assume;
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 
-import java.io.File;
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.util.ArrayList;
@@ -222,14 +221,14 @@ public class RelMetadataTest extends SqlToRelTestBase {
         1.0);
   }
 
-  @Ignore
+  @Disabled
   @Test public void testPercentageOriginalRowsOneFilter() {
     checkPercentageOriginalRows(
         "select * from dept where deptno = 20",
         DEFAULT_EQUAL_SELECTIVITY);
   }
 
-  @Ignore
+  @Disabled
   @Test public void testPercentageOriginalRowsTwoFilters() {
     checkPercentageOriginalRows("select * from (\n"
         + "  select * from dept where name='X')\n"
@@ -237,7 +236,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
         DEFAULT_EQUAL_SELECTIVITY_SQUARED);
   }
 
-  @Ignore
+  @Disabled
   @Test public void testPercentageOriginalRowsRedundantFilter() {
     checkPercentageOriginalRows("select * from (\n"
         + "  select * from dept where deptno=20)\n"
@@ -251,7 +250,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
         1.0);
   }
 
-  @Ignore
+  @Disabled
   @Test public void testPercentageOriginalRowsJoinTwoFilters() {
     checkPercentageOriginalRows("select * from (\n"
         + "  select * from emp where deptno=10) e\n"
@@ -266,7 +265,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
         1.0);
   }
 
-  @Ignore
+  @Disabled
   @Test public void testPercentageOriginalRowsUnionLittleFilter() {
     checkPercentageOriginalRows(
         "select name from dept where deptno=20"
@@ -275,7 +274,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
             / (DEPT_SIZE + EMP_SIZE));
   }
 
-  @Ignore
+  @Disabled
   @Test public void testPercentageOriginalRowsUnionBigFilter() {
     checkPercentageOriginalRows(
         "select name from dept"
@@ -835,10 +834,11 @@ public class RelMetadataTest extends SqlToRelTestBase {
   /** Test case for
    * <a href="https://issues.apache.org/jira/browse/CALCITE-1808">[CALCITE-1808]
    * JaninoRelMetadataProvider loading cache might cause
-   * OutOfMemoryError</a>. */
+   * OutOfMemoryError</a>.
+   *
+   * Too slow to run every day, and it does not reproduce the issue. */
+  @Tag("slow")
   @Test public void testMetadataHandlerCacheLimit() {
-    Assume.assumeTrue("too slow to run every day, and it does not reproduce the issue",
-        CalciteSystemProperty.TEST_SLOW.value());
     Assume.assumeTrue("If cache size is too large, this test may fail and the "
             + "test won't be to blame",
         CalciteSystemProperty.METADATA_HANDLER_CACHE_MAXIMUM_SIZE.value()
@@ -1795,11 +1795,8 @@ public class RelMetadataTest extends SqlToRelTestBase {
    * <a href="https://issues.apache.org/jira/browse/CALCITE-2205">[CALCITE-2205]</a>.
    * Since this is a performance problem, the test result does not
    * change, but takes over 15 minutes before the fix and 6 seconds after. */
-  @Test(timeout = 20_000) public void testPullUpPredicatesForExprsItr() {
-    // If we're running Windows, we are probably in a VM and the test may
-    // exceed timeout by a small margin.
-    Assume.assumeThat("Too slow to run on Windows",
-        File.separatorChar, Is.is('/'));
+  @Test
+  public void testPullUpPredicatesForExprsItr() {
     final String sql = "select a.EMPNO, a.ENAME\n"
         + "from (select * from sales.emp ) a\n"
         + "join (select * from sales.emp  ) b\n"
diff --git a/core/src/test/java/org/apache/calcite/test/RexProgramTest.java b/core/src/test/java/org/apache/calcite/test/RexProgramTest.java
index 2c7c7d8..434983a 100644
--- a/core/src/test/java/org/apache/calcite/test/RexProgramTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RexProgramTest.java
@@ -17,7 +17,6 @@
 package org.apache.calcite.test;
 
 import org.apache.calcite.avatica.util.ByteString;
-import org.apache.calcite.config.CalciteSystemProperty;
 import org.apache.calcite.plan.RelOptPredicateList;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.Strong;
@@ -994,7 +993,7 @@ public class RexProgramTest extends RexProgramBuilderBase {
    * to CNF. */
   @Test public void testCnfExponential() {
     // run out of memory if limit is higher than about 20
-    final int limit = CalciteSystemProperty.TEST_SLOW.value() ? 16 : 6;
+    int limit = 16;
     for (int i = 2; i < limit; i++) {
       checkExponentialCnf(i);
     }
diff --git a/core/src/test/java/org/apache/calcite/test/SlowTests.java b/core/src/test/java/org/apache/calcite/test/SlowTests.java
deleted file mode 100644
index af32a8b..0000000
--- a/core/src/test/java/org/apache/calcite/test/SlowTests.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.calcite.test;
-
-/**
- * Declares a JUnit category for slow tests to speedup CI.
- */
-public interface SlowTests {
-}
-
-// End SlowTests.java
diff --git a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
index ec677be..8ca9533 100644
--- a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
+++ b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
@@ -25,7 +25,6 @@ import org.apache.calcite.adapter.jdbc.JdbcConvention;
 import org.apache.calcite.adapter.jdbc.JdbcImplementor;
 import org.apache.calcite.adapter.jdbc.JdbcRel;
 import org.apache.calcite.adapter.jdbc.JdbcRules;
-import org.apache.calcite.config.CalciteSystemProperty;
 import org.apache.calcite.config.Lex;
 import org.apache.calcite.plan.ConventionTraitDef;
 import org.apache.calcite.plan.RelOptCluster;
@@ -89,8 +88,10 @@ import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 
 import org.hamcrest.Matcher;
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -134,11 +135,13 @@ public class PlannerTest {
         + "    EnumerableTableScan(table=[[hr, emps]])\n");
   }
 
-  @Test(expected = SqlParseException.class)
-  public void testParseIdentiferMaxLengthWithDefault() throws Exception {
-    Planner planner = getPlanner(null, SqlParser.configBuilder().build());
-    planner.parse("select name as "
-        + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa from \"emps\"");
+  @Test
+  public void testParseIdentiferMaxLengthWithDefault() {
+    Assertions.assertThrows(SqlParseException.class, () -> {
+      Planner planner = getPlanner(null, SqlParser.configBuilder().build());
+      planner.parse("select name as "
+          + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa from \"emps\"");
+    });
   }
 
   @Test
@@ -417,7 +420,7 @@ public class PlannerTest {
         plan, extraRules);
   }
 
-  @Ignore("[CALCITE-2773] java.lang.AssertionError: rel"
+  @Disabled("[CALCITE-2773] java.lang.AssertionError: rel"
       + " [rel#69:EnumerableUnion.ENUMERABLE.[](input#0=RelSubset#78,input#1=RelSubset#71,all=true)]"
       + " has lower cost {4.0 rows, 4.0 cpu, 0.0 io} than best cost {5.0 rows, 5.0 cpu, 0.0 io}"
       + " of subset [rel#67:Subset#6.ENUMERABLE.[]]")
@@ -473,7 +476,7 @@ public class PlannerTest {
         toString(transform), equalTo(plan));
   }
 
-  @Ignore("[CALCITE-2773] java.lang.AssertionError: rel"
+  @Disabled("[CALCITE-2773] java.lang.AssertionError: rel"
       + " [rel#17:EnumerableUnion.ENUMERABLE.[](input#0=RelSubset#26,input#1=RelSubset#19,all=true)]"
       + " has lower cost {4.0 rows, 4.0 cpu, 0.0 io}"
       + " than best cost {5.0 rows, 5.0 cpu, 0.0 io} of subset [rel#15:Subset#5.ENUMERABLE.[]]")
@@ -878,9 +881,29 @@ public class PlannerTest {
             + "  MockJdbcTableScan(table=[[hr, emps]])\n"));
   }
 
-  /** Unit test that plans a query with a large number of joins. */
-  @Test public void testPlanNWayJoin()
+  @Test public void testPlan5WayJoin()
+      throws Exception {
+    checkJoinNWay(5); // LoptOptimizeJoinRule disabled; takes about .4s
+  }
+
+  @Test public void testPlan9WayJoin()
       throws Exception {
+    checkJoinNWay(9); // LoptOptimizeJoinRule enabled; takes about 0.04s
+  }
+
+  @Test public void testPlan35WayJoin()
+      throws Exception {
+    checkJoinNWay(35); // takes about 2s
+  }
+
+  @Tag("slow")
+  @Test public void testPlan60WayJoin()
+      throws Exception {
+    checkJoinNWay(60); // takes about 15s
+  }
+
+  /** Test that plans a query with a large number of joins. */
+  private void checkJoinNWay(int n) throws Exception {
     // Here the times before and after enabling LoptOptimizeJoinRule.
     //
     // Note the jump between N=6 and N=7; LoptOptimizeJoinRule is disabled if
@@ -899,15 +922,6 @@ public class PlannerTest {
     //      13 OOM              96
     //      35 OOM           1,716
     //      60 OOM          12,230
-    checkJoinNWay(5); // LoptOptimizeJoinRule disabled; takes about .4s
-    checkJoinNWay(9); // LoptOptimizeJoinRule enabled; takes about 0.04s
-    checkJoinNWay(35); // takes about 2s
-    if (CalciteSystemProperty.TEST_SLOW.value()) {
-      checkJoinNWay(60); // takes about 15s
-    }
-  }
-
-  private void checkJoinNWay(int n) throws Exception {
     final StringBuilder buf = new StringBuilder();
     buf.append("select *");
     for (int i = 0; i < n; i++) {
diff --git a/core/src/test/java/org/apache/calcite/util/PartiallyOrderedSetTest.java b/core/src/test/java/org/apache/calcite/util/PartiallyOrderedSetTest.java
index 359e86d..81a1d49 100644
--- a/core/src/test/java/org/apache/calcite/util/PartiallyOrderedSetTest.java
+++ b/core/src/test/java/org/apache/calcite/util/PartiallyOrderedSetTest.java
@@ -16,12 +16,8 @@
  */
 package org.apache.calcite.util;
 
-import org.apache.calcite.config.CalciteSystemProperty;
-import org.apache.calcite.test.SlowTests;
-
-import org.junit.Assume;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 
 import java.util.AbstractList;
 import java.util.ArrayList;
@@ -43,12 +39,12 @@ import static org.junit.Assert.assertTrue;
 /**
  * Unit test for {@link PartiallyOrderedSet}.
  */
-@Category(SlowTests.class)
+@Tag("slow")
 public class PartiallyOrderedSetTest {
   private static final boolean DEBUG = false;
 
   // 100, 250, 1000, 3000 are reasonable
-  private static final int SCALE = CalciteSystemProperty.TEST_SLOW.value() ? 250 : 50;
+  private static final int SCALE = 250;
 
   final long seed = new Random().nextLong();
   final Random random = new Random(seed);
@@ -214,16 +210,13 @@ public class PartiallyOrderedSetTest {
   }
 
   @Test public void testPosetBitsLarge() {
-    Assume.assumeTrue(
-        "it takes 80 seconds, and the computations are exactly the same every time",
-        CalciteSystemProperty.TEST_SLOW.value());
+    // It takes 80 seconds, and the computations are exactly the same every time
     final PartiallyOrderedSet<Integer> poset =
         new PartiallyOrderedSet<>(PartiallyOrderedSetTest::isBitSuperset);
     checkPosetBitsLarge(poset, 30000, 2921, 164782);
   }
 
   @Test public void testPosetBitsLarge2() {
-    Assume.assumeTrue("too slow to run every day", CalciteSystemProperty.TEST_SLOW.value());
     final int n = 30000;
     final PartiallyOrderedSet<Integer> poset =
         new PartiallyOrderedSet<>(PartiallyOrderedSetTest::isBitSuperset,
diff --git a/core/src/test/resources/log4j.properties b/core/src/test/resources/log4j.properties
index 02b2ef7..073c0db 100644
--- a/core/src/test/resources/log4j.properties
+++ b/core/src/test/resources/log4j.properties
@@ -20,7 +20,7 @@ log4j.rootLogger=INFO, A1
 log4j.logger.org.apache.calcite.runtime.CalciteException=FATAL
 log4j.logger.org.apache.calcite.sql.validate.SqlValidatorException=FATAL
 log4j.logger.org.apache.calcite.plan.RexImplicationChecker=ERROR
-
+log4j.logger.org.apache.calcite.sql.test.SqlOperatorBaseTest=FATAL
 # A1 goes to the console
 log4j.appender.A1=org.apache.log4j.ConsoleAppender
 
diff --git a/plus/src/test/java/org/apache/calcite/adapter/tpcds/TpcdsTest.java b/plus/src/test/java/org/apache/calcite/adapter/tpcds/TpcdsTest.java
index b1ecb00..79bb846 100644
--- a/plus/src/test/java/org/apache/calcite/adapter/tpcds/TpcdsTest.java
+++ b/plus/src/test/java/org/apache/calcite/adapter/tpcds/TpcdsTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.calcite.adapter.tpcds;
 
-import org.apache.calcite.config.CalciteSystemProperty;
 import org.apache.calcite.plan.RelTraitDef;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.JoinRelType;
@@ -34,8 +33,9 @@ import org.apache.calcite.util.Holder;
 
 import net.hydromatic.tpcds.query.Query;
 
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 
 import java.util.List;
 import java.util.Random;
@@ -47,8 +47,9 @@ import static org.junit.Assert.assertThat;
 
 /** Unit test for {@link org.apache.calcite.adapter.tpcds.TpcdsSchema}.
  *
- * <p>Only runs if {@link org.apache.calcite.config.CalciteSystemProperty#TEST_SLOW} is set.</p>
+ * <p>Only runs as part of slow test suite.</p>
  */
+@Tag("slow")
 public class TpcdsTest {
   private static Consumer<Holder<Program>> handler(
       final boolean bushy, final int minJoinCount) {
@@ -82,8 +83,7 @@ public class TpcdsTest {
       + "}";
 
   private CalciteAssert.AssertThat with() {
-    return CalciteAssert.model(TPCDS_MODEL)
-        .enable(CalciteSystemProperty.TEST_SLOW.value());
+    return CalciteAssert.model(TPCDS_MODEL);
   }
 
   @Test public void testCallCenter() {
@@ -201,14 +201,14 @@ public class TpcdsTest {
   }
 
   /** Tests the customer table with scale factor 5. */
-  @Ignore("add tests like this that count each table")
+  @Disabled("add tests like this that count each table")
   @Test public void testCustomer5() {
     with()
         .query("select * from tpcds_5.customer")
         .returnsCount(750000);
   }
 
-  @Ignore("throws 'RuntimeException: Cannot convert null to long'")
+  @Disabled("throws 'RuntimeException: Cannot convert null to long'")
   @Test public void testQuery01() {
     checkQuery(1).runs();
   }
@@ -244,29 +244,29 @@ public class TpcdsTest {
                 + "                EnumerableTableScan(table=[[TPCDS, CATALOG_SALES]]): rowcount = 1441548.0, cumulative cost = {1441548.0 rows, 1441549.0 cpu, 0.0 io}\n"));
   }
 
-  @Ignore("throws 'RuntimeException: Cannot convert null to long'")
+  @Disabled("throws 'RuntimeException: Cannot convert null to long'")
   @Test public void testQuery27() {
     checkQuery(27).runs();
   }
 
-  @Ignore("throws 'RuntimeException: Cannot convert null to long'")
+  @Disabled("throws 'RuntimeException: Cannot convert null to long'")
   @Test public void testQuery58() {
     checkQuery(58).explainContains("PLAN").runs();
   }
 
-  @Ignore("takes too long to optimize")
+  @Disabled("takes too long to optimize")
   @Test public void testQuery72() {
     checkQuery(72).runs();
   }
 
-  @Ignore("work in progress")
+  @Disabled("work in progress")
   @Test public void testQuery72Plan() {
     checkQuery(72)
         .withHook(Hook.PROGRAM, handler(true, 2))
         .planContains("xx");
   }
 
-  @Ignore("throws 'java.lang.AssertionError: type mismatch'")
+  @Disabled("throws 'java.lang.AssertionError: type mismatch'")
   @Test public void testQuery95() {
     checkQuery(95)
         .withHook(Hook.PROGRAM, handler(false, 6))
diff --git a/plus/src/test/java/org/apache/calcite/adapter/tpch/TpchTest.java b/plus/src/test/java/org/apache/calcite/adapter/tpch/TpchTest.java
index 7bc1440..69823e0 100644
--- a/plus/src/test/java/org/apache/calcite/adapter/tpch/TpchTest.java
+++ b/plus/src/test/java/org/apache/calcite/adapter/tpch/TpchTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.calcite.adapter.tpch;
 
-import org.apache.calcite.config.CalciteSystemProperty;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.test.CalciteAssert;
 import org.apache.calcite.util.Bug;
@@ -25,8 +24,9 @@ import org.apache.calcite.util.TestUtil;
 import com.google.common.collect.ImmutableList;
 
 import org.junit.Assume;
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 
 import java.util.List;
 
@@ -37,12 +37,11 @@ import static org.junit.Assert.assertThat;
 /** Unit test for {@link org.apache.calcite.adapter.tpch.TpchSchema}.
  *
  * <p>Because the TPC-H data generator takes time and memory to instantiate,
- * tests that read data (that is, most tests) only run
- * if {@link org.apache.calcite.config.CalciteSystemProperty#TEST_SLOW} is set.</p>
+ * tests only run as part of slow tests.</p>
  */
+@Tag("slow")
 public class TpchTest {
-  public static final boolean ENABLE =
-      CalciteSystemProperty.TEST_SLOW.value() && TestUtil.getJavaMajorVersion() >= 7;
+  public static final boolean ENABLE = TestUtil.getJavaMajorVersion() >= 7;
 
   private static String schema(String name, String scaleFactor) {
     return "     {\n"
@@ -781,7 +780,7 @@ public class TpchTest {
    * <a href="https://issues.apache.org/jira/browse/CALCITE-1543">[CALCITE-1543]
    * Correlated scalar sub-query with multiple aggregates gives
    * AssertionError</a>. */
-  @Ignore("planning succeeds, but gives OutOfMemoryError during execution")
+  @Disabled("planning succeeds, but gives OutOfMemoryError during execution")
   @Test public void testDecorrelateScalarAggregate() {
     final String sql = "select sum(l_extendedprice)\n"
         + "from lineitem, part\n"
@@ -803,7 +802,6 @@ public class TpchTest {
 
   private CalciteAssert.AssertThat with() {
     // Only run on JDK 1.7 or higher. The io.airlift.tpch library requires it.
-    // Only run if slow tests are enabled; the library uses lots of memory.
     return CalciteAssert.model(TPCH_MODEL).enable(ENABLE);
   }
 
@@ -818,12 +816,12 @@ public class TpchTest {
     checkQuery(1);
   }
 
-  @Ignore("Infinite planning")
+  @Disabled("Infinite planning")
   @Test public void testQuery02() {
     checkQuery(2);
   }
 
-  @Ignore("Infinite planning")
+  @Disabled("Infinite planning")
   @Test public void testQuery02Conversion() {
     query(2)
         .convertMatches(relNode -> {
@@ -837,12 +835,12 @@ public class TpchTest {
     checkQuery(3);
   }
 
-  @Ignore("NoSuchMethodException: SqlFunctions.lt(Date, Date)")
+  @Disabled("NoSuchMethodException: SqlFunctions.lt(Date, Date)")
   @Test public void testQuery04() {
     checkQuery(4);
   }
 
-  @Ignore("OutOfMemoryError")
+  @Disabled("OutOfMemoryError")
   @Test public void testQuery05() {
     checkQuery(5);
   }
@@ -851,18 +849,16 @@ public class TpchTest {
     checkQuery(6);
   }
 
-  @Ignore("slow")
   @Test public void testQuery07() {
     Assume.assumeTrue(Bug.CALCITE_2223_FIXED);
     checkQuery(7);
   }
 
-  @Ignore("slow")
   @Test public void testQuery08() {
     checkQuery(8);
   }
 
-  @Ignore("no method found")
+  @Disabled("no method found")
   @Test public void testQuery09() {
     checkQuery(9);
   }
@@ -871,17 +867,17 @@ public class TpchTest {
     checkQuery(10);
   }
 
-  @Ignore("CannotPlanException")
+  @Disabled("CannotPlanException")
   @Test public void testQuery11() {
     checkQuery(11);
   }
 
-  @Ignore("NoSuchMethodException: SqlFunctions.lt(Date, Date)")
+  @Disabled("NoSuchMethodException: SqlFunctions.lt(Date, Date)")
   @Test public void testQuery12() {
     checkQuery(12);
   }
 
-  @Ignore("CannotPlanException")
+  @Disabled("CannotPlanException")
   @Test public void testQuery13() {
     checkQuery(13);
   }
@@ -890,7 +886,7 @@ public class TpchTest {
     checkQuery(14);
   }
 
-  @Ignore("AssertionError")
+  @Disabled("AssertionError")
   @Test public void testQuery15() {
     checkQuery(15);
   }
@@ -899,7 +895,6 @@ public class TpchTest {
     checkQuery(16);
   }
 
-  @Ignore("slow")
   @Test public void testQuery17() {
     checkQuery(17);
   }
@@ -917,12 +912,11 @@ public class TpchTest {
     checkQuery(20);
   }
 
-  @Ignore("slow")
   @Test public void testQuery21() {
     checkQuery(21);
   }
 
-  @Ignore("IllegalArgumentException during decorrelation")
+  @Disabled("IllegalArgumentException during decorrelation")
   @Test public void testQuery22() {
     checkQuery(22);
   }
diff --git a/site/_docs/howto.md b/site/_docs/howto.md
index 6a0668c..99f8fb2 100644
--- a/site/_docs/howto.md
+++ b/site/_docs/howto.md
@@ -112,11 +112,11 @@ environment, as follows.
      `mysql` and `postgresql` might be somewhat faster than hsqldb, but you need
      to populate it (i.e. provision a VM).
 * `-Dcalcite.debug` prints extra debugging information to stdout.
-* `-Dcalcite.test.slow` enables tests that take longer to execute. For
-  example, there are tests that create virtual TPC-H and TPC-DS schemas
-  in-memory and run tests from those benchmarks.
 * `-Dcalcite.test.splunk` enables tests that run against Splunk.
   Splunk must be installed and running.
+* `./gradlew testSlow` runs tests that take longer to execute. For
+  example, there are tests that create virtual TPC-H and TPC-DS schemas
+  in-memory and run tests from those benchmarks.
 
 Note: tests are executed in a forked JVM, so system properties are not passed automatically
 when running tests with Gradle.
@@ -473,6 +473,12 @@ from a contributor, found it satisfactory, and is about to merge it to master.
 Usually the contributor is not a committer (otherwise they would be committing
 it themselves, after you gave approval in a review).
 
+There are certain kinds of continuous integration tests that are not run
+automatically against the PR. These tests can be triggered explicitly by adding
+an appropriate label to the PR. For instance, you can run slow tests by adding
+the `slow-tests-needed` label. It is up to you to decide if these additional
+tests need to run before merging.
+
 If the PR has multiple commits, squash them into a single commit. The
 commit message should follow the conventions outined in
 [contribution guidelines]({{ site.baseurl }}/develop/#contributing).
@@ -570,12 +576,13 @@ Before you start:
   Guava.  These will probably be the same as those described in the
   release notes of the previous release.  Document them in the release
   notes.  To test Guava version _x.y_, specify `-Pguava.version=x.y`
-* Optional extra tests:
+* Optional tests using properties:
   * `-Dcalcite.test.db=mysql`
   * `-Dcalcite.test.db=hsqldb`
-  * `-Dcalcite.test.slow`
   * `-Dcalcite.test.mongodb`
   * `-Dcalcite.test.splunk`
+* Optional tests using tasks:
+  * `./gradlew testSlow`
 * Trigger a
   <a href="https://scan.coverity.com/projects/julianhyde-calcite">Coverity scan</a>
   by merging the latest code into the `julianhyde/coverity_scan` branch,