You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by gu...@apache.org on 2020/02/28 02:40:46 UTC

[lucene-solr] branch branch_8x updated: SOLR-14283 - fix NPE in SolrTestCaseJ4

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

gus pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/branch_8x by this push:
     new 9c9a69c  SOLR-14283 - fix NPE in SolrTestCaseJ4
9c9a69c is described below

commit 9c9a69c643243caee119666c50434b1cf485d0ca
Author: Gus Heck <gu...@apache.org>
AuthorDate: Wed Feb 26 09:15:35 2020 -0500

    SOLR-14283 - fix NPE in SolrTestCaseJ4
---
 .../test/org/apache/solr/SolrTestCaseJ4Test.java   |   9 +
 .../test/org/apache/solr/SourceHomeNullifier.java  |  75 +++++
 .../src/java/org/apache/solr/SolrTestCaseJ4.java   | 318 +++++++++++----------
 3 files changed, 244 insertions(+), 158 deletions(-)

diff --git a/solr/core/src/test/org/apache/solr/SolrTestCaseJ4Test.java b/solr/core/src/test/org/apache/solr/SolrTestCaseJ4Test.java
index fc995e3..1799699 100644
--- a/solr/core/src/test/org/apache/solr/SolrTestCaseJ4Test.java
+++ b/solr/core/src/test/org/apache/solr/SolrTestCaseJ4Test.java
@@ -22,11 +22,20 @@ import org.apache.commons.io.FileUtils;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
+import org.junit.ClassRule;
 import org.junit.Test;
 
 
 public class SolrTestCaseJ4Test extends SolrTestCaseJ4 {
 
+  /**
+   * Randomly test that it is possible to use SolrTestCaseJ4 even if
+   * {@link org.apache.solr.util.ExternalPaths#SOURCE_HOME} is null. This
+   * will be common for usages of Solr Test Framework outside of solr.
+   */
+  @ClassRule
+  public static final SourceHomeNullifier shn = new SourceHomeNullifier();
+
   private static String tmpSolrHome;
 
   @BeforeClass
diff --git a/solr/core/src/test/org/apache/solr/SourceHomeNullifier.java b/solr/core/src/test/org/apache/solr/SourceHomeNullifier.java
new file mode 100644
index 0000000..e3dff56
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/SourceHomeNullifier.java
@@ -0,0 +1,75 @@
+/*
+ * 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.solr;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import com.carrotsearch.randomizedtesting.RandomizedContext;
+import org.apache.solr.common.util.SuppressForbidden;
+import org.apache.solr.util.ExternalPaths;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import static org.junit.Assert.fail;
+
+public class SourceHomeNullifier extends TestWatcher {
+
+  private String oldSourceHome;
+  private Field sourceHomeField;
+
+  @Override
+  protected void starting(Description description) {
+    boolean nullify = RandomizedContext.current().getRandom().nextBoolean();
+    if (nullify) {
+      oldSourceHome = ExternalPaths.SOURCE_HOME;
+      try {
+        sourceHomeField = ExternalPaths.class.getDeclaredField("SOURCE_HOME");
+        setFinalStatic(sourceHomeField, null);
+      } catch (Exception e) {
+        e.printStackTrace();
+        fail(e.getMessage());
+      }
+    }
+    super.starting(description);
+  }
+
+  @SuppressForbidden(reason = "Need to tweak the value of a static final field on a per test basis")
+  static void setFinalStatic(Field field, Object newValue) throws Exception {
+    field.setAccessible(true);
+
+    Field modifiersField = Field.class.getDeclaredField("modifiers");
+    modifiersField.setAccessible(true);
+    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+
+    field.set(null, newValue);
+  }
+
+  @Override
+  protected void finished(Description description) {
+    if (sourceHomeField != null) {
+      try {
+        sourceHomeField.set(null, oldSourceHome);
+      } catch (IllegalAccessException e) {
+        e.printStackTrace();
+        fail(e.getMessage());
+      }
+    }
+    super.finished(description);
+  }
+}
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index ad07cb7..19c3b68 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -191,7 +191,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       "java.lang.reflect.",
       "com.carrotsearch.randomizedtesting.",
   });
-  
+
   public static final String DEFAULT_TEST_COLLECTION_NAME = "collection1";
   public static final String DEFAULT_TEST_CORENAME = DEFAULT_TEST_COLLECTION_NAME;
   protected static final String CORE_PROPERTIES_FILENAME = "core.properties";
@@ -204,9 +204,9 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
   protected static String coreName = DEFAULT_TEST_CORENAME;
 
   public static int DEFAULT_CONNECTION_TIMEOUT = 60000;  // default socket connection timeout in ms
-  
+
   private static String initialRootLogLevel;
-  
+
   protected volatile static ExecutorService testExecutor;
 
   protected void writeCoreProperties(Path coreDirectory, String corename) throws IOException {
@@ -239,7 +239,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     /** Point to JIRA entry. */
     public String bugUrl() default "None";
   }
-  
+
   /**
    * Annotation for test classes that want to disable PointFields.
    * PointFields will otherwise randomly used by some schemas.
@@ -252,13 +252,13 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     /** Point to JIRA entry. */
     public String bugUrl();
   }
-  
+
   // these are meant to be accessed sequentially, but are volatile just to ensure any test
   // thread will read the latest value
   protected static volatile SSLTestConfig sslConfig;
 
   @Rule
-  public TestRule solrTestRules = 
+  public TestRule solrTestRules =
     RuleChain.outerRule(new SystemPropertiesRestoreRule());
 
   @BeforeClass
@@ -266,7 +266,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     initialRootLogLevel = StartupLoggingUtils.getLogLevelString();
     initClassLogLevels();
     resetExceptionIgnores();
-    
+
     testExecutor = new ExecutorUtil.MDCAwareThreadPoolExecutor(0, Integer.MAX_VALUE,
         15L, TimeUnit.SECONDS,
         new SynchronousQueue<>(),
@@ -274,14 +274,16 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
         true);
 
     // set solr.install.dir needed by some test configs outside of the test sandbox (!)
-    System.setProperty("solr.install.dir", ExternalPaths.SOURCE_HOME);
+    if (ExternalPaths.SOURCE_HOME != null) {
+      System.setProperty("solr.install.dir", ExternalPaths.SOURCE_HOME);
+    }
     // not strictly needed by this class at this point in the control lifecycle, but for
     // backcompat create it now in case any third party tests expect initCoreDataDir to be
     // non-null after calling setupTestCases()
     initAndGetDataDir();
 
-    System.setProperty("solr.zkclienttimeout", "90000"); 
-    
+    System.setProperty("solr.zkclienttimeout", "90000");
+
     System.setProperty("solr.httpclient.retries", "1");
     System.setProperty("solr.retries.on.forward", "1");
     System.setProperty("solr.retries.to.followers", "1");
@@ -318,7 +320,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       } catch (Exception e) {
         log.error("Error deleting SolrCore.");
       }
-      
+
       if (null != testExecutor) {
         ExecutorUtil.shutdownAndAwaitTermination(testExecutor);
         testExecutor = null;
@@ -366,7 +368,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       StartupLoggingUtils.changeLogLevel(initialRootLogLevel);
     }
   }
-  
+
   /**
    * a "dead" host, if you try to connect to it, it will likely fail fast
    * please consider using mocks and not real networking to simulate failure
@@ -395,14 +397,14 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       fail("ByteBuddy and Mockito are not available on classpath: " + e.toString());
     }
   }
-  
+
   /**
    * @return null if ok else error message
    */
   public static String clearObjectTrackerAndCheckEmpty(int waitSeconds) {
     return clearObjectTrackerAndCheckEmpty(waitSeconds, false);
   }
-  
+
   /**
    * @return null if ok else error message
    */
@@ -430,12 +432,12 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       } catch (InterruptedException e) { break; }
     }
     while (retries++ < waitSeconds);
-    
-    
+
+
     log.info("------------------------------------------------------- Done waiting for tracked resources to be released");
-    
+
     ObjectReleaseTracker.clear();
-    
+
     return result;
   }
 
@@ -470,7 +472,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     LogLevel.Configurer.restoreLogLevels(savedMethodLogLevels);
     savedMethodLogLevels.clear();
   }
-  
+
   protected static boolean isSSLMode() {
     return sslConfig != null && sslConfig.isSSLMode();
   }
@@ -506,7 +508,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
 
     SSLRandomizer sslRandomizer =
       SSLRandomizer.getSSLRandomizerForClass(RandomizedContext.current().getTargetClass());
-    
+
     if (Constants.MAC_OS_X) {
       // see SOLR-9039
       // If a solution is found to remove this, please make sure to also update
@@ -523,7 +525,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
   protected static JettyConfig buildJettyConfig(String context) {
     return JettyConfig.builder().setContext(context).withSSLConfig(sslConfig.buildServerSSLConfig()).build();
   }
-  
+
   protected static String buildUrl(final int port, final String context) {
     return (isSSLMode() ? "https" : "http") + "://127.0.0.1:" + port + context;
   }
@@ -555,11 +557,11 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     h = new TestHarness(SolrXmlConfig.fromSolrHome(solrHome));
     lrf = h.getRequestFactory("/select", 0, 20, CommonParams.VERSION, "2.2");
   }
-  
-  /** 
+
+  /**
    * Sets system properties to allow generation of random configurations of
-   * solrconfig.xml and schema.xml. 
-   * Sets properties used on  
+   * solrconfig.xml and schema.xml.
+   * Sets properties used on
    * {@link #newIndexWriterConfig(org.apache.lucene.analysis.Analyzer)}
    *  and base schema.xml (Point Fields)
    */
@@ -596,17 +598,17 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
 
   @Override
   public void tearDown() throws Exception {
-    log.info("###Ending " + getTestName());    
+    log.info("###Ending " + getTestName());
     super.tearDown();
   }
 
   /**
-   * Subclasses may call this method to access the "dataDir" that will be used by 
+   * Subclasses may call this method to access the "dataDir" that will be used by
    * {@link #initCore} (either prior to or after the core is created).
    * <p>
    * If the dataDir has not yet been initialized when this method is called, this method will do so.
-   * Calling {@link #deleteCore} will "reset" the value, such that subsequent calls will 
-   * re-initialize a new value.  All directories returned by any calls to this method will 
+   * Calling {@link #deleteCore} will "reset" the value, such that subsequent calls will
+   * re-initialize a new value.  All directories returned by any calls to this method will
    * automatically be cleaned up per {@link #createTempDir}
    * </p>
    * <p>
@@ -627,17 +629,17 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     }
     return dataDir;
   }
-  /** 
-   * Counter for ensuring we don't ask {@link #createTempDir} to try and 
+  /**
+   * Counter for ensuring we don't ask {@link #createTempDir} to try and
    * re-create the same dir prefix over and over.
    * <p>
-   * (createTempDir has it's own counter for uniqueness, but it tries all numbers in a loop 
-   * until it finds one available.  No reason to force that O(N^2) behavior when we know we've 
+   * (createTempDir has it's own counter for uniqueness, but it tries all numbers in a loop
+   * until it finds one available.  No reason to force that O(N^2) behavior when we know we've
    * already created N previous directories with the same prefix.)
    * </p>
    */
   private static final AtomicInteger dataDirCount = new AtomicInteger(0);
-  
+
   /** Call initCore in @BeforeClass to instantiate a solr core in your test class.
    * deleteCore will be called for you via SolrTestCaseJ4 @AfterClass */
   public static void initCore(String config, String schema) throws Exception {
@@ -661,7 +663,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     coreName=pCoreName;
     initCore(config,schema,solrHome);
   }
-  
+
   static long numOpens;
   static long numCloses;
   public static void startTrackingSearchers() {
@@ -673,7 +675,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       numOpens = numCloses = 0;
     }
   }
-  
+
   /** Causes an exception matching the regex pattern to not be logged. */
   public static void ignoreException(String pattern) {
     if (SolrException.ignorePatterns == null) // usually initialized already but in case not...
@@ -746,10 +748,10 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
   }
 
   /**
-   * The directory used as the <code>dataDir</code> for the TestHarness unless 
-   * {@link #hdfsDataDir} is non null.  
+   * The directory used as the <code>dataDir</code> for the TestHarness unless
+   * {@link #hdfsDataDir} is non null.
    * <p>
-   * Will be set to null by {@link #deleteCore} and re-initialized as needed by {@link #createCore}.  
+   * Will be set to null by {@link #deleteCore} and re-initialized as needed by {@link #createCore}.
    * In the event of a test failure, the contents will be left on disk.
    * </p>
    * @see #createTempDir(String)
@@ -758,7 +760,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
    */
   @Deprecated
   protected static volatile File initCoreDataDir;
-  
+
   // hack due to File dataDir
   protected static String hdfsDataDir;
 
@@ -897,7 +899,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       // clears the updatelog sysprop at the end of the test run
       System.clearProperty(UPDATELOG_SYSPROP);
     }
-    
+
     solrConfig = null;
     h = null;
     lrf = null;
@@ -1098,7 +1100,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
           }
         } finally {
           if (failed) {
-            log.error("JSON query validation threw an exception." + 
+            log.error("JSON query validation threw an exception." +
                 "\n expected =" + testJSON +
                 "\n response = " + response +
                 "\n request = " + req.getParamString()
@@ -1111,7 +1113,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       // restore the params
       if (params != null && params != req.getParams()) req.setParams(params);
     }
-  }  
+  }
 
 
   /** Makes sure a query throws a SolrException with the listed response code */
@@ -1156,7 +1158,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       fail( failMessage );
     } catch (SolrException e) {
       assertEquals( code.code, e.code() );
-      assertTrue("Unexpected error message. Expecting \"" + exceptionMessage + 
+      assertTrue("Unexpected error message. Expecting \"" + exceptionMessage +
           "\" but got \"" + e.getMessage() + "\"", e.getMessage()!= null && e.getMessage().contains(exceptionMessage));
     } catch (Exception e2) {
       throw new RuntimeException("Exception during query", e2);
@@ -1350,11 +1352,11 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
   }
 
   /**
-   * Does a low level delete of all docs in the index. 
+   * Does a low level delete of all docs in the index.
    *
    * The behavior of this method is slightly different then doing a normal <code>*:*</code> DBQ because it
-   * takes advantage of internal methods to ensure all index data is wiped, regardless of optimistic 
-   * concurrency version constraints -- making it suitable for tests that create synthetic versions, 
+   * takes advantage of internal methods to ensure all index data is wiped, regardless of optimistic
+   * concurrency version constraints -- making it suitable for tests that create synthetic versions,
    * and/or require a completely pristine index w/o any field metdata.
    *
    * @see #deleteByQueryAndGetVersion
@@ -1617,7 +1619,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
   /////////////////////////////////////////////////////////////////////////////////////
   //////////////////////////// random document / index creation ///////////////////////
   /////////////////////////////////////////////////////////////////////////////////////
-  
+
   public abstract static class Vals {
     public abstract Comparable get();
     public String toJSON(Comparable val) {
@@ -1814,7 +1816,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     public FldType(String fname, IVals numValues, Vals vals) {
       this.fname = fname;
       this.numValues = numValues;
-      this.vals = vals;      
+      this.vals = vals;
     }
 
     public Comparable createValue() {
@@ -1837,7 +1839,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       Fld fld = new Fld();
       fld.ftype = this;
       fld.vals = vals;
-      return fld;          
+      return fld;
     }
 
   }
@@ -1874,7 +1876,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       // duplicate 10% of the docs
       if (random().nextInt(10)==0) {
         updateJ(toJSON(doc), null);
-        model.put(doc.id, doc);        
+        model.put(doc.id, doc);
       }
     }
 
@@ -1954,7 +1956,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
 
     if (comparators.size() == 0) {
       // default sort is by score desc
-      comparators.add(createComparator("score", false, false, false, false));      
+      comparators.add(createComparator("score", false, false, false, false));
     }
 
     return createComparator(comparators);
@@ -2090,7 +2092,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       try {
         return new File(url.toURI());
       } catch (Exception e) {
-        throw new RuntimeException("Resource was found on classpath, but cannot be resolved to a " + 
+        throw new RuntimeException("Resource was found on classpath, but cannot be resolved to a " +
             "normal file (maybe it is part of a JAR file): " + name);
       }
     }
@@ -2100,7 +2102,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     }
     throw new RuntimeException("Cannot find resource in classpath or in file-system (relative to CWD): " + name);
   }
-  
+
   public static String TEST_HOME() {
     return getFile("solr/collection1").getParent();
   }
@@ -2134,7 +2136,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       throw new RuntimeException("XPath is invalid", e2);
     }
   }
-                                                         
+
   /**
    * Fails if the number of documents in the given SolrDocumentList differs
    * from the given number of expected values, or if any of the values in the
@@ -2202,7 +2204,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     }
     File xmlF = new File(SolrTestCaseJ4.TEST_HOME(), fromFile);
     FileUtils.copyFile(xmlF, new File(dstRoot, "solr.xml"));
-    
+
   }
   // Creates a consistent configuration, _including_ solr.xml at dstRoot. Creates collection1/conf and copies
   // the stock files in there.
@@ -2294,7 +2296,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     if (list1.getMaxScore() == null) {
       if (list2.getMaxScore() != null) {
         return false;
-      } 
+      }
     } else if (list2.getMaxScore() == null) {
       return false;
     } else {
@@ -2406,7 +2408,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     return true;
   }
 
-  /** 
+  /**
    * Returns <code>likely</code> most (1/10) of the time, otherwise <code>unlikely</code>
    */
   public static Object skewed(Object likely, Object unlikely) {
@@ -2473,27 +2475,27 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
    * some internal settings.
    */
   public static class CloudSolrClientBuilder extends CloudSolrClient.Builder {
-    
+
     public CloudSolrClientBuilder(List<String> zkHosts, Optional<String> zkChroot) {
       super(zkHosts, zkChroot);
       randomizeCloudSolrClient();
     }
-    
+
     public CloudSolrClientBuilder(ClusterStateProvider stateProvider) {
       this.stateProvider = stateProvider;
       randomizeCloudSolrClient();
     }
-    
+
     public CloudSolrClientBuilder(MiniSolrCloudCluster cluster) {
       if (random().nextBoolean()) {
         this.zkHosts.add(cluster.getZkServer().getZkAddress());
       } else {
         populateSolrUrls(cluster);
       }
-      
+
       randomizeCloudSolrClient();
     }
-    
+
     private void populateSolrUrls(MiniSolrCloudCluster cluster) {
       if (random().nextBoolean()) {
         final List<JettySolrRunner> solrNodes = cluster.getJettySolrRunners();
@@ -2504,7 +2506,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
         this.solrUrls.add(cluster.getRandomJetty(random()).getBaseUrl().toString());
       }
     }
-    
+
     private void randomizeCloudSolrClient() {
       this.directUpdatesToLeadersOnly = random().nextBoolean();
       this.shardLeadersOnly = random().nextBoolean();
@@ -2516,7 +2518,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
    * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.CloudSolrClient.Builder} class directly
-   */ 
+   */
   public static CloudSolrClient getCloudSolrClient(String zkHost) {
     return new CloudSolrClientBuilder(Collections.singletonList(zkHost), Optional.empty()).build();
   }
@@ -2532,20 +2534,20 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
 
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.CloudSolrClient.Builder} class directly
-   */ 
+   */
   public static CloudSolrClient getCloudSolrClient(String zkHost, HttpClient httpClient) {
     return new CloudSolrClientBuilder(Collections.singletonList(zkHost), Optional.empty())
         .withHttpClient(httpClient)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.CloudSolrClient.Builder} class directly
-   */ 
+   */
   public static CloudSolrClient getCloudSolrClient(String zkHost, boolean shardLeadersOnly) {
     if (shardLeadersOnly) {
       return new CloudSolrClientBuilder(Collections.singletonList(zkHost), Optional.empty())
@@ -2563,9 +2565,9 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
 
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.CloudSolrClient.Builder} class directly
-   */ 
+   */
   public static CloudSolrClient getCloudSolrClient(String zkHost, boolean shardLeadersOnly, int socketTimeoutMillis) {
     if (shardLeadersOnly) {
       return new CloudSolrClientBuilder(Collections.singletonList(zkHost), Optional.empty())
@@ -2578,12 +2580,12 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
         .withSocketTimeout(socketTimeoutMillis)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.CloudSolrClient.Builder} class directly
-   */ 
+   */
   public static CloudSolrClient getCloudSolrClient(String zkHost, boolean shardLeadersOnly, int connectionTimeoutMillis, int socketTimeoutMillis) {
     if (shardLeadersOnly) {
       return new CloudSolrClientBuilder(Collections.singletonList(zkHost), Optional.empty())
@@ -2598,14 +2600,14 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
         .withSocketTimeout(socketTimeoutMillis)
         .build();
   }
-  
-  
-  
+
+
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.CloudSolrClient.Builder} class directly
-   */ 
+   */
   public static CloudSolrClient getCloudSolrClient(String zkHost, boolean shardLeadersOnly, HttpClient httpClient) {
     if (shardLeadersOnly) {
       return new CloudSolrClientBuilder(Collections.singletonList(zkHost), Optional.empty())
@@ -2618,12 +2620,12 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
         .sendUpdatesToAllReplicasInShard()
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.CloudSolrClient.Builder} class directly
-   */ 
+   */
   public static CloudSolrClient getCloudSolrClient(String zkHost, boolean shardLeadersOnly, HttpClient httpClient,
       int connectionTimeoutMillis, int socketTimeoutMillis) {
     if (shardLeadersOnly) {
@@ -2641,24 +2643,24 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
         .withSocketTimeout(socketTimeoutMillis)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrClient.Builder} class directly
-   */ 
+   */
   public static ConcurrentUpdateSolrClient getConcurrentUpdateSolrClient(String baseSolrUrl, int queueSize, int threadCount) {
     return new ConcurrentUpdateSolrClient.Builder(baseSolrUrl)
         .withQueueSize(queueSize)
         .withThreadCount(threadCount)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrClient.Builder} class directly
-   */ 
+   */
   public static ConcurrentUpdateSolrClient getConcurrentUpdateSolrClient(String baseSolrUrl, int queueSize, int threadCount, int connectionTimeoutMillis) {
     return new ConcurrentUpdateSolrClient.Builder(baseSolrUrl)
         .withQueueSize(queueSize)
@@ -2666,12 +2668,12 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
         .withConnectionTimeout(connectionTimeoutMillis)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrClient.Builder} class directly
-   */ 
+   */
   public static ConcurrentUpdateSolrClient getConcurrentUpdateSolrClient(String baseSolrUrl, HttpClient httpClient, int queueSize, int threadCount) {
     return new ConcurrentUpdateSolrClient.Builder(baseSolrUrl)
         .withHttpClient(httpClient)
@@ -2679,24 +2681,24 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
         .withThreadCount(threadCount)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.LBHttpSolrClient.Builder} class directly
-   */ 
+   */
   public static LBHttpSolrClient getLBHttpSolrClient(HttpClient client, String... solrUrls) {
     return new LBHttpSolrClient.Builder()
         .withHttpClient(client)
         .withBaseSolrUrls(solrUrls)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.LBHttpSolrClient.Builder} class directly
-   */ 
+   */
   public static LBHttpSolrClient getLBHttpSolrClient(HttpClient client, int connectionTimeoutMillis,
       int socketTimeoutMillis, String... solrUrls) {
     return new LBHttpSolrClient.Builder()
@@ -2706,23 +2708,23 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
         .withSocketTimeout(socketTimeoutMillis)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.LBHttpSolrClient.Builder} class directly
-   */ 
+   */
   public static LBHttpSolrClient getLBHttpSolrClient(String... solrUrls) throws MalformedURLException {
     return new LBHttpSolrClient.Builder()
         .withBaseSolrUrls(solrUrls)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.HttpSolrClient.Builder} class directly
-   */ 
+   */
   public static HttpSolrClient getHttpSolrClient(String url, HttpClient httpClient, ResponseParser responseParser, boolean compression) {
     return new Builder(url)
         .withHttpClient(httpClient)
@@ -2730,35 +2732,35 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
         .allowCompression(compression)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.HttpSolrClient.Builder} class directly
-   */ 
+   */
   public static HttpSolrClient getHttpSolrClient(String url, HttpClient httpClient, ResponseParser responseParser) {
     return new Builder(url)
         .withHttpClient(httpClient)
         .withResponseParser(responseParser)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.HttpSolrClient.Builder} class directly
-   */ 
+   */
   public static HttpSolrClient getHttpSolrClient(String url, HttpClient httpClient) {
     return new Builder(url)
         .withHttpClient(httpClient)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.HttpSolrClient.Builder} class directly
-   */ 
+   */
   public static HttpSolrClient getHttpSolrClient(String url, HttpClient httpClient, int connectionTimeoutMillis) {
     return new Builder(url)
         .withHttpClient(httpClient)
@@ -2768,30 +2770,30 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
 
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.HttpSolrClient.Builder} class directly
-   */ 
+   */
   public static HttpSolrClient getHttpSolrClient(String url) {
     return new Builder(url)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.HttpSolrClient.Builder} class directly
-   */ 
+   */
   public static HttpSolrClient getHttpSolrClient(String url, int connectionTimeoutMillis) {
     return new Builder(url)
         .withConnectionTimeout(connectionTimeoutMillis)
         .build();
   }
-  
+
   /**
    * This method <i>may</i> randomize unspecified aspects of the resulting SolrClient.
-   * Tests that do not wish to have any randomized behavior should use the 
+   * Tests that do not wish to have any randomized behavior should use the
    * {@link org.apache.solr.client.solrj.impl.HttpSolrClient.Builder} class directly
-   */ 
+   */
   public static HttpSolrClient getHttpSolrClient(String url, int connectionTimeoutMillis, int socketTimeoutMillis) {
     return new Builder(url)
         .withConnectionTimeout(connectionTimeoutMillis)
@@ -2799,18 +2801,18 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
         .build();
   }
 
-  /** 
-   * Returns a randomly generated Date in the appropriate Solr external (input) format 
+  /**
+   * Returns a randomly generated Date in the appropriate Solr external (input) format
    * @see #randomSkewedDate
    */
   public static String randomDate() {
     return Instant.ofEpochMilli(random().nextLong()).toString();
   }
 
-  /** 
-   * Returns a Date such that all results from this method always have the same values for 
-   * year+month+day+hour+minute but the seconds are randomized.  This can be helpful for 
-   * indexing documents with random date values that are biased for a narrow window 
+  /**
+   * Returns a Date such that all results from this method always have the same values for
+   * year+month+day+hour+minute but the seconds are randomized.  This can be helpful for
+   * indexing documents with random date values that are biased for a narrow window
    * (one day) to test collisions/overlaps
    *
    * @see #randomDate
@@ -2831,7 +2833,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     }
     return result;
   }
-  
+
   protected static void waitForWarming(SolrCore core) throws InterruptedException {
     RefCounted<SolrIndexSearcher> registeredSearcher = core.getRegisteredSearcher();
     RefCounted<SolrIndexSearcher> newestSearcher = core.getNewestSearcher(false);
@@ -2862,7 +2864,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     }
     return testName;
   }
-  
+
   @BeforeClass
   public static void assertNonBlockingRandomGeneratorAvailable() throws InterruptedException {
     final String EGD = "java.security.egd";
@@ -2870,36 +2872,36 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     final String ALLOWED = "test.solr.allowed.securerandom";
     final String allowedAlg = System.getProperty(ALLOWED);
     final String actualEGD = System.getProperty(EGD);
-    
+
     log.info("SecureRandom sanity checks: {}={} & {}={}", ALLOWED, allowedAlg, EGD, actualEGD);
 
     if (null != allowedAlg) {
       // the user has explicitly requested to bypass our assertions and allow a particular alg
       // the only thing we should do is assert that the algorithm they have whitelisted is actaully used
-      
-      
+
+
       final String actualAlg = (new SecureRandom()).getAlgorithm();
       assertEquals("Algorithm specified using "+ALLOWED+" system property " +
                    "does not match actual algorithm", allowedAlg, actualAlg);
       return;
     }
-    // else: no user override, do the checks we want including 
-    
+    // else: no user override, do the checks we want including
+
     if (null == actualEGD) {
       System.setProperty(EGD, URANDOM);
       log.warn("System property {} was not set by test runner, forcibly set to expected: {}", EGD, URANDOM);
     } else if (! URANDOM.equals(actualEGD) ) {
       log.warn("System property {}={} .. test runner should use expected: {}", EGD, actualEGD, URANDOM);
     }
-    
+
     final String algorithm = (new SecureRandom()).getAlgorithm();
-    
+
     assertFalse("SecureRandom algorithm '" + algorithm + "' is in use by your JVM, " +
                 "which is a potentially blocking algorithm on some environments. " +
                 "Please report the details of this failure (and your JVM vendor/version) to solr-user@lucene.apache.org. " +
                 "You can try to run your tests with -D"+EGD+"="+URANDOM+" or bypass this check using " +
                 "-Dtest.solr.allowed.securerandom="+ algorithm +" as a JVM option when running tests.",
-                // be permissive in our checks and blacklist only algorithms 
+                // be permissive in our checks and blacklist only algorithms
                 // that are known to be blocking under some circumstances
                 algorithm.equals("NativePRNG") || algorithm.equals("NativePRNGBlocking"));
   }
@@ -2911,7 +2913,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
   protected static void systemClearPropertySolrTestsMergePolicyFactory() {
     System.clearProperty(SYSTEM_PROPERTY_SOLR_TESTS_MERGEPOLICYFACTORY);
   }
-  
+
   @Deprecated // For backwards compatibility only. Please do not use in new tests.
   protected static void systemSetPropertySolrDisableShardsWhitelist(String value) {
     System.setProperty(SYSTEM_PROPERTY_SOLR_DISABLE_SHARDS_WHITELIST, value);
@@ -2925,9 +2927,9 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
   protected <T> T pickRandom(T... options) {
     return options[random().nextInt(options.length)];
   }
-  
+
   /**
-   * The name of a sysprop that can be set by users when running tests to force the types of numerics 
+   * The name of a sysprop that can be set by users when running tests to force the types of numerics
    * used for test classes that do not have the {@link SuppressPointFields} annotation:
    * <ul>
    *  <li>If unset, then a random variable will be used to decide the type of numerics.</li>
@@ -2937,23 +2939,23 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
    * @see #NUMERIC_POINTS_SYSPROP
    */
   public static final String USE_NUMERIC_POINTS_SYSPROP = "solr.tests.use.numeric.points";
-  
+
   /**
-   * The name of a sysprop that will either <code>true</code> or <code>false</code> indicating if 
-   * numeric points fields are currently in use, depending on the user specified value of 
-   * {@link #USE_NUMERIC_POINTS_SYSPROP} and/or the {@link SuppressPointFields} annotation and/or 
+   * The name of a sysprop that will either <code>true</code> or <code>false</code> indicating if
+   * numeric points fields are currently in use, depending on the user specified value of
+   * {@link #USE_NUMERIC_POINTS_SYSPROP} and/or the {@link SuppressPointFields} annotation and/or
    * randomization. Tests can use <code>Boolean.getBoolean(NUMERIC_POINTS_SYSPROP)</code>.
    *
    * @see #randomizeNumericTypesProperties
    */
   public static final String NUMERIC_POINTS_SYSPROP = "solr.tests.numeric.points";
-  
+
   /**
-   * The name of a sysprop that will be either <code>true</code> or <code>false</code> indicating if 
-   * docValues should be used on a numeric field.  This property string should be used in the 
-   * <code>docValues</code> attribute of (most) numeric fieldTypes in schemas, and by default will be 
-   * randomized by this class in a <code>@BeforeClass</code>.  Subclasses that need to force specific 
-   * behavior can use <code>System.setProperty(NUMERIC_DOCVALUES_SYSPROP,"true")</code> 
+   * The name of a sysprop that will be either <code>true</code> or <code>false</code> indicating if
+   * docValues should be used on a numeric field.  This property string should be used in the
+   * <code>docValues</code> attribute of (most) numeric fieldTypes in schemas, and by default will be
+   * randomized by this class in a <code>@BeforeClass</code>.  Subclasses that need to force specific
+   * behavior can use <code>System.setProperty(NUMERIC_DOCVALUES_SYSPROP,"true")</code>
    * to override the default randomization.
    *
    * @see #randomizeNumericTypesProperties
@@ -2975,7 +2977,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
   }
 
   /**
-   * Sets various sys props related to user specified or randomized choices regarding the types 
+   * Sets various sys props related to user specified or randomized choices regarding the types
    * of numerics that should be used in tests.
    *
    * @see #NUMERIC_DOCVALUES_SYSPROP
@@ -2988,17 +2990,17 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
 
     final boolean useDV = random().nextBoolean();
     System.setProperty(NUMERIC_DOCVALUES_SYSPROP, ""+useDV);
-    
+
     // consume a consistent amount of random data even if sysprop/annotation is set
     final boolean randUsePoints = 0 != random().nextInt(5);  // 80% likelihood
 
     final String usePointsStr = System.getProperty(USE_NUMERIC_POINTS_SYSPROP);
     final boolean usePoints = (null == usePointsStr) ? randUsePoints : Boolean.parseBoolean(usePointsStr);
-    
+
     if (RandomizedContext.current().getTargetClass().isAnnotationPresent(SolrTestCaseJ4.SuppressPointFields.class)
         || (! usePoints)) {
       log.info("Using TrieFields (NUMERIC_POINTS_SYSPROP=false) w/NUMERIC_DOCVALUES_SYSPROP="+useDV);
-      
+
       org.apache.solr.schema.PointField.TEST_HACK_IGNORE_USELESS_TRIEFIELD_ARGS = false;
       private_RANDOMIZED_NUMERIC_FIELDTYPES.put(Integer.class, "solr.TrieIntField");
       private_RANDOMIZED_NUMERIC_FIELDTYPES.put(Float.class, "solr.TrieFloatField");
@@ -3006,7 +3008,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       private_RANDOMIZED_NUMERIC_FIELDTYPES.put(Double.class, "solr.TrieDoubleField");
       private_RANDOMIZED_NUMERIC_FIELDTYPES.put(Date.class, "solr.TrieDateField");
       private_RANDOMIZED_NUMERIC_FIELDTYPES.put(Enum.class, "solr.EnumField");
-      
+
       System.setProperty(NUMERIC_POINTS_SYSPROP, "false");
     } else {
       log.info("Using PointFields (NUMERIC_POINTS_SYSPROP=true) w/NUMERIC_DOCVALUES_SYSPROP="+useDV);
@@ -3018,7 +3020,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       private_RANDOMIZED_NUMERIC_FIELDTYPES.put(Double.class, "solr.DoublePointField");
       private_RANDOMIZED_NUMERIC_FIELDTYPES.put(Date.class, "solr.DatePointField");
       private_RANDOMIZED_NUMERIC_FIELDTYPES.put(Enum.class, "solr.EnumFieldType");
-      
+
       System.setProperty(NUMERIC_POINTS_SYSPROP, "true");
     }
     for (Map.Entry<Class,String> entry : RANDOMIZED_NUMERIC_FIELDTYPES.entrySet()) {
@@ -3035,7 +3037,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     }
     return new DistributedUpdateProcessor(req, rsp, next);
   }
-  
+
   /**
    * Cleans up the randomized sysproperties and variables set by {@link #randomizeNumericTypesProperties}
    *
@@ -3073,7 +3075,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
   }
 
   private static final Map<Class,String> private_RANDOMIZED_NUMERIC_FIELDTYPES = new HashMap<>();
-  
+
   /**
    * A Map of "primitive" java "numeric" types and the string name of the <code>class</code> used in the
    * corresponding schema fieldType declaration.