You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by md...@apache.org on 2022/03/01 23:00:28 UTC

[solr] branch main updated: SOLR-11749 Script testing for bin/solr (#619)

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

mdrob pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 48e11d7  SOLR-11749 Script testing for bin/solr  (#619)
48e11d7 is described below

commit 48e11d79a690c705ce3aa2e567534c7ac3519115
Author: Mike Drob <md...@apache.org>
AuthorDate: Tue Mar 1 16:59:37 2022 -0600

    SOLR-11749 Script testing for bin/solr  (#619)
    
    * SOLR-15993 Convert tests from old framework to BATS, check for unbound variables in bin/solr
    
    * SOLR-15994 move bats tests to packaging directory
    
    * SOLR-12228 Add Gradle plugin for bats
---
 build.gradle                                       |   2 +
 gradle/node.gradle                                 |  41 ++++
 solr/CHANGES.txt                                   |   2 +
 solr/bin-test/README.md                            |  53 -----
 solr/bin-test/test                                 | 191 ----------------
 solr/bin-test/test_auth.sh                         |  40 ----
 solr/bin-test/test_create_collection.sh            | 133 -----------
 solr/bin-test/test_delete_collection.sh            |  70 ------
 solr/bin-test/test_help.sh                         | 134 -----------
 solr/bin-test/utils/assert.sh                      | 127 -----------
 solr/bin/solr                                      | 248 +++++++++------------
 solr/docker/tests/cases/initdb/test.sh             |   2 +-
 solr/packaging/build.gradle                        |  61 +++++
 solr/packaging/test/README.md                      |  61 +++++
 solr/packaging/test/bats_helper.bash               |  65 ++++++
 .../test/test_auth.bats}                           |  30 ++-
 solr/packaging/test/test_create_collection.bats    |  88 ++++++++
 solr/packaging/test/test_delete_collection.bats    |  68 ++++++
 solr/packaging/test/test_help.bats                 |  94 ++++++++
 .../test/test_start_solr.bats}                     |  26 ++-
 solr/solr-ref-guide/build.gradle                   |  26 ---
 21 files changed, 619 insertions(+), 943 deletions(-)

diff --git a/build.gradle b/build.gradle
index fde516d..a44d20e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -27,6 +27,7 @@ plugins {
   id "de.undercouch.download" version "4.0.2" apply false
   id "net.ltgt.errorprone" version "2.0.2" apply false
   id 'com.diffplug.spotless' version "6.3.0" apply false
+  id 'com.github.node-gradle.node' version '3.1.1' apply false
 }
 
 apply from: file('gradle/globals.gradle')
@@ -204,4 +205,5 @@ apply from: file('gradle/solr/solr-forbidden-apis.gradle')
 
 apply from: file('gradle/ant-compat/solr.post-jar.gradle')
 
+apply from: file('gradle/node.gradle')
 
diff --git a/gradle/node.gradle b/gradle/node.gradle
new file mode 100644
index 0000000..bef2415
--- /dev/null
+++ b/gradle/node.gradle
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+configure([project(":solr:packaging"), project(":solr:solr-ref-guide")]) {
+    apply plugin: "com.github.node-gradle.node"
+
+    ext {
+        rootNodeDir = "$rootDir/.gradle/node"
+        nodeProjectDir = file("$rootNodeDir/$project.name")
+    }
+
+    node {
+        download = true
+        version = "16.13.2" // LTS
+
+        // The directory where Node.js is unpacked (when download is true)
+        workDir = file("${project.ext.rootNodeDir}/nodejs")
+
+        // The directory where npm is installed (when a specific version is defined)
+        npmWorkDir = file("${project.ext.rootNodeDir}/npm")
+
+        // The Node.js project directory location
+        // This is where the package.json file and node_modules directory are located
+        // By default it is at the root of the current project
+        nodeProjectDir = project.ext.nodeProjectDir
+    }
+}
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 5a78e23..bafd4b4 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -342,6 +342,8 @@ Build
 
 * SOLR-15992: Globally forbid and exclude known bad dependencies (Kevin Risden)
 
+* SOLR-12228: Move bin-test scripts to packaging project and create gradle task for running them. (Mike Drob)
+
 Other Changes
 ----------------------
 * SOLR-14656: Autoscaling framework removed (Ishan Chattopadhyaya, noble, Ilan Ginzburg)
diff --git a/solr/bin-test/README.md b/solr/bin-test/README.md
deleted file mode 100644
index 2a429c4..0000000
--- a/solr/bin-test/README.md
+++ /dev/null
@@ -1,53 +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.
- -->
-
-# bin/solr Tests
-
-This directory contains tests for the `bin/solr` command-line scripts.  For
-instructions on running these tests, run `bin-test/test -h`.
-
-## Test Harness/Infrastructure
-
-Where possible, these tests model themselves after the pattern well-established
-by JUnit.
-  - JUnit's `@Test` is emulated using the function name prefix: `solr_test_`
-    Any bash functions starting with that prefix are identified as tests.
-  - JUnit's `@Before` and `@After` are imitated using the function names
-    `solr_unit_test_before`, and `solr_unit_test_after`.  If a suite contains
-    these functions, they will be run before and after each test.
-  - JUnit's `@BeforeClass` and `@AfterClass` are imitated using the function
-    names: `solr_suite_before`, and `solr_suite_after`.  If a suite contains
-    these functions, they will be run at the very beginning and end of suite
-    execution.
-  - Test success/failure is judged by the test's return value. 0 indicates
-    success; non-zero indicates failure.  Unlike in JUnit/Java which has
-    exceptions, bash assertions have no way to suspend test execution on
-    failure.  Because of this, assertions are often followed by ` || return 1`,
-    which ensures the test exists immediately if the assertion fails.  Existing
-    tests provided examples of this.
-
-## Test Helpers
-
-A variety of assertions and general utilities are available for use in
-`bin-test/utils/`.
-
-## Limitations
-
-1. Currently this test suite is only available for \*nix environments
-2. Tests written in bash are both slow, and harder to maintain than traditional
-   JUnit tests.  If a test _can_ be written as a JUnit test, it should be.  This
-   suite should only be used to test things that cannot be tested by JUnit.
diff --git a/solr/bin-test/test b/solr/bin-test/test
deleted file mode 100755
index f67c522..0000000
--- a/solr/bin-test/test
+++ /dev/null
@@ -1,191 +0,0 @@
-#!/bin/bash
-# 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.
-
-
-function ensure_cwd_is_solr() {
-  local cwd=`pwd`
-  if [[ "solr" != `basename $cwd` ]]
-  then
-    echo "ERROR: Please run this script from the 'solr' directory."
-    exit 1
-  fi
-}
-
-function run_suite_before_if_present() {
-  if declare -f solr_suite_before > /dev/null ; then
-    solr_suite_before
-  fi
-}
-
-function run_suite_after_if_present() {
-  if declare -f solr_suite_after > /dev/null ; then
-    solr_suite_after
-  fi
-}
-
-function run_test_before_if_present() {
-  if declare -f solr_unit_test_before > /dev/null ; then
-    solr_unit_test_before
-  fi
-}
-
-function run_test_after_if_present() {
-  if declare -f solr_unit_test_after > /dev/null ; then
-    solr_unit_test_after
-  fi
-}
-
-function run_test_suite() {
-  local test_file=$1
-  local test_name_filter="${2:-}"
-
-  echo "Executing $test_file"
-  source $test_file
-  test_names="$(declare -F | awk '{print $3}' | grep 'solr_test')"
-
-  run_suite_before_if_present
-  for test_name in $test_names
-  do
-    if [[ -z "$test_name_filter" || "$test_name_filter" == "$test_name" ]]; then
-      run_single_test $test_name
-    fi
-    unset -f $test_name
-  done
-  run_suite_after_if_present
-
-  unset solr_suite_before
-  unset solr_suite_after
-  unset solr_unit_test_before
-  unset solr_unit_test_after
-}
-
-function run_single_test() {
-  local test_name=$1
-
-  echo -n "  $test_name "
-  run_test_before_if_present
-  let NUM_TESTS+=1
-  output=$($test_name)
-  if [[ $? -ne 0 ]]; then
-    let NUM_FAILURES+=1
-    echo "FAILED"
-    echo "---------------------------------------------------"
-    echo "$output"
-    echo "---------------------------------------------------"
-  else
-    let NUM_SUCCESSES+=1
-    echo "SUCCEEDED"
-  fi
-  run_test_after_if_present
-}
-
-function ensure_param_arg_present() {
-  if [[ $# -lt 2 || "$2" == -* ]]; then
-    echo "Option '$1' requires a single argument, but none provided."
-    exit 1
-  fi
-}
-
-function print_help() {
-  echo "Usage: bin-test/test [-h] [-s SUITE_NAME] [-t SUITE_NAME#TEST_NAME]"
-  echo ""
-  echo "  Run tests for the 'bin/solr' Solr startup/admin scripts.  By default all tests are run."
-  echo "  Tests suites or single tests can be selected by use of the options below:"
-  echo ""
-  echo "  -s|--run-single-suite    Runs all tests living in the specified file.  Filename argument"
-  echo "                           should include the full file extension, but no path prefix."
-  echo "                           (e.g. test_help.sh works, bin-test/test_help.sh and test_help"
-  echo "                           do not)"
-  echo ""
-  echo "  -t|--run-single-test     Runs the specified test from the specified test suite file."
-  echo "                           Takes an argument in the form 'SUITE_NAME#TEST_NAME', where"
-  echo "                           SUITE_NAME is the filename of a test suite (see -s above), and"
-  echo "                           TEST_NAME matches the name of a bash test function present in"
-  echo "                           that file"
-  echo ""
-  echo "  -h|--help                You're soaking in it."
-  echo ""
-  exit 0
-}
-
-function run_all_tests() {
-  test_files="$(find bin-test -name "test_*.sh")"
-
-  for test_file in $test_files
-  do
-    run_test_suite $test_file
-  done
-}
-
-
-## MAIN ##
-##########
-ensure_cwd_is_solr
-
-# Can be 'all', 'help', 'single-suite', or 'single-test'
-MODE="all"
-SUITE_NAME=""
-TEST_NAME=""
-NUM_TESTS=0
-NUM_SUCCESSES=0
-NUM_FAILURES=0
-
-while [[ $# -gt 0 ]]
-do
-  case $1 in
-    -h|--help)
-      MODE="help"
-      shift 1
-    ;;
-    -s|--run-single-suite)
-      ensure_param_arg_present $@
-      MODE="single-suite"
-      SUITE_NAME="bin-test/$2"
-      shift 2
-    ;;
-    -t|--run-single-test)
-      ensure_param_arg_present $@
-      MODE="single-test"
-      SUITE_NAME="bin-test/$(echo "$2" | cut -d "#" -f 1 | tr -d " ")"
-      TEST_NAME=$(echo "$2" | cut -d "#" -f 2 | tr -d " ")
-      shift 2
-    ;;
-    *)
-      echo "WARNING: Unexpected argument [$1] detected, ignoring."
-      shift 1
-    ;;
-  esac
-done
-
-case $MODE in
-  "help")
-    print_help
-    MAIN_RESULT=0
-  ;;
-  "single-suite")
-    run_test_suite $SUITE_NAME
-    MAIN_RESULT=$?
-  ;;
-  "single-test")
-    run_test_suite $SUITE_NAME $TEST_NAME
-    MAIN_RESULT=$?
-  ;;
-  "all")
-    run_all_tests
-    MAIN_RESULT=$?
-esac
-echo "Ran $NUM_TESTS tests, with $NUM_SUCCESSES passing, and $NUM_FAILURES failures"
-exit $MAIN_RESULT
diff --git a/solr/bin-test/test_auth.sh b/solr/bin-test/test_auth.sh
deleted file mode 100644
index b31a996..0000000
--- a/solr/bin-test/test_auth.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/bin/bash
-# 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.
-
-source bin-test/utils/assert.sh
-
-function solr_unit_test_before() {
-  bin/solr auth disable > /dev/null 2>&1
-}
-
-function solr_test_auth_rejects_blockUnknown_option_with_invalid_boolean() {
-  local auth_cmd="bin/solr auth enable -type basicAuth -credentials anyUser:anyPass -blockUnknown ture"
-  local expected_output="Argument [blockUnknown] must be either true or false, but was [ture]"
-  local actual_output; actual_output=$($auth_cmd)
-
-  assert_cmd_failed "$auth_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-}
-
-function solr_test_auth_rejects_updateIncludeFileOnly_option_with_invalid_boolean() {
-  local auth_cmd="bin/solr auth enable -type basicAuth -credentials anyUser:anyPass -updateIncludeFileOnly ture"
-  local expected_output="Argument [updateIncludeFileOnly] must be either true or false, but was [ture]"
-  local actual_output; actual_output=$($auth_cmd)
-
-  assert_cmd_failed "$auth_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-}
-
diff --git a/solr/bin-test/test_create_collection.sh b/solr/bin-test/test_create_collection.sh
deleted file mode 100644
index 011c4ab..0000000
--- a/solr/bin-test/test_create_collection.sh
+++ /dev/null
@@ -1,133 +0,0 @@
-#!/bin/bash
-# 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.
-
-source bin-test/utils/assert.sh
-source bin-test/utils/cleanup.sh
-
-
-# All tests should start with solr_test
-
-function solr_suite_before() {
-  bin/solr stop -all > /dev/null 2>&1
-  bin/solr start -c > /dev/null 2>&1
-
-
-  local source_configset_dir="server/solr/configsets/sample_techproducts_configs"
-  TMP_CONFIGSET_DIR="/tmp/test_config"
-  rm -rf $TMP_CONFIGSET_DIR; cp -r $source_configset_dir $TMP_CONFIGSET_DIR
-}
-
-function solr_suite_after() {
-  bin/solr stop -all > /dev/null 2>&1
-  rm -rf $TMP_CONFIGSET_DIR
-}
-
-function solr_unit_test_before() {
-  delete_all_collections > /dev/null 2>&1
-}
-
-function solr_unit_test_after() {
-  delete_all_collections > /dev/null 2>&1
-}
-
-
-function solr_test_can_create_collection() {
-  local create_cmd="bin/solr create_collection -c COLL_NAME"
-  local expected_output="Created collection 'COLL_NAME'"
-  local actual_output; actual_output=$($create_cmd)
-
-  assert_cmd_succeeded "$create_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-}
-
-function solr_test_rejects_d_option_with_invalid_config_dir() {
-  local create_cmd="bin/solr create_collection -c COLL_NAME -d /asdf"
-  local expected_output="Specified configuration directory /asdf not found!"
-  local actual_output; actual_output=$($create_cmd)
-
-  assert_cmd_failed "$create_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-}
-
-function solr_test_accepts_d_option_with_explicit_builtin_config() {
-  local create_cmd="bin/solr create_collection -c COLL_NAME -d sample_techproducts_configs"
-  local expected_output="Created collection 'COLL_NAME'"
-  local actual_output; actual_output=$($create_cmd)
-
-  assert_cmd_succeeded "$create_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-}
-
-function solr_test_accepts_d_option_with_explicit_path_to_config() {
-  local create_cmd="bin/solr create_collection -c COLL_NAME -d $TMP_CONFIGSET_DIR"
-  local expected_output="Created collection 'COLL_NAME'"
-  local actual_output; actual_output=$($create_cmd)
-
-  assert_cmd_succeeded "$create_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-}
-
-function solr_test_accepts_n_option_as_config_name() {
-  local create_cmd="bin/solr create_collection -c COLL_NAME -n other_conf_name"
-  local expected_name_output="Created collection 'COLL_NAME'"
-  local expected_config_name_output="config-set 'other_conf_name'"
-  local actual_output; actual_output=$($create_cmd)
-
-  # Expect to fail, change to success
-  assert_cmd_succeeded "$create_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_name_output" || return 1
-  assert_output_contains "$actual_output" "$expected_config_name_output" || return 1
-}
-
-function solr_test_allows_config_reuse_when_n_option_specifies_same_config() {
-  local create_cmd1="bin/solr create_collection -c COLL_NAME_1 -n shared_config"
-  local expected_coll_name_output1="Created collection 'COLL_NAME_1'"
-  local create_cmd2="bin/solr create_collection -c COLL_NAME_2 -n shared_config"
-  local expected_coll_name_output2="Created collection 'COLL_NAME_2'"
-  local expected_config_name_output="config-set 'shared_config'"
-
-  local actual_output1; actual_output1=$($create_cmd1)
-  assert_cmd_succeeded "$create_cmd1" || return 1
-  assert_output_contains "$actual_output1" "$expected_coll_name_output1" || return 1
-  assert_output_contains "$actual_output1" "$expected_config_name_output" || return 1
-
-  local actual_output2; actual_output2=$($create_cmd2)
-  assert_cmd_succeeded "$create_cmd2" || return 1
-  assert_output_contains "$actual_output2" "$expected_coll_name_output2" || return 1
-  assert_output_contains "$actual_output2" "$expected_config_name_output" || return 1
-}
-
-function solr_test_create_multisharded_collections_when_s_provided() {
-  local create_cmd="bin/solr create_collection -c COLL_NAME -s 2"
-  local expected_coll_name_output="Created collection 'COLL_NAME'"
-  local expected_shards_output="2 shard(s)"
-
-  local actual_output; actual_output=$($create_cmd)
-  assert_cmd_succeeded "$create_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_coll_name_output" || return 1
-  assert_output_contains "$actual_output" "$expected_shards_output" || return 1
-}
-
-function solr_test_creates_replicated_collections_when_r_provided() {
-  local create_cmd="bin/solr create_collection -c COLL_NAME -rf 2"
-  local expected_coll_name_output="Created collection 'COLL_NAME'"
-  local expected_rf_output="2 replica(s)"
-
-  local actual_output; actual_output=$($create_cmd)
-  assert_cmd_succeeded "$create_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_coll_name_output" || return 1
-  assert_output_contains "$actual_output" "$expected_rf_output" || return 1
-}
diff --git a/solr/bin-test/test_delete_collection.sh b/solr/bin-test/test_delete_collection.sh
deleted file mode 100644
index 00d3d72..0000000
--- a/solr/bin-test/test_delete_collection.sh
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/bin/bash
-# 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.
-
-source bin-test/utils/assert.sh
-source bin-test/utils/cleanup.sh
-
-
-# All tests should start with solr_test
-
-function solr_suite_before() {
-  bin/solr stop -all > /dev/null 2>&1
-  bin/solr start -c > /dev/null 2>&1
-}
-
-function solr_suite_after() {
-  bin/solr stop -all > /dev/null 2>&1
-}
-
-function solr_unit_test_before() {
-  delete_all_collections
-}
-
-function solr_unit_test_after() {
-  delete_all_collections
-}
-
-function solr_test_can_delete_collection() {
-  bin/solr create_collection -c COLL_NAME
-  assert_collection_exists "COLL_NAME" || return 1
-
-  bin/solr delete -c "COLL_NAME"
-  assert_collection_doesnt_exist "COLL_NAME" || return 1
-}
-
-function solr_test_deletes_accompanying_zk_config_by_default() {
-  bin/solr create_collection -c "COLL_NAME"
-  assert_config_exists "COLL_NAME" || return 1
-
-  bin/solr delete -c "COLL_NAME"
-  assert_config_doesnt_exist "COLL_NAME" || return 1
-}
-
-function solr_test_deletes_accompanying_zk_config_with_nondefault_name() {
-  bin/solr create_collection -c "COLL_NAME" -n "NONDEFAULT_CONFIG_NAME"
-  assert_config_exists "NONDEFAULT_CONFIG_NAME" || return 1
-
-  bin/solr delete -c "COLL_NAME"
-  assert_config_doesnt_exist "NONDEFAULT_CONFIG_NAME"
-}
-
-function solr_test_deleteConfig_option_can_opt_to_leave_config_in_zk() {
-  bin/solr create_collection -c "COLL_NAME"
-  assert_config_exists "COLL_NAME"
-
-  bin/solr delete -c "COLL_NAME" -deleteConfig false
-  assert_config_exists "COLL_NAME"
-}
diff --git a/solr/bin-test/test_help.sh b/solr/bin-test/test_help.sh
deleted file mode 100644
index e9c8c8a..0000000
--- a/solr/bin-test/test_help.sh
+++ /dev/null
@@ -1,134 +0,0 @@
-#!/bin/bash
-# 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.
-
-source bin-test/utils/assert.sh
-
-function solr_test_start_help_flag_prints_help() {
-  local help_cmd="bin/solr start -help"
-  local expected_output="Usage: solr start"
-  local actual_output; actual_output=$($help_cmd)
-
-  assert_cmd_succeeded "$help_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-  assert_output_not_contains "$actual_output" "ERROR" || return 1
-}
-
-function solr_test_stop_help_flag_prints_help() {
-  local help_cmd="bin/solr stop -help"
-  local expected_output="Usage: solr stop"
-  local actual_output; actual_output=$($help_cmd)
-
-  assert_cmd_succeeded "$help_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-  assert_output_not_contains "$actual_output" "ERROR" || return 1
-}
-
-function solr_test_restart_help_flag_prints_help() {
-  local help_cmd="bin/solr restart -help"
-  local expected_output="Usage: solr restart"
-  local actual_output; actual_output=$($help_cmd)
-
-  assert_cmd_succeeded "$help_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-  assert_output_not_contains "$actual_output" "ERROR" || return 1
-}
-
-function solr_test_status_help_flag_prints_help() {
-  #TODO Currently the status flag doesn't return nice help text!
-  return 0
-}
-
-function solr_test_healthcheck_help_flag_prints_help() {
-  local help_cmd="bin/solr healthcheck -help"
-  local expected_output="Usage: solr healthcheck"
-  local actual_output; actual_output=$($help_cmd)
-
-  assert_cmd_succeeded "$help_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-  assert_output_not_contains "$actual_output" "ERROR" || return 1
-}
-
-function solr_test_create_help_flag_prints_help() {
-  local help_cmd="bin/solr create -help"
-  local expected_output="Usage: solr create"
-  local actual_output; actual_output=$($help_cmd)
-
-  assert_cmd_succeeded "$help_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-  assert_output_not_contains "$actual_output" "ERROR" || return 1
-}
-
-function solr_test_create_core_help_flag_prints_help() {
-  local help_cmd="bin/solr create_core -help"
-  local expected_output="Usage: solr create_core"
-  local actual_output; actual_output=$($help_cmd)
-
-  assert_cmd_succeeded "$help_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-  assert_output_not_contains "$actual_output" "ERROR" || return 1
-}
-
-function solr_test_create_collection_help_flag_prints_help() {
-  local help_cmd="bin/solr create_collection -help"
-  local expected_output="Usage: solr create_collection"
-  local actual_output; actual_output=$($help_cmd)
-
-  assert_cmd_succeeded "$help_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-  assert_output_not_contains "$actual_output" "ERROR" || return 1
-}
-
-function solr_test_delete_help_flag_prints_help() {
-  local help_cmd="bin/solr delete -help"
-  local expected_output="Usage: solr delete"
-  local actual_output; actual_output=$($help_cmd)
-
-  assert_cmd_succeeded "$help_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-  assert_output_not_contains "$actual_output" "ERROR" || return 1
-}
-
-function solr_test_version_help_flag_prints_help() {
-  #TODO Currently the version -help flag doesn't return nice help text!
-  return 0
-}
-
-function solr_test_zk_help_flag_prints_help() {
-  local help_cmd="bin/solr zk -help"
-  local expected_output="Usage: solr zk"
-  local actual_output; actual_output=$($help_cmd)
-
-  assert_cmd_succeeded "$help_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-  assert_output_not_contains "$actual_output" "ERROR" || return 1
-}
-
-function solr_test_auth_help_flag_prints_help() {
-  local help_cmd="bin/solr auth -help"
-  local expected_output="Usage: solr auth"
-  local actual_output; actual_output=$($help_cmd)
-
-  assert_cmd_succeeded "$help_cmd" || return 1
-  assert_output_contains "$actual_output" "$expected_output" || return 1
-  assert_output_not_contains "$actual_output" "ERROR" || return 1
-}
-
-function solr_test_assert_help_flag_prints_help() {
-  #TODO Currently the assert -help flag doesn't return nice help text!
-  # It returns autogenerated SolrCLI help, which is similar but not _quite_
-  # the same thing.
-  return 0
-}
diff --git a/solr/bin-test/utils/assert.sh b/solr/bin-test/utils/assert.sh
deleted file mode 100644
index 3f83b43..0000000
--- a/solr/bin-test/utils/assert.sh
+++ /dev/null
@@ -1,127 +0,0 @@
-#!/bin/bash
-# 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.
-
-ASSERT_SUCCESS=0
-ASSERT_FAILURE=1
-
-TEST_SUCCESS=0
-TEST_FAILURE=1
-
-function assert_cmd_succeeded() {
-  retval=$?
-  
-  if [[ $retval -ne 0 ]]; then
-    echo "Expected command $1 to succeed, but exited with $retval"
-    return $ASSERT_FAILURE
-  fi
-
-  return $ASSERT_SUCCESS
-}
-
-function assert_cmd_failed() {
-  retval=$?
-  
-  if [[ $retval -eq 0 ]]; then
-    echo "Expected command $1 to fail, but exited with $retval"
-    return $ASSERT_FAILURE
-  fi
-
-  return $ASSERT_SUCCESS
-}
-
-function assert_output_contains() {
-  local actual_output="$1"
-  local needle="$2"
-
-  if [[ "$actual_output" == *"$needle"* ]]; then
-    return $ASSERT_SUCCESS
-  fi
-
-  echo "Expected to find "$needle" in output [$actual_output]"
-  return $ASSERT_FAILURE
-}
-
-function assert_output_not_contains() {
-  local actual_output="$1"
-  local needle="$2"
-
-  if echo "$actual_output" | grep -q "$needle"; then
-    echo "Didn't expect to find "$needle" in output [$actual_output]"
-    return $ASSERT_FAILURE
-  fi
-
-  return $ASSERT_SUCCESS
-}
-
-function assert_collection_exists() {
-  local coll_name=$1
-  local coll_list=$(bin/solr zk ls /collections -z localhost:9983)
-
-  for coll in $coll_list;
-  do
-    if [[ $(echo $coll | tr -d " ") == $coll_name ]]; then
-      return $ASSERT_SUCCESS
-    fi
-  done
-
-  echo "Expected to find collection named [$coll_name], but could only find: $coll_list"
-  return $ASSERT_FAILURE
-}
-
-function assert_collection_doesnt_exist() {
-  local coll_name=$1
-  local coll_list=$(bin/solr zk ls /collections -z localhost:9983)
-  for coll in $coll_list;
-  do
-    echo "Comparing $coll to $coll_name"
-    if [[ $(echo $coll | tr -d " ") == "$coll_name" ]]; then
-      echo "Expected not to find collection [$coll_name], but it exists"
-      return $ASSERT_FAILURE
-    fi
-  done
-
-  return $ASSERT_SUCCESS
-}
-
-function assert_config_exists() {
-  local config_name=$1
-  local config_list=$(bin/solr zk ls /configs -z localhost:9983)
-
-  for config in $config_list;
-  do
-    if [[ $(echo $config | tr -d " ") == $config_name ]]; then
-      return $ASSERT_SUCCESS
-    fi
-  done
-
-  echo "Expected to find config named [$config_name], but could only find: $config_list"
-  return $ASSERT_FAILURE
-}
-
-function assert_config_doesnt_exist() {
-  local config_name=$1
-  local config_list=$(bin/solr zk ls /configs -z localhost:9983)
-
-  for config in $config_list;
-  do
-    if [[ $(echo $config | tr -d " ") == $config_name ]]; then
-      echo "Expected not to find config [$config_name], but it exists"
-      return $ASSERT_FAILURE
-    fi
-  done
-
-  return $ASSERT_SUCCESS
-}
diff --git a/solr/bin/solr b/solr/bin/solr
index cfc6f30..817e5c0 100755
--- a/solr/bin/solr
+++ b/solr/bin/solr
@@ -51,7 +51,7 @@ verbose=false
 THIS_OS=`uname -s`
 
 # What version of Java is required to run this version of Solr.
-JAVA_VER_REQ="8"
+JAVA_VER_REQ=11
 
 stop_all=false
 
@@ -62,6 +62,9 @@ if [ "${THIS_OS:0:6}" == "CYGWIN" ]; then
   exit 1
 fi
 
+# This helps with debugging when running bats tests but not the whole script is compliant yet
+# set -u
+
 # Resolve symlinks to this script
 while [ -h "$SOLR_SCRIPT" ] ; do
   ls=`ls -ld "$SOLR_SCRIPT"`
@@ -80,7 +83,7 @@ SOLR_TIP=`cd "$SOLR_TIP"; pwd`
 DEFAULT_SERVER_DIR="$SOLR_TIP/server"
 
 # If an include wasn't specified in the environment, then search for one...
-if [ -z "$SOLR_INCLUDE" ]; then
+if [[ -z "${SOLR_INCLUDE:-}" ]]; then
   # Locations (in order) to use when searching for an include file.
   for include in "`dirname "$0"`/solr.in.sh" \
                "$HOME/.solr.in.sh" \
@@ -99,14 +102,13 @@ elif [ -r "$SOLR_INCLUDE" ]; then
   . "$SOLR_INCLUDE"
 fi
 
-if [ -z "$SOLR_PID_DIR" ]; then
-  SOLR_PID_DIR="$SOLR_TIP/bin"
-fi
+# if pid dir is unset, deafult to $solr_tip/bin
+: ${SOLR_PID_DIR:=$SOLR_TIP/bin}
 
-if [ -n "$SOLR_JAVA_HOME" ]; then
+if [ -n "${SOLR_JAVA_HOME:-}" ]; then
   JAVA="$SOLR_JAVA_HOME/bin/java"
   JSTACK="$SOLR_JAVA_HOME/bin/jstack"
-elif [ -n "$JAVA_HOME" ]; then
+elif [ -v JAVA_HOME ]; then
   for java in "$JAVA_HOME"/bin/amd64 "$JAVA_HOME"/bin; do
     if [ -x "$java/java" ]; then
       JAVA="$java/java"
@@ -132,12 +134,9 @@ else
   JSTACK=jstack
 fi
 
-if [ -z "$SOLR_STOP_WAIT" ]; then
-  SOLR_STOP_WAIT=180
-fi
-if [ -z "$SOLR_START_WAIT" ]; then
-  SOLR_START_WAIT=$SOLR_STOP_WAIT # defaulting to $SOLR_STOP_WAIT for backwards compatibility reasons
-fi
+: ${SOLR_STOP_WAIT:=180}
+: ${SOLR_START_WAIT:=$SOLR_STOP_WAIT} # defaulting to $SOLR_STOP_WAIT for backwards compatibility reasons
+
 # test that Java exists, is executable and correct version
 JAVA_VER=$("$JAVA" -version 2>&1)
 if [[ $? -ne 0 ]] ; then
@@ -180,12 +179,12 @@ SOLR_URL_SCHEME=http
 SOLR_JETTY_CONFIG=()
 SOLR_SSL_OPTS=""
 
-if [ -n "$SOLR_HADOOP_CREDENTIAL_PROVIDER_PATH" ]; then
+if [ -n "${SOLR_HADOOP_CREDENTIAL_PROVIDER_PATH:-}" ]; then
   SOLR_SSL_OPTS+=" -Dhadoop.security.credential.provider.path=$SOLR_HADOOP_CREDENTIAL_PROVIDER_PATH"
 fi
 
-if [ -z "$SOLR_SSL_ENABLED" ]; then
-  if [ -n "$SOLR_SSL_KEY_STORE" ]; then
+if [ -z "${SOLR_SSL_ENABLED:-}" ]; then
+  if [ -n "${SOLR_SSL_KEY_STORE:-}" ]; then
     SOLR_SSL_ENABLED="true" # implicitly from earlier behaviour
   else
     SOLR_SSL_ENABLED="false"
@@ -280,20 +279,20 @@ if [ "${SOLR_GZIP_ENABLED:-true}" == "true" ]; then
 fi
 
 # Authentication options
-if [ -z "$SOLR_AUTH_TYPE" ] && [ -n "$SOLR_AUTHENTICATION_OPTS" ]; then
+if [ -z "${SOLR_AUTH_TYPE:-}" ] && [ -n "${SOLR_AUTHENTICATION_OPTS:-}" ]; then
   echo "WARNING: SOLR_AUTHENTICATION_OPTS environment variable configured without associated SOLR_AUTH_TYPE variable"
   echo "         Please configure SOLR_AUTH_TYPE environment variable with the authentication type to be used."
   echo "         Currently supported authentication types are [kerberos, basic]"
 fi
 
-if [ -n "$SOLR_AUTH_TYPE" ] && [ -n "$SOLR_AUTHENTICATION_CLIENT_BUILDER" ]; then
+if [ -n "${SOLR_AUTH_TYPE:-}" ] && [ -n "${SOLR_AUTHENTICATION_CLIENT_BUILDER:-}" ]; then
   echo "WARNING: SOLR_AUTHENTICATION_CLIENT_BUILDER and SOLR_AUTH_TYPE environment variables are configured together."
   echo "         Use SOLR_AUTH_TYPE environment variable to configure authentication type to be used. "
   echo "         Currently supported authentication types are [kerberos, basic]"
   echo "         The value of SOLR_AUTHENTICATION_CLIENT_BUILDER environment variable will be ignored"
 fi
 
-if [ -n "$SOLR_AUTH_TYPE" ]; then
+if [ -n "${SOLR_AUTH_TYPE:-}" ]; then
   case "$(echo $SOLR_AUTH_TYPE | awk '{print tolower($0)}')" in
     basic)
       SOLR_AUTHENTICATION_CLIENT_BUILDER="org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory"
@@ -307,31 +306,29 @@ if [ -n "$SOLR_AUTH_TYPE" ]; then
    esac
 fi
 
-if [ "$SOLR_AUTHENTICATION_CLIENT_CONFIGURER" != "" ]; then
+if [ -n "${SOLR_AUTHENTICATION_CLIENT_CONFIGURER:-}" ]; then
   echo "WARNING: Found unsupported configuration variable SOLR_AUTHENTICATION_CLIENT_CONFIGURER"
   echo "         Please start using SOLR_AUTH_TYPE instead"
 fi
-if [ "$SOLR_AUTHENTICATION_CLIENT_BUILDER" != "" ]; then
+if [ -n "${SOLR_AUTHENTICATION_CLIENT_BUILDER:-}" ]; then
   AUTHC_CLIENT_BUILDER_ARG="-Dsolr.httpclient.builder.factory=$SOLR_AUTHENTICATION_CLIENT_BUILDER"
+  AUTHC_OPTS="${AUTHC_CLIENT_BUILDER_ARG:-}"
 fi
-AUTHC_OPTS="$AUTHC_CLIENT_BUILDER_ARG $SOLR_AUTHENTICATION_OPTS"
+# This looks strange, but it is to avoid extra spaces when we have only one of the values set
+AUTHC_OPTS="${AUTHC_OPTS:-}${SOLR_AUTHENTICATION_OPTS:+ $SOLR_AUTHENTICATION_OPTS}"
 
 # Set the SOLR_TOOL_HOST variable for use when connecting to a running Solr instance
-if [ "$SOLR_HOST" != "" ]; then
-  SOLR_TOOL_HOST="$SOLR_HOST"
-else
-  SOLR_TOOL_HOST="localhost"
-fi
+SOLR_TOOL_HOST="${SOLR_HOST:-localhost}"
 
 function print_usage() {
-  CMD="$1"
-  ERROR_MSG="$2"
+  CMD="${1:-}"
+  ERROR_MSG="${2:-}"
 
-  if [ "$ERROR_MSG" != "" ]; then
+  if [ -n "${ERROR_MSG:-}" ]; then
     echo -e "\nERROR: $ERROR_MSG\n"
   fi
 
-  if [ -z "$CMD" ]; then
+  if [ -z "${CMD:-}" ]; then
     echo ""
     echo "Usage: solr COMMAND OPTIONS"
     echo "       where COMMAND is one of: start, stop, restart, status, healthcheck, create, create_core, create_collection, delete, version, zk, auth, assert, config, export, api, package"
@@ -700,11 +697,10 @@ function solr_pid_by_port() {
   if [ -e "$SOLR_PID_DIR/solr-$THE_PORT.pid" ]; then
     PID=`cat "$SOLR_PID_DIR/solr-$THE_PORT.pid"`
     CHECK_PID=`ps -o pid='' $PID | tr -d ' '`
-    if [ "$CHECK_PID" != "" ]; then
-      local solrPID=$PID
+    if [ -n "$CHECK_PID" ]; then
+      echo $PID
     fi
   fi
-  echo "$solrPID"
 }
 
 # extract the value of the -Djetty.port parameter from a running Solr process
@@ -727,7 +723,7 @@ function jetty_port() {
 # useful for doing cross-platform work from the command-line using Java
 function run_tool() {
 
-  "$JAVA" $SOLR_SSL_OPTS $AUTHC_OPTS $SOLR_ZK_CREDS_AND_ACLS -Dsolr.install.dir="$SOLR_TIP" \
+  "$JAVA" $SOLR_SSL_OPTS $AUTHC_OPTS ${SOLR_ZK_CREDS_AND_ACLS:-} -Dsolr.install.dir="$SOLR_TIP" \
     -Dlog4j.configurationFile="$DEFAULT_SERVER_DIR/resources/log4j2-console.xml" \
     -classpath "$DEFAULT_SERVER_DIR/solr-webapp/webapp/WEB-INF/lib/*:$DEFAULT_SERVER_DIR/lib/ext/*:$DEFAULT_SERVER_DIR/lib/*" \
     org.apache.solr.util.SolrCLI "$@"
@@ -1061,7 +1057,7 @@ if [[ "$SCRIPT_CMD" == "create" || "$SCRIPT_CMD" == "create_core" || "$SCRIPT_CM
 
   if [ $# -gt 0 ]; then
     while true; do
-      case "$1" in
+      case "${1:-}" in
           -c|-core|-collection)
               if [[ -z "$2" || "${2:0:1}" == "-" ]]; then
                 print_usage "$SCRIPT_CMD" "name is required when using the $1 option!"
@@ -1127,7 +1123,7 @@ if [[ "$SCRIPT_CMD" == "create" || "$SCRIPT_CMD" == "create_core" || "$SCRIPT_CM
               break
           ;;
           *)
-              if [ "$1" != "" ]; then
+              if [ -n "${1:-}" ]; then
                 print_usage "$SCRIPT_CMD" "Unrecognized or misplaced argument: $1!"
                 exit 1
               else
@@ -1138,9 +1134,7 @@ if [[ "$SCRIPT_CMD" == "create" || "$SCRIPT_CMD" == "create_core" || "$SCRIPT_CM
     done
   fi
 
-  if [ -z "$CREATE_CONFDIR" ]; then
-    CREATE_CONFDIR='_default'
-  fi
+  : ${CREATE_CONFDIR:=_default}
 
   # validate the confdir arg (if provided)
   if [[ ! -d "$SOLR_TIP/server/solr/configsets/$CREATE_CONFDIR" && ! -d "$CREATE_CONFDIR" ]]; then
@@ -1148,13 +1142,13 @@ if [[ "$SCRIPT_CMD" == "create" || "$SCRIPT_CMD" == "create_core" || "$SCRIPT_CM
     exit 1
   fi
 
-  if [ -z "$CREATE_NAME" ]; then
+  if [ -z "${CREATE_NAME:-}" ]; then
     echo "Name (-c) argument is required!"
     print_usage "$SCRIPT_CMD"
     exit 1
   fi
 
-  if [ -z "$CREATE_PORT" ]; then
+  if [ -z "${CREATE_PORT:-}" ]; then
     for ID in `ps auxww | grep java | grep start\.jar | awk '{print $2}' | sort -r`
       do
         port=`jetty_port "$ID"`
@@ -1165,12 +1159,12 @@ if [[ "$SCRIPT_CMD" == "create" || "$SCRIPT_CMD" == "create_core" || "$SCRIPT_CM
     done
   fi
 
-  if [ -z "$CREATE_PORT" ]; then
+  if [ -z "${CREATE_PORT:-}" ]; then
     echo "Failed to determine the port of a local Solr instance, cannot create $CREATE_NAME!"
     exit 1
   fi
 
-  if [[ "$CREATE_CONFDIR" == "_default" ]] && ([[ "$CREATE_CONFNAME" == "" ]] || [[ "$CREATE_CONFNAME" == "_default" ]]); then
+  if [[ "$CREATE_CONFDIR" == "_default" ]] && [[ "${CREATE_CONFNAME:-_default}" == "_default" ]]; then
     echo "WARNING: Using _default configset with data driven schema functionality. NOT RECOMMENDED for production use."
     echo "         To turn off: bin/solr config -c $CREATE_NAME -p $CREATE_PORT -action set-user-property -property update.autoCreateFields -value false"
   fi
@@ -1186,9 +1180,10 @@ if [[ "$SCRIPT_CMD" == "create" || "$SCRIPT_CMD" == "create_core" || "$SCRIPT_CM
       $VERBOSE
     exit $?
   else
+    # should we be passing confname if it is unset?
     run_tool "$SCRIPT_CMD" -name "$CREATE_NAME" -solrUrl "$SOLR_URL_SCHEME://$SOLR_TOOL_HOST:$CREATE_PORT/solr" \
       -shards "$CREATE_NUM_SHARDS" -replicationFactor "$CREATE_REPFACT" \
-      -confname "$CREATE_CONFNAME" -confdir "$CREATE_CONFDIR" \
+      -confname "${CREATE_CONFNAME:-}" -confdir "$CREATE_CONFDIR" \
       -configsetsDir "$SOLR_TIP/server/solr/configsets" \
       $VERBOSE
     exit $?
@@ -1202,7 +1197,7 @@ if [[ "$SCRIPT_CMD" == "delete" ]]; then
 
   if [ $# -gt 0 ]; then
     while true; do
-      case "$1" in
+      case "${1:-}" in
           -c|-core|-collection)
               if [[ -z "$2" || "${2:0:1}" == "-" ]]; then
                 print_usage "$SCRIPT_CMD" "name is required when using the $1 option!"
@@ -1240,7 +1235,7 @@ if [[ "$SCRIPT_CMD" == "delete" ]]; then
               break
           ;;
           *)
-              if [ "$1" != "" ]; then
+              if [ -n "${1:-}" ]; then
                 print_usage "$SCRIPT_CMD" "Unrecognized or misplaced argument: $1!"
                 exit 1
               else
@@ -1258,11 +1253,9 @@ if [[ "$SCRIPT_CMD" == "delete" ]]; then
   fi
 
   # If not defined, use the collection name for the name of the configuration in Zookeeper
-  if [ -z "$DELETE_CONFIG" ]; then
-    DELETE_CONFIG=true
-  fi
+  : ${DELETE_CONFIG:=true}
 
-  if [ -z "$DELETE_PORT" ]; then
+  if [ -z "${DELETE_PORT:-}" ]; then
     for ID in `ps auxww | grep java | grep start\.jar | awk '{print $2}' | sort -r`
       do
         port=`jetty_port "$ID"`
@@ -1273,7 +1266,7 @@ if [[ "$SCRIPT_CMD" == "delete" ]]; then
     done
   fi
 
-  if [ -z "$DELETE_PORT" ]; then
+  if [ -z "${DELETE_PORT:-}" ]; then
     echo "Failed to determine the port of a local Solr instance, cannot delete $DELETE_NAME!"
     exit 1
   fi
@@ -1294,7 +1287,7 @@ if [[ "$SCRIPT_CMD" == "zk" ]]; then
 
   if [ $# -gt 0 ]; then
     while true; do
-      case "$1" in
+      case "${1:-}" in
         -upconfig|upconfig|-downconfig|downconfig|cp|rm|mv|ls|mkroot)
             if [ "${1:0:1}" == "-" ]; then
               ZK_OP=${1:1}
@@ -1341,13 +1334,13 @@ if [[ "$SCRIPT_CMD" == "zk" ]]; then
             break
         ;;
         *)  # Pick up <src> <dst> or <path> params for rm, ls, cp, mv, mkroot.
-            if [ "$1" == "" ]; then
+            if [ -z "${1:-}" ]; then
               break # out-of-args, stop looping
             fi
-            if [ -z "$ZK_SRC" ]; then
+            if [ -z "${ZK_SRC:-}" ]; then
               ZK_SRC=$1
             else
-              if [ -z "$ZK_DST" ]; then
+              if [ -z "${ZK_DST:-}" ]; then
                 ZK_DST=$1
               else
                 print_short_zk_usage "Unrecognized or misplaced command $1. 'cp' with trailing asterisk requires quoting, see help text."
@@ -1450,7 +1443,7 @@ if [[ "$SCRIPT_CMD" == "auth" ]]; then
   declare -a AUTH_PARAMS
   if [ $# -gt 0 ]; then
     while true; do
-      case "$1" in
+      case "${1:-}" in
         enable|disable)
             AUTH_OP=$1
             AUTH_PARAMS=("${AUTH_PARAMS[@]}" "$AUTH_OP")
@@ -1541,15 +1534,13 @@ if [[ "$SCRIPT_CMD" == "auth" ]]; then
     done
   fi
 
-  if [ -z "$SOLR_SERVER_DIR" ]; then
-    SOLR_SERVER_DIR="$DEFAULT_SERVER_DIR"
-  fi
+  : ${SOLR_SERVER_DIR:=$DEFAULT_SERVER_DIR}
   if [ ! -e "$SOLR_SERVER_DIR" ]; then
     echo -e "\nSolr server directory $SOLR_SERVER_DIR not found!\n"
     exit 1
   fi
 
-  if [ -z "$SOLR_HOME" ]; then
+  if [ -z "${SOLR_HOME:-}" ]; then
     SOLR_HOME="$SOLR_SERVER_DIR/solr"
   elif [[ $SOLR_HOME != /* ]]; then
     if [[ -d "`pwd`/$SOLR_HOME" ]]; then
@@ -1567,7 +1558,7 @@ if [[ "$SCRIPT_CMD" == "auth" ]]; then
 
   AUTH_PARAMS=("${AUTH_PARAMS[@]}" "-solrIncludeFile" "$SOLR_INCLUDE")
 
-  if [ -z "$AUTH_PORT" ]; then
+  if [ -z "${AUTH_PORT:-}" ]; then
     for ID in `ps auxww | grep java | grep start\.jar | awk '{print $2}' | sort -r`
       do
         port=`jetty_port "$ID"`
@@ -1577,7 +1568,7 @@ if [[ "$SCRIPT_CMD" == "auth" ]]; then
         fi
       done
   fi
-  run_tool auth ${AUTH_PARAMS[@]} -solrUrl "$SOLR_URL_SCHEME://$SOLR_TOOL_HOST:$AUTH_PORT/solr" -authConfDir "$SOLR_HOME" $VERBOSE
+  run_tool auth ${AUTH_PARAMS[@]} -solrUrl "$SOLR_URL_SCHEME://$SOLR_TOOL_HOST:${AUTH_PORT:-8983}/solr" -authConfDir "$SOLR_HOME" $VERBOSE
   exit $?
 fi
 
@@ -1591,15 +1582,10 @@ fi
 
 #Check current Ulimits for Open Files and Max Processes.  Warn if they are below the recommended values.
 
-if [ -z "$SOLR_RECOMMENDED_MAX_PROCESSES" ]; then
-  SOLR_RECOMMENDED_MAX_PROCESSES=65000
-fi
+: ${SOLR_RECOMMENDED_MAX_PROCESSES:=65000}
+: ${SOLR_RECOMMENDED_OPEN_FILES:=65000}
 
-if [ -z "$SOLR_RECOMMENDED_OPEN_FILES" ]; then
-  SOLR_RECOMMENDED_OPEN_FILES=65000
-fi
-
-if [ -z "$SOLR_ULIMIT_CHECKS" ] || [ "$SOLR_ULIMIT_CHECKS" != "false" ]; then
+if [[ "${SOLR_ULIMIT_CHECKS:-}" != "false" ]]; then
   if [ "$SCRIPT_CMD" == "start" ] || [ "$SCRIPT_CMD" == "restart" ] || [ "$SCRIPT_CMD" == "status" ]; then
     if hash ulimit 2>/dev/null; then
        openFiles=$(ulimit -n)
@@ -1642,12 +1628,12 @@ fi
 FG="false"
 FORCE=false
 noprompt=false
-SOLR_OPTS=($SOLR_OPTS)
+SOLR_OPTS=(${SOLR_OPTS:-})
 PASS_TO_RUN_EXAMPLE=
 
 if [ $# -gt 0 ]; then
   while true; do
-    case "$1" in
+    case "${1:-}" in
         -c|-cloud)
             SOLR_MODE="solrcloud"
             PASS_TO_RUN_EXAMPLE+=" -c"
@@ -1787,38 +1773,34 @@ if [ $# -gt 0 ]; then
             break
         ;;
         *)
-            if [ "${1:0:2}" == "-D" ]; then
+            if [ -z "${1:-}" ]; then
+              break # out-of-args, stop looping
+            elif [ "${1:0:2}" == "-D" ]; then
               # pass thru any opts that begin with -D (java system props)
               SOLR_OPTS+=("$1")
               PASS_TO_RUN_EXAMPLE+=" $1"
               shift
             else
-              if [ "$1" != "" ]; then
-                print_usage "$SCRIPT_CMD" "$1 is not supported by this script"
-                exit 1
-              else
-                break # out-of-args, stop looping
-              fi
+              print_usage "$SCRIPT_CMD" "$1 is not supported by this script"
+              exit 1
             fi
         ;;
     esac
   done
 fi
 
-if [[ $SOLR_LOG_LEVEL ]] ; then
+if [[ -n ${SOLR_LOG_LEVEL:-} ]] ; then
   SOLR_LOG_LEVEL_OPT="-Dsolr.log.level=$SOLR_LOG_LEVEL"
 fi
 
-if [ -z "$SOLR_SERVER_DIR" ]; then
-  SOLR_SERVER_DIR="$DEFAULT_SERVER_DIR"
-fi
+: ${SOLR_SERVER_DIR:=$DEFAULT_SERVER_DIR}
 
 if [ ! -e "$SOLR_SERVER_DIR" ]; then
   echo -e "\nSolr server directory $SOLR_SERVER_DIR not found!\n"
   exit 1
 fi
 
-if [[ "$FG" == 'true' && "$EXAMPLE" != "" ]]; then
+if [[ "$FG" == 'true' && -n "${EXAMPLE:-}" ]]; then
   FG='false'
   echo -e "\nWARNING: Foreground mode (-f) not supported when running examples.\n"
 fi
@@ -1827,7 +1809,7 @@ fi
 # If the user specified an example to run, invoke the run_example tool (Java app) and exit
 # otherwise let this script proceed to process the user request
 #
-if [ -n "$EXAMPLE" ] && [ "$SCRIPT_CMD" == "start" ]; then
+if [ -n "${EXAMPLE:-}" ] && [ "$SCRIPT_CMD" == "start" ]; then
   run_tool run_example -e $EXAMPLE -d "$SOLR_SERVER_DIR" -urlScheme $SOLR_URL_SCHEME $PASS_TO_RUN_EXAMPLE
   exit $?
 fi
@@ -1840,21 +1822,19 @@ if $verbose ; then
   "$JAVA" -version
 fi
 
-if [ "$SOLR_HOST" != "" ]; then
+if [ -n "${SOLR_HOST:-}" ]; then
   SOLR_HOST_ARG=("-Dhost=$SOLR_HOST")
-elif [[ -z "$SOLR_JETTY_HOST" || "$SOLR_JETTY_HOST" == "127.0.0.1" ]]; then
+elif [[ "${SOLR_JETTY_HOST:-127.0.0.1}" == "127.0.0.1" ]]; then
   # Jetty will only bind on localhost interface, so nodes must advertise themselves with localhost
   SOLR_HOST_ARG=("-Dhost=localhost")
 else
   SOLR_HOST_ARG=()
 fi
 
-if [ -z "$STOP_KEY" ]; then
-  STOP_KEY='solrrocks'
-fi
+: ${STOP_KEY:=solrrocks}
 
 # stop all if no port specified
-if [[ "$SCRIPT_CMD" == "stop" && -z "$SOLR_PORT" ]]; then
+if [[ "$SCRIPT_CMD" == "stop" && -z "${SOLR_PORT:-}" ]]; then
   if $stop_all; then
     none_stopped=true
     find "$SOLR_PID_DIR" -name "solr-*.pid" -type f | while read PIDF
@@ -1901,21 +1881,17 @@ if [[ "$SCRIPT_CMD" == "stop" && -z "$SOLR_PORT" ]]; then
   exit
 fi
 
-if [ -z "$SOLR_PORT" ]; then
-  SOLR_PORT=8983
-fi
+: ${SOLR_PORT:=8983}
 
-if [ -n "$SOLR_PORT_ADVERTISE" ]; then
+if [ -n "${SOLR_PORT_ADVERTISE:-}" ]; then
   SOLR_OPTS+=("-Dsolr.port.advertise=$SOLR_PORT_ADVERTISE")
 fi
 
-if [ -n "$SOLR_JETTY_HOST" ]; then
+if [ -n "${SOLR_JETTY_HOST:-}" ]; then
   SOLR_OPTS+=("-Dsolr.jetty.host=$SOLR_JETTY_HOST")
 fi
 
-if [ -z "$STOP_PORT" ]; then
-  STOP_PORT=`expr $SOLR_PORT - 1000`
-fi
+: ${STOP_PORT:=`expr $SOLR_PORT - 1000`}
 
 if [ "$SCRIPT_CMD" == "start" ] || [ "$SCRIPT_CMD" == "restart" ] ; then
   if [[ $EUID -eq 0 ]] && [[ "$FORCE" == "false" ]] ; then
@@ -1956,7 +1932,7 @@ else
   fi
 fi
 
-if [ -z "$SOLR_HOME" ]; then
+if [ -z "${SOLR_HOME:-}" ]; then
   SOLR_HOME="$SOLR_SERVER_DIR/solr"
 elif [[ $SOLR_HOME != /* ]]; then
   if [[ -d "`pwd`/$SOLR_HOME" ]]; then
@@ -1968,23 +1944,20 @@ elif [[ $SOLR_HOME != /* ]]; then
 fi
 
 # Set the default configset dir to be bootstrapped as _default
-if [ -z "$DEFAULT_CONFDIR" ]; then
-  DEFAULT_CONFDIR="$SOLR_SERVER_DIR/solr/configsets/_default/conf"
-fi
+: ${DEFAULT_CONFDIR:="$SOLR_SERVER_DIR/solr/configsets/_default/conf"}
 
 # This is quite hacky, but examples rely on a different log4j2.xml
 # so that we can write logs for examples to $SOLR_HOME/../logs
-if [ -z "$SOLR_LOGS_DIR" ]; then
-  SOLR_LOGS_DIR="$SOLR_SERVER_DIR/logs"
-fi
+: ${SOLR_LOGS_DIR:="$SOLR_SERVER_DIR/logs"}
 EXAMPLE_DIR="$SOLR_TIP/example"
+# if SOLR_HOME is inside of EXAMPLE_DIR
 if [ "${SOLR_HOME:0:${#EXAMPLE_DIR}}" = "$EXAMPLE_DIR" ]; then
   LOG4J_PROPS="$DEFAULT_SERVER_DIR/resources/log4j2.xml"
   SOLR_LOGS_DIR="$SOLR_HOME/../logs"
 fi
 
 LOG4J_CONFIG=()
-if [ -n "$LOG4J_PROPS" ]; then
+if [ -n "${LOG4J_PROPS:-}" ]; then
   LOG4J_CONFIG+=("-Dlog4j.configurationFile=$LOG4J_PROPS")
 fi
 
@@ -1999,7 +1972,7 @@ if [ ! -e "$SOLR_HOME" ]; then
   echo -e "\nSolr home directory $SOLR_HOME not found!\n"
   exit 1
 fi
-if [[ $SOLR_DATA_HOME ]] && [ ! -e "$SOLR_DATA_HOME" ]; then
+if [[ -n ${SOLR_DATA_HOME:-} ]] && [ ! -e "$SOLR_DATA_HOME" ]; then
   echo -e "\nSolr data home directory $SOLR_DATA_HOME not found!\n"
   exit 1
 fi
@@ -2047,18 +2020,15 @@ if [ "$GC_LOG_OPTS" != "" ]; then
 fi
 
 # If ZK_HOST is defined, the assume SolrCloud mode
-if [[ -n "$ZK_HOST" ]]; then
+if [[ -n "${ZK_HOST:-}" ]]; then
   SOLR_MODE="solrcloud"
 fi
 
-if [ "$SOLR_MODE" == 'solrcloud' ]; then
-  if [ -z "$ZK_CLIENT_TIMEOUT" ]; then
-    ZK_CLIENT_TIMEOUT="30000"
-  fi
-
+if [ "${SOLR_MODE:-}" == 'solrcloud' ]; then
+  : ${ZK_CLIENT_TIMEOUT:=30000}
   CLOUD_MODE_OPTS=("-DzkClientTimeout=$ZK_CLIENT_TIMEOUT")
 
-  if [ "$ZK_HOST" != "" ]; then
+  if [ -n "${ZK_HOST:-}" ]; then
     CLOUD_MODE_OPTS+=("-DzkHost=$ZK_HOST")
   else
     if $verbose ; then
@@ -2068,7 +2038,7 @@ if [ "$SOLR_MODE" == 'solrcloud' ]; then
     CLOUD_MODE_OPTS+=('-DzkRun')
   fi
 
-  if [ -n "$ZK_CREATE_CHROOT" ]; then
+  if [ -n "${ZK_CREATE_CHROOT:-}" ]; then
     CLOUD_MODE_OPTS+=("-DcreateZkChroot=$ZK_CREATE_CHROOT")
   fi
 
@@ -2077,28 +2047,28 @@ if [ "$SOLR_MODE" == 'solrcloud' ]; then
     CLOUD_MODE_OPTS+=('-Dbootstrap_confdir=./solr/collection1/conf' '-Dcollection.configName=myconf' '-DnumShards=1')
   fi
 
-  if [ "$SOLR_SOLRXML_REQUIRED" == "true" ]; then
+  if [ "${SOLR_SOLRXML_REQUIRED:-false}" == "true" ]; then
     CLOUD_MODE_OPTS+=("-Dsolr.solrxml.required=true")
   fi
 else
-  if [ ! -e "$SOLR_HOME/solr.xml" ] && [ "$SOLR_SOLRXML_REQUIRED" == "true" ]; then
+  if [ ! -e "$SOLR_HOME/solr.xml" ] && [ "${SOLR_SOLRXML_REQUIRED:-}" == "true" ]; then
     echo -e "\nSolr home directory $SOLR_HOME must contain a solr.xml file!\n"
     exit 1
   fi
 fi
 
 # Exit if old syntax found
-if [ -n "${SOLR_IP_BLACKLIST}" ] || [ -n "${SOLR_IP_WHITELIST}" ]; then
+if [ -n "${SOLR_IP_BLACKLIST:-}" ] || [ -n "${SOLR_IP_WHITELIST:-}" ]; then
   echo "ERROR: SOLR_IP_BLACKLIST and SOLR_IP_WHITELIST are no longer supported. Please use SOLR_IP_ALLOWLIST and SOLR_IP_DENYLIST instead."
   exit 1
 fi
 
 # IP-based access control
-IP_ACL_OPTS=("-Dsolr.jetty.inetaccess.includes=${SOLR_IP_ALLOWLIST}" \
-             "-Dsolr.jetty.inetaccess.excludes=${SOLR_IP_DENYLIST}")
+IP_ACL_OPTS=("-Dsolr.jetty.inetaccess.includes=${SOLR_IP_ALLOWLIST:-}" \
+             "-Dsolr.jetty.inetaccess.excludes=${SOLR_IP_DENYLIST:-}")
 
 # These are useful for attaching remote profilers like VisualVM/JConsole
-if [ "$ENABLE_REMOTE_JMX_OPTS" == "true" ]; then
+if [ "${ENABLE_REMOTE_JMX_OPTS:-false}" == "true" ]; then
 
   if [ -z "$RMI_PORT" ]; then
     RMI_PORT=`expr $SOLR_PORT + 10000`
@@ -2134,7 +2104,7 @@ else
 fi
 
 # Enable ADMIN UI by default, and give the option for users to disable it
-if [ "$SOLR_ADMIN_UI_DISABLED" == "true" ]; then
+if [ "${SOLR_ADMIN_UI_DISABLED:-false}" == "true" ]; then
   SOLR_ADMIN_UI="-DdisableAdminUI=true"
   echo -e "ADMIN UI Disabled"
 else
@@ -2142,7 +2112,7 @@ else
 fi
 
 JAVA_MEM_OPTS=()
-if [ -z "$SOLR_HEAP" ] && [ -n "$SOLR_JAVA_MEM" ]; then
+if [ -z "${SOLR_HEAP:-}" ] && [ -n "${SOLR_JAVA_MEM:-}" ]; then
   JAVA_MEM_OPTS=($SOLR_JAVA_MEM)
 else
   SOLR_HEAP="${SOLR_HEAP:-512m}"
@@ -2150,14 +2120,9 @@ else
 fi
 
 # Pick default for Java thread stack size, and then add to SOLR_OPTS
-if [ -z ${SOLR_JAVA_STACK_SIZE+x} ]; then
-  SOLR_JAVA_STACK_SIZE='-Xss256k'
-fi
-SOLR_OPTS+=($SOLR_JAVA_STACK_SIZE)
+SOLR_OPTS+=(${SOLR_JAVA_STACK_SIZE:-"-Xss256k"})
 
-if [ -z "$SOLR_TIMEZONE" ]; then
-  SOLR_TIMEZONE='UTC'
-fi
+: ${SOLR_TIMEZONE:=UTC}
 
 function mk_writable_dir() {
   local DIRNAME="$1"
@@ -2182,7 +2147,7 @@ function start_solr() {
   SOLR_JETTY_ADDL_CONFIG="$3"
 
   # define default GC_TUNE
-  if [ -z ${GC_TUNE+x} ]; then
+  if [ -z ${GC_TUNE+x} ]; then # I think this check is backwards?
       GC_TUNE=('-XX:+UseG1GC' \
         '-XX:+PerfDisableSharedMem' \
         '-XX:+ParallelRefProcEnabled' \
@@ -2194,9 +2159,8 @@ function start_solr() {
     GC_TUNE=($GC_TUNE)
   fi
 
-  if [ -n "$SOLR_WAIT_FOR_ZK" ]; then
-    WAIT_FOR_ZK_PROP="-DwaitForZk=$SOLR_WAIT_FOR_ZK"
-    SOLR_OPTS+=($WAIT_FOR_ZK_PROP)
+  if [ -n "${SOLR_WAIT_FOR_ZK:-}" ]; then
+    SOLR_OPTS+=("-DwaitForZk=$SOLR_WAIT_FOR_ZK")
   fi
 
   # If SSL-related system props are set, add them to SOLR_OPTS
@@ -2212,10 +2176,10 @@ function start_solr() {
   fi
 
   # If a heap dump directory is specified, enable it in SOLR_OPTS
-  if [[ -z "$SOLR_HEAP_DUMP_DIR" ]] && [[ "$SOLR_HEAP_DUMP" == "true" ]]; then
+  if [[ -z "${SOLR_HEAP_DUMP_DIR:-}" ]] && [[ "${SOLR_HEAP_DUMP:-}" == "true" ]]; then
     SOLR_HEAP_DUMP_DIR="${SOLR_LOGS_DIR}/dumps"
   fi
-  if [[ -n "$SOLR_HEAP_DUMP_DIR" ]]; then
+  if [[ -n "${SOLR_HEAP_DUMP_DIR:-}" ]]; then
     SOLR_OPTS+=("-XX:+HeapDumpOnOutOfMemoryError")
     SOLR_OPTS+=("-XX:HeapDumpPath=$SOLR_HEAP_DUMP_DIR/solr-$(date +%s)-pid$$.hprof")
   fi
@@ -2269,17 +2233,17 @@ function start_solr() {
   fi
 
   SOLR_START_OPTS=('-server' "${JAVA_MEM_OPTS[@]}" "${GC_TUNE[@]}" "${GC_LOG_OPTS[@]}" "${IP_ACL_OPTS[@]}" \
-    "${REMOTE_JMX_OPTS[@]}" "${CLOUD_MODE_OPTS[@]}" $SOLR_LOG_LEVEL_OPT -Dsolr.log.dir="$SOLR_LOGS_DIR" \
+    "${REMOTE_JMX_OPTS[@]}" "${CLOUD_MODE_OPTS[@]}" ${SOLR_LOG_LEVEL_OPT:-} -Dsolr.log.dir="$SOLR_LOGS_DIR" \
     "-Djetty.port=$SOLR_PORT" "-DSTOP.PORT=$stop_port" "-DSTOP.KEY=$STOP_KEY" \
     # '-OmitStackTraceInFastThrow' ensures stack traces in errors,
     # users who don't care about useful error msgs can override in SOLR_OPTS with +OmitStackTraceInFastThrow
     "${SOLR_HOST_ARG[@]}" "-Duser.timezone=$SOLR_TIMEZONE" "-XX:-OmitStackTraceInFastThrow" \
     "-XX:OnOutOfMemoryError=$SOLR_TIP/bin/oom_solr.sh $SOLR_PORT $SOLR_LOGS_DIR" \
-    "-Djetty.home=$SOLR_SERVER_DIR" "-Dsolr.solr.home=$SOLR_HOME" "-Dsolr.data.home=$SOLR_DATA_HOME" "-Dsolr.install.dir=$SOLR_TIP" \
+    "-Djetty.home=$SOLR_SERVER_DIR" "-Dsolr.solr.home=$SOLR_HOME" "-Dsolr.data.home=${SOLR_DATA_HOME:-}" "-Dsolr.install.dir=$SOLR_TIP" \
     "-Dsolr.default.confdir=$DEFAULT_CONFDIR" "${LOG4J_CONFIG[@]}" "${SOLR_OPTS[@]}" "${SECURITY_MANAGER_OPTS[@]}" "${SOLR_ADMIN_UI}")
 
   mk_writable_dir "$SOLR_LOGS_DIR" "Logs"
-  if [[ -n "$SOLR_HEAP_DUMP_DIR" ]]; then
+  if [[ -n "${SOLR_HEAP_DUMP_DIR:-}" ]]; then
     mk_writable_dir "$SOLR_HEAP_DUMP_DIR" "Heap Dump"
   fi
   case "$SOLR_LOGS_DIR" in
@@ -2338,6 +2302,6 @@ function start_solr() {
   fi
 }
 
-start_solr "$FG" "$ADDITIONAL_CMD_OPTS" "$ADDITIONAL_JETTY_CONFIG"
+start_solr "$FG" "${ADDITIONAL_CMD_OPTS:-}" "${ADDITIONAL_JETTY_CONFIG:-}"
 
 exit $?
diff --git a/solr/docker/tests/cases/initdb/test.sh b/solr/docker/tests/cases/initdb/test.sh
index 9a64244..aff72d6 100755
--- a/solr/docker/tests/cases/initdb/test.sh
+++ b/solr/docker/tests/cases/initdb/test.sh
@@ -41,7 +41,7 @@ if [[ "$data" != /var/solr/initdb-was-here ]]; then
   echo "Test $TEST_DIR $tag failed; script did not run"
   exit 1
 fi
-data=$(docker exec --user=solr "$container_name" ls /var/solr/should-not-be; true)
+data=$(docker exec --user=solr "$container_name" ls /var/solr/should-not-be || true)
 if [[ -n "$data" ]]; then
   echo "Test $TEST_DIR $tag failed; should-not-be was"
   exit 1
diff --git a/solr/packaging/build.gradle b/solr/packaging/build.gradle
index dd25017..b97d0af 100644
--- a/solr/packaging/build.gradle
+++ b/solr/packaging/build.gradle
@@ -15,6 +15,8 @@
  * limitations under the License.
  */
 
+import org.apache.tools.ant.util.TeeOutputStream
+
 // This project puts together a "distribution", assembling dependencies from
 // various other projects.
 
@@ -162,3 +164,62 @@ distTar {
 distZip.enabled = false
 
 assemble.dependsOn installDist
+
+task downloadBats(type: NpmTask) {
+  group = 'Build Dependency Download'
+  args = ["install", "bats",
+                     "ztombol/bats-support",
+                     "ztombol/bats-assert",
+  ]
+
+  inputs.files("${project.ext.nodeProjectDir}/package.json")
+  outputs.dir("${project.ext.nodeProjectDir}/node_modules/bats")
+  outputs.dir("${project.ext.nodeProjectDir}/node_modules/bats-support")
+  outputs.dir("${project.ext.nodeProjectDir}/node_modules/bats-assert")
+}
+
+task integrationTests(type: BatsTask) {
+  dependsOn installDist
+  dependsOn downloadBats
+
+  def integrationTestOutput = "$buildDir/test-output"
+  def solrHome = "$integrationTestOutput/solr-home"
+
+  outputs.dir(integrationTestOutput)
+  outputs.dir(solrHome)
+
+  doFirst {
+    // TODO - if quiet then don't tee
+    standardOutput = new TeeOutputStream(System.out, new FileOutputStream("$integrationTestOutput/test-output.txt"))
+  }
+
+  environment SOLR_TIP: distDir.toString()
+  environment SOLR_HOME: solrHome
+  environment SOLR_LOGS_DIR: "$solrHome/logs"
+  environment BATS_LIB_PREFIX: "$nodeProjectDir/node_modules"
+}
+
+class BatsTask extends Exec {
+  @InputDirectory
+  String testDir = 'test'
+
+  @Input
+  var testFiles = []
+
+  @Option(option = "tests", description = "Sets test cases to be included")
+  public void setTestNamePatterns(List<String> tests) {
+    // TODO: bats --filter <regex>
+    tests.each { testFiles << "$testDir/$it" }
+  }
+
+  @Override
+  @TaskAction
+  protected void exec() {
+    executable "$project.ext.nodeProjectDir/node_modules/bats/bin/bats"
+
+    // Note: tests to run must be listed after all other arguments
+    setArgs(['--print-output-on-failure'] + (testFiles.empty ? testDir : testFiles))
+
+    super.exec()
+  }
+}
\ No newline at end of file
diff --git a/solr/packaging/test/README.md b/solr/packaging/test/README.md
new file mode 100644
index 0000000..91fdac2
--- /dev/null
+++ b/solr/packaging/test/README.md
@@ -0,0 +1,61 @@
+<!--
+    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.
+ -->
+
+# bin/solr Tests
+
+This directory contains tests for the `bin/solr` command-line scripts.
+
+All tests in this project use the [BATS](https://bats-core.readthedocs.io/en/stable/index.html) framework.
+
+## Running Tests
+
+Tests can be run via `./gradlew integrationTests` or using gradle abbreviation `./gradlew iTest`.
+ This will download libraries to your `.gradle` directory, assemble the binary distribution,
+ and run the full test suite.
+
+Individual test files can be selected by specifying the `--tests [test_file.bats]` option.
+ The `--tests` option may be repeated to select multiple test files to run.
+ Wildcarding or specifying individual test methods is currently not supported.
+
+Tests do not currently randomize ports or directories, so they cannot be run
+ in parallel. They may also fail if you already have an external cluster up.
+
+## Writing Tests
+
+Our tests should all `load bats_helper` which provides a `common_setup`
+function that test files can call from their own `setup()` function.
+
+Test are defined as `@test "description of the test" { ... }`
+ with statements in the function body. Test names can include
+ letters, numbers, and spaces. They cannot include special
+ characters like dashes or underscores, so JIRA issue numbers
+ and command line flags are not valid test names. For more detail
+ about BATS features, please consult the documentation.
+
+Some tests will start clusters or create collections,
+ please take care to delete any resources that you create.
+ They will not be cleaned for you automatically.
+
+It is recommended that you install and run `shellcheck` to verify your test scripts and catch common mistakes before committing your changes.
+
+## Limitations
+
+1. Currently this test suite is only available for \*nix environments. Although, newer
+   versions of Windows may be able to run these scripts.
+2. Tests written in bash are both slow, and harder to maintain than traditional
+   JUnit tests.  If a test _can_ be written as a JUnit test, it should be.  This
+   suite should only be used to test things that cannot be tested by JUnit.
diff --git a/solr/packaging/test/bats_helper.bash b/solr/packaging/test/bats_helper.bash
new file mode 100644
index 0000000..88a6cc9
--- /dev/null
+++ b/solr/packaging/test/bats_helper.bash
@@ -0,0 +1,65 @@
+#!/usr/bin/env bats
+
+# 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.
+
+common_setup() {
+    if [ -z ${BATS_LIB_PREFIX:-} ]; then
+        # Debugging help, if you want to run bats directly, try to detect where libraries might be
+        if brew list bats-core; then
+            BATS_LIB_PREFIX="$(brew --prefix)/lib";
+        fi
+    fi
+
+    load "${BATS_LIB_PREFIX}/bats-support/load.bash"
+    load "${BATS_LIB_PREFIX}/bats-assert/load.bash"
+
+    PATH="${SOLR_TIP:-.}/bin:$PATH"
+}
+
+delete_all_collections() {
+  local collection_list="$(solr zk ls /collections -z localhost:9983)"
+  for collection in $collection_list; do
+    if [[ -n $collection ]]; then
+      solr delete -c $collection >/dev/null 2>&1
+    fi
+  done
+}
+
+config_exists() {
+  local config_name=$1
+  local config_list=$(solr zk ls /configs -z localhost:9983)
+
+  for config in $config_list; do
+    if [[ $(echo $config | tr -d " ") == $config_name ]]; then
+      return 0
+    fi
+  done
+
+  return 1
+}
+
+collection_exists() {
+  local coll_name=$1
+  local coll_list=$(solr zk ls /collections -z localhost:9983)
+
+  for coll in $coll_list; do
+    if [[ $(echo $coll | tr -d " ") == $coll_name ]]; then
+      return 0
+    fi
+  done
+
+  return 1
+}
diff --git a/solr/bin-test/test_start_solr.sh b/solr/packaging/test/test_auth.bats
similarity index 56%
rename from solr/bin-test/test_start_solr.sh
rename to solr/packaging/test/test_auth.bats
index 4ec5952..8c3ba47 100644
--- a/solr/bin-test/test_start_solr.sh
+++ b/solr/packaging/test/test_auth.bats
@@ -1,4 +1,5 @@
-#!/bin/bash
+#!/usr/bin/env bats
+
 # 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.
@@ -14,26 +15,21 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+load bats_helper
 
-# All tests should start with solr_test
+setup() {
+  common_setup
 
-function solr_suite_before() {
-  bin/solr stop -all > /dev/null 2>&1
+  run solr auth disable
 }
 
-function solr_suite_after() {
-  bin/solr stop -all > /dev/null 2>&1
+@test "auth rejects blockUnknown option with invalid boolean" {
+  run ! solr auth enable -type basicAuth -credentials any:any -blockUnknown ture
+  assert_output --partial "Argument [blockUnknown] must be either true or false, but was [ture]"
 }
 
-function solr_test_11740_checks_f() {
-  # SOLR-11740
-  bin/solr start
-  bin/solr start -p 7574
-  bin/solr stop -all 2>&1 | grep -i "forcefully killing"
-  rcode=$?
-  if [[ $rcode -eq 0 ]]
-  then
-    echo "Unexpected forceful kill - please check."
-    return 2
-  fi
+@test "auth rejects updateIncludeFileOnly option with invalid boolean" {
+  run ! solr auth enable -type basicAuth -credentials any:any -updateIncludeFileOnly ture
+  assert_output --partial "Argument [updateIncludeFileOnly] must be either true or false, but was [ture]"
 }
+
diff --git a/solr/packaging/test/test_create_collection.bats b/solr/packaging/test/test_create_collection.bats
new file mode 100644
index 0000000..7d01ba4
--- /dev/null
+++ b/solr/packaging/test/test_create_collection.bats
@@ -0,0 +1,88 @@
+#!/usr/bin/env bats
+
+# 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.
+
+load bats_helper
+
+setup_file() {
+  common_setup
+  run solr start -c
+
+  local source_configset_dir="$SOLR_TIP/server/solr/configsets/sample_techproducts_configs"
+  test -d $source_configset_dir
+  cp -r $source_configset_dir "$BATS_TMPDIR/config"
+}
+
+teardown_file() {
+  common_setup
+  run solr stop -all
+}
+
+setup() {
+  common_setup
+}
+
+teardown() {
+  delete_all_collections
+}
+
+@test "create collection" {
+  run solr create_collection -c COLL_NAME
+  assert_output --partial "Created collection 'COLL_NAME'"
+}
+
+@test "reject d option with invalid config dir" {
+  run ! solr create_collection -c COLL_NAME -d /asdf
+  assert_output --partial "Specified configuration directory /asdf not found!"
+}
+
+@test "accept d option with builtin config" {
+  run solr create_collection -c COLL_NAME -d sample_techproducts_configs
+  assert_output --partial "Created collection 'COLL_NAME'"
+}
+
+@test "accept d option with explicit path to config" {
+  run solr create_collection -c COLL_NAME -d "$BATS_TMPDIR/config"
+  assert_output --partial "Created collection 'COLL_NAME'"
+}
+
+@test "accept n option as config name" {
+  run solr create_collection -c COLL_NAME -n other_conf_name
+  assert_output --partial "Created collection 'COLL_NAME'"
+  assert_output --partial "config-set 'other_conf_name'"
+}
+
+@test "allow config reuse when n option specifies same config" {
+  run -0 solr create_collection -c COLL_NAME_1 -n shared_config
+  assert_output --partial "Created collection 'COLL_NAME_1'"
+  assert_output --partial "config-set 'shared_config'"
+
+  run -0 solr create_collection -c COLL_NAME_2 -n shared_config
+  assert_output --partial "Created collection 'COLL_NAME_2'"
+  assert_output --partial "config-set 'shared_config'"
+}
+
+@test "create multisharded collections when s provided" {
+  run -0 solr create_collection -c COLL_NAME -s 2
+  assert_output --partial "Created collection 'COLL_NAME'"
+  assert_output --partial "2 shard(s)"
+}
+
+@test "create replicated collections when rf provided" {
+  run -0 solr create_collection -c COLL_NAME -rf 2
+  assert_output --partial "Created collection 'COLL_NAME'"
+  assert_output --partial "2 replica(s)"
+}
\ No newline at end of file
diff --git a/solr/packaging/test/test_delete_collection.bats b/solr/packaging/test/test_delete_collection.bats
new file mode 100644
index 0000000..1951a98
--- /dev/null
+++ b/solr/packaging/test/test_delete_collection.bats
@@ -0,0 +1,68 @@
+#!/usr/bin/env bats
+
+# 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.
+
+load bats_helper
+
+setup_file() {
+  common_setup
+  run solr start -c
+}
+
+teardown_file() {
+  common_setup
+  run solr stop -all
+}
+
+setup() {
+  common_setup
+}
+
+teardown() {
+  delete_all_collections
+}
+
+@test "can delete collections" {
+  solr create_collection -c "COLL_NAME"
+  assert collection_exists "COLL_NAME"
+
+  solr delete -c "COLL_NAME"
+  refute collection_exists "COLL_NAME"
+}
+
+@test "collection delete also deletes zk config" {
+  solr create_collection -c "COLL_NAME"
+  assert config_exists "COLL_NAME"
+
+  solr delete -c "COLL_NAME"
+  refute config_exists "COLL_NAME"
+}
+
+@test "deletes accompanying zk config with nondefault name" {
+  solr create_collection -c "COLL_NAME" -n "NONDEFAULT_CONFIG_NAME"
+  assert config_exists "NONDEFAULT_CONFIG_NAME"
+
+  solr delete -c "COLL_NAME"
+  refute config_exists "NONDEFAULT_CONFIG_NAME"
+}
+
+@test "deleteConfig option can opt to leave config in zk" {
+  solr create_collection -c "COLL_NAME"
+  assert config_exists "COLL_NAME"
+
+  solr delete -c "COLL_NAME" -deleteConfig false
+  assert config_exists "COLL_NAME"
+}
diff --git a/solr/packaging/test/test_help.bats b/solr/packaging/test/test_help.bats
new file mode 100644
index 0000000..b60cff1
--- /dev/null
+++ b/solr/packaging/test/test_help.bats
@@ -0,0 +1,94 @@
+#!/usr/bin/env bats
+
+# 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.
+
+load bats_helper
+
+setup() {
+  common_setup
+}
+
+@test "start help flag prints help" {
+  run solr start -help
+  assert_output --partial 'Usage: solr start'
+  refute_output --partial 'ERROR'
+}
+
+@test "stop help flag prints help" {
+  run solr stop -help
+  assert_output --partial 'Usage: solr stop'
+  refute_output --partial 'ERROR'
+}
+
+@test "restart help flag prints help" {
+  run solr restart -help
+  assert_output --partial 'Usage: solr restart'
+  refute_output --partial 'ERROR'
+}
+
+@test "status help flag prints help" {
+  skip "Currently the status -help flag doesn't return nice help text!"
+}
+
+@test "healthcheck help flag prints help" {
+  run solr healthcheck -help
+  assert_output --partial 'Usage: solr healthcheck'
+  refute_output --partial 'ERROR'
+}
+
+@test "create help flag prints help" {
+  run solr create -help
+  assert_output --partial 'Usage: solr create'
+  refute_output --partial 'ERROR'
+}
+
+@test "createcore help flag prints help" {
+  run solr create_core -help
+  assert_output --partial 'Usage: solr create_core'
+  refute_output --partial 'ERROR'
+}
+
+@test "createcollection help flag prints help" {
+  run solr create_collection -help
+  assert_output --partial 'Usage: solr create_collection'
+  refute_output --partial 'ERROR'
+}
+
+@test "delete help flag prints help" {
+  run solr delete -help
+  assert_output --partial 'Usage: solr delete'
+  refute_output --partial 'ERROR'
+}
+
+@test "version help flag prints help" {
+  skip "Currently the version -help flag doesn't return nice help text!"
+}
+
+@test "zk help flag prints help" {
+  run solr zk -help
+  assert_output --partial 'Usage: solr zk'
+  refute_output --partial 'ERROR'
+}
+
+@test "auth help flag prints help" {
+  run solr auth -help
+  assert_output --partial 'Usage: solr auth'
+  refute_output --partial 'ERROR'
+}
+
+@test "assert help flag prints help" {
+  skip "Currently the assert -help flag doesn't return nice help text!"
+}
\ No newline at end of file
diff --git a/solr/bin-test/utils/cleanup.sh b/solr/packaging/test/test_start_solr.bats
similarity index 74%
rename from solr/bin-test/utils/cleanup.sh
rename to solr/packaging/test/test_start_solr.bats
index 096cc28..ff0ba35 100644
--- a/solr/bin-test/utils/cleanup.sh
+++ b/solr/packaging/test/test_start_solr.bats
@@ -1,4 +1,5 @@
-#!/bin/bash
+#!/usr/bin/env bats
+
 # 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.
@@ -14,12 +15,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-function delete_all_collections() {
-  local collection_list="$(bin/solr zk ls /collections -z localhost:9983)"
-  for collection in $collection_list;
-  do
-    if [[ -n $collection ]]; then
-      bin/solr delete -c $collection
-    fi
-  done
+load bats_helper
+
+setup() {
+  common_setup
+}
+
+teardown() {
+  solr stop -all >/dev/null 2>&1
+}
+
+@test "SOLR11740 check f" {
+  run -0 solr start
+  run -0 solr start -p 7574
+  run bash -c 'solr stop -all 2>&1'
+  refute_output --partial 'forcefully killing'
 }
diff --git a/solr/solr-ref-guide/build.gradle b/solr/solr-ref-guide/build.gradle
index 1c1f07e..8a7b5cf 100644
--- a/solr/solr-ref-guide/build.gradle
+++ b/solr/solr-ref-guide/build.gradle
@@ -17,10 +17,6 @@
 
 import org.apache.tools.ant.util.TeeOutputStream
 
-plugins {
-    id "com.github.node-gradle.node" version "3.1.1"
-}
-
 description = 'Solr Reference Guide'
 
 def officialSiteIncludePrerelease = propertyOrEnvOrDefault("refguide.official.includePrereleaseVersions", "SOLR_REF_GUIDE_OFFICIAL_INCLUDE_PRERELEASE", "false").toBoolean()
@@ -56,8 +52,6 @@ dependencies {
 
 ext {
     antoraVersion = "3.0.1"
-    rootNodeDir = "${project.rootDir}/.gradle/node"
-    nodeProjectDir = file("${project.ext.rootNodeDir}/ref-guide-project")
 
     siteDir = "${buildDir}/site"
     antoraConfigBuildDir = "${buildDir}/antora-config"
@@ -193,26 +187,6 @@ dependencies {
     Downloading Build Tools
  */
 
-// We use Node to install and run Antora
-node {
-    download = true
-    version = "16.13.2" // LTS
-
-    // The directory where Node.js is unpacked (when download is true)
-    workDir = file("${project.ext.rootNodeDir}/nodejs")
-
-    // The directory where npm is installed (when a specific version is defined)
-    npmWorkDir = file("${project.ext.rootNodeDir}/npm")
-
-    // The directory where yarn is installed (when a Yarn task is used)
-    yarnWorkDir = file("${project.ext.rootNodeDir}/yarn")
-
-    // The Node.js project directory location
-    // This is where the package.json file and node_modules directory are located
-    // By default it is at the root of the current project
-    nodeProjectDir = project.ext.nodeProjectDir
-}
-
 task downloadAntoraCli(type: NpmTask) {
     group = 'Build Dependency Download'
     args = ["install", "@antora/cli@${project.ext.antoraVersion}"]