#!/usr/bin/env 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.

# You'll need a local installation of
# [Apache Yetus' precommit checker](http://yetus.apache.org/documentation/0.1.0/#yetus-precommit)
# to use this personality.
#
# Download from: http://yetus.apache.org/downloads/ . You can either grab the source artifact and
# build from it, or use the convenience binaries provided on that download page.
#
# To run against, e.g. HBASE-15074 you'd then do
# ```bash
# test-patch --personality=dev-support/hbase-personality.sh HBASE-15074
# ```
#
# If you want to skip the ~1 hour it'll take to do all the hadoop API checks, use
# ```bash
# test-patch  --plugins=all,-hadoopcheck --personality=dev-support/hbase-personality.sh HBASE-15074
# ````
#
# pass the `--sentinel` flag if you want to allow test-patch to destructively alter local working
# directory / branch in order to have things match what the issue patch requests.

personality_plugins "all"

if ! declare -f "yetus_info" >/dev/null; then

  function yetus_info
  {
    echo "[$(date) INFO]: $*" 1>&2
  }

fi

# work around yetus overwriting JAVA_HOME from our docker image
function docker_do_env_adds
{
  declare k

  for k in "${DOCKER_EXTRAENVS[@]}"; do
    if [[ "JAVA_HOME" == "${k}" ]]; then
      if [ -n "${JAVA_HOME}" ]; then
        DOCKER_EXTRAARGS+=("--env=JAVA_HOME=${JAVA_HOME}")
      fi
    else
      DOCKER_EXTRAARGS+=("--env=${k}=${!k}")
    fi
  done
}


## @description  Globals specific to this personality
## @audience     private
## @stability    evolving
function personality_globals
{
  BUILDTOOL=maven
  #shellcheck disable=SC2034
  PROJECT_NAME=hbase
  #shellcheck disable=SC2034
  PATCH_BRANCH_DEFAULT=master
  #shellcheck disable=SC2034
  JIRA_ISSUE_RE='^HBASE-[0-9]+$'
  #shellcheck disable=SC2034
  GITHUB_REPO="apache/hbase"

  # TODO use PATCH_BRANCH to select jdk versions to use.

  # Yetus 0.7.0 enforces limits. Default proclimit is 1000.
  # Up it. See HBASE-25081 for how we arrived at this number.
  #shellcheck disable=SC2034
  PROC_LIMIT=30000

  # Set docker container to run with 20g. Default is 4g in yetus.
  # See HBASE-19902 for how we arrived at 20g.
  #shellcheck disable=SC2034
  DOCKERMEMLIMIT=20g
}

## @description  Parse extra arguments required by personalities, if any.
## @audience     private
## @stability    evolving
function personality_parse_args
{
  declare i

  for i in "$@"; do
    case ${i} in
      --exclude-tests-url=*)
        delete_parameter "${i}"
        EXCLUDE_TESTS_URL=${i#*=}
      ;;
      --include-tests-url=*)
        delete_parameter "${i}"
        INCLUDE_TESTS_URL=${i#*=}
      ;;
      --hadoop-profile=*)
        delete_parameter "${i}"
        HADOOP_PROFILE=${i#*=}
      ;;
      --hadoop-three-version=*)
        delete_parameter "${i}"
        HADOOP_THREE_VERSION=${i#*=}
      ;;
      --test-profile=*)
        delete_parameter "${i}"
        TEST_PROFILE=${i#*=}
      ;;
      --skip-errorprone)
        delete_parameter "${i}"
        SKIP_ERRORPRONE=true
      ;;
      --asf-nightlies-general-check-base=*)
        delete_parameter "${i}"
        ASF_NIGHTLIES_GENERAL_CHECK_BASE=${i#*=}
      ;;
      --build-thread=*)
        delete_parameter "${i}"
        BUILD_THREAD=${i#*=}
      ;;
      --surefire-first-part-fork-count=*)
        delete_parameter "${i}"
        SUREFIRE_FIRST_PART_FORK_COUNT=${i#*=}
      ;;
      --surefire-second-part-fork-count=*)
        delete_parameter "${i}"
        SUREFIRE_SECOND_PART_FORK_COUNT=${i#*=}
      ;;
      --java8-home=*)
        delete_parameter "${i}"
        JAVA8_HOME=${i#*=}
      ;;
    esac
  done
}

## @description  Queue up modules for this personality
## @audience     private
## @stability    evolving
## @param        repostatus
## @param        testtype
function personality_modules
{
  local repostatus=$1
  local testtype=$2
  local extra=""
  local MODULES=("${CHANGED_MODULES[@]}")

  yetus_info "Personality: ${repostatus} ${testtype}"

  clear_personality_queue

  # At a few points, hbase modules can run build, test, etc. in parallel
  # Let it happen. Means we'll use more CPU but should be for short bursts.
  # https://cwiki.apache.org/confluence/display/MAVEN/Parallel+builds+in+Maven+3
  if [[ "${testtype}" == mvnsite ]]; then
    yetus_debug "Skip specifying --threads since maven-site-plugin does not support building in parallel."
  else
    if [[ -n "${BUILD_THREAD}" ]]; then
      extra="--threads=${BUILD_THREAD}"
    else
      extra="--threads=2"
    fi
  fi

  # Set java.io.tmpdir to avoid exhausting the /tmp space
  # Just simply set to 'target', it is not very critical so we do not care
  # whether it is placed in the root directory or a sub module's directory
  # let's make it absolute
  tmpdir=$(realpath target)
  extra="${extra} -Djava.io.tmpdir=${tmpdir} -DHBasePatchProcess"

  # If we have HADOOP_PROFILE specified and we're on branch-2.x, pass along
  # the hadoop.profile system property. Ensures that Hadoop2 and Hadoop3
  # logic is not both activated within Maven.
  if [[ -n "${HADOOP_PROFILE}" ]] && [[ "${PATCH_BRANCH}" == *"branch-2"* ]] ; then
    extra="${extra} -Dhadoop.profile=${HADOOP_PROFILE}"
  fi

  # If we have HADOOP_THREE_VERSION specified it pass along in the hadoop.version system property.
  if [[ -n "${HADOOP_THREE_VERSION}" ]] ; then
    extra="${extra} -Dhadoop-three.version=${HADOOP_THREE_VERSION}"
  fi

  # BUILDMODE value is 'full' when there is no patch to be tested, and we are running checks on
  # full source code instead. In this case, do full compiles, tests, etc instead of per
  # module.
  # Used in nightly runs.
  # If BUILDMODE is 'patch', for unit and compile testtypes, there is no need to run individual
  # modules if root is included. HBASE-18505
  if [[ "${BUILDMODE}" == "full" ]] || \
     { { [[ "${testtype}" == unit ]] || [[ "${testtype}" == compile ]] || [[ "${testtype}" == checkstyle ]]; } && \
     [[ "${MODULES[*]}" =~ \. ]]; }; then
    MODULES=(.)
  fi

  # If the checkstyle configs change, check everything.
  if [[ "${testtype}" == checkstyle ]] && [[ "${MODULES[*]}" =~ hbase-checkstyle ]]; then
    MODULES=(.)
  fi

  if [[ ${testtype} == mvninstall ]]; then
    # shellcheck disable=SC2086
    personality_enqueue_module . ${extra}
    return
  fi

  if [[ ${testtype} == spotbugs ]]; then
    # Run spotbugs on each module individually to diff pre-patch and post-patch results and
    # report new warnings for changed modules only.
    # For some reason, spotbugs on root is not working, but running on individual modules is
    # working. For time being, let it run on original list of CHANGED_MODULES. HBASE-19491
    for module in "${CHANGED_MODULES[@]}"; do
      # skip spotbugs on any module that lacks content in `src/main/java`
      if [[ "$(find "${BASEDIR}/${module}" -iname '*.java' -and -ipath '*/src/main/java/*' \
          -type f | wc -l | tr -d '[:space:]')" -eq 0 ]]; then
        yetus_debug "no java files found under ${module}/src/main/java. skipping."
        continue
      else
        # shellcheck disable=SC2086
        personality_enqueue_module ${module} ${extra}
      fi
    done
    return
  fi

  if [[ ${testtype} == compile ]] && [[ "${SKIP_ERRORPRONE}" != "true" ]]; then
    extra="${extra} -PerrorProne"
  fi

  # If EXCLUDE_TESTS_URL/INCLUDE_TESTS_URL is set, fetches the url
  # and sets -Dtest.exclude.pattern/-Dtest to exclude/include the
  # tests respectively.
  if [[ ${testtype} == unit ]]; then
    local tests_arg=""

    if [ -n "${TEST_PROFILE}" ]; then
      extra="${extra} -P${TEST_PROFILE}"
    else
      extra="${extra} -PrunAllTests"
    fi

    get_include_exclude_tests_arg tests_arg
    extra="${extra} ${tests_arg}"

    # Inject the jenkins build-id for our surefire invocations
    # Used by zombie detection stuff, even though we're not including that yet.
    if [ -n "${BUILD_ID}" ]; then
      extra="${extra} -Dbuild.id=${BUILD_ID}"
    fi

    # set forkCount
    if [[ -n "${SUREFIRE_FIRST_PART_FORK_COUNT}" ]]; then
      extra="${extra} -Dsurefire.firstPartForkCount=${SUREFIRE_FIRST_PART_FORK_COUNT}"
    fi

    if [[ -n "${SUREFIRE_SECOND_PART_FORK_COUNT}" ]]; then
      extra="${extra} -Dsurefire.secondPartForkCount=${SUREFIRE_SECOND_PART_FORK_COUNT}"
    fi

    # If the set of changed files includes CommonFSUtils then add the hbase-server
    # module to the set of modules (if not already included) to be tested
    for f in "${CHANGED_FILES[@]}"
    do
      if [[ "${f}" =~ CommonFSUtils ]]; then
        if [[ ! "${MODULES[*]}" =~ hbase-server ]] && [[ ! "${MODULES[*]}" =~ \. ]]; then
          MODULES+=("hbase-server")
        fi
        break
      fi
    done
  fi

  for module in "${MODULES[@]}"; do
    # shellcheck disable=SC2086
    personality_enqueue_module ${module} ${extra}
  done
}

## @description places where we override the built in assumptions about what tests to run
## @audience    private
## @stability   evolving
## @param       filename of changed file
function personality_file_tests
{
  local filename=$1
  yetus_debug "HBase specific personality_file_tests"
  # If the change is to the refguide, then we don't need any builtin yetus tests
  # the refguide test (below) will suffice for coverage.
  if [[ ${filename} =~ src/main/asciidoc ]] ||
     [[ ${filename} =~ src/main/xslt ]]; then
    yetus_debug "Skipping builtin yetus checks for ${filename}. refguide test should pick it up."
  else
    # If we change our asciidoc, rebuild mvnsite
    if [[ ${BUILDTOOL} = maven ]]; then
      if [[ ${filename} =~ src/site || ${filename} =~ src/main/asciidoc ]]; then
        yetus_debug "tests/mvnsite: ${filename}"
        add_test mvnsite
      fi
    fi
    # If we change checkstyle configs, run checkstyle
    if [[ ${filename} =~ checkstyle.*\.xml ]]; then
      yetus_debug "tests/checkstyle: ${filename}"
      add_test checkstyle
    fi
    # fallback to checking which tests based on what yetus would do by default
    if declare -f "${BUILDTOOL}_builtin_personality_file_tests" >/dev/null; then
      "${BUILDTOOL}_builtin_personality_file_tests" "${filename}"
    elif declare -f builtin_personality_file_tests >/dev/null; then
      builtin_personality_file_tests "${filename}"
    fi
  fi
}

## @description  Uses relevant include/exclude env variable to fetch list of included/excluded
#                tests and sets given variable to arguments to be passes to maven command.
## @audience     private
## @stability    evolving
## @param        name of variable to set with maven arguments
# NOTE: INCLUDE_TESTS_URL uses -Dtest= which conflicts with pom.xml <include> patterns.
#       Do not use INCLUDE_TESTS_URL with profiles that define their own <include> patterns
#       (e.g., runLargeTests-wave1, runLargeTests-wave2, runLargeTests-wave3).
function get_include_exclude_tests_arg
{
  local  __resultvar=$1
  yetus_info "EXCLUDE_TESTS_URL=${EXCLUDE_TESTS_URL}"
  yetus_info "INCLUDE_TESTS_URL=${INCLUDE_TESTS_URL}"
  if [[ -n "${EXCLUDE_TESTS_URL}" ]]; then
      if wget "${EXCLUDE_TESTS_URL}" -O "excludes"; then
        excludes=$(cat excludes)
        yetus_debug "excludes=${excludes}"
        if [[ -n "${excludes}" ]]; then
          eval "${__resultvar}='-Dtest.exclude.pattern=${excludes}'"
        fi
        rm excludes
      else
        yetus_error "Wget error $? in fetching excludes file from url" \
             "${EXCLUDE_TESTS_URL}. Ignoring and proceeding."
      fi
  elif [[ -n "$INCLUDE_TESTS_URL" ]]; then
      if wget "$INCLUDE_TESTS_URL" -O "includes"; then
        includes=$(cat includes)
        yetus_debug "includes=${includes}"
        if [[ -n "${includes}" ]]; then
          eval "${__resultvar}='-Dtest=${includes}'"
        fi
        rm includes
      else
        yetus_error "Wget error $? in fetching includes file from url" \
             "${INCLUDE_TESTS_URL}. Ignoring and proceeding."
      fi
  else
    # Use branch specific exclude list when EXCLUDE_TESTS_URL and INCLUDE_TESTS_URL are empty
    FLAKY_URL="https://ci-hadoop.apache.org/job/HBase/job/HBase-Find-Flaky-Tests/job/${PATCH_BRANCH}/lastSuccessfulBuild/artifact/output/excludes"
    if wget "${FLAKY_URL}" -O "excludes"; then
      excludes=$(cat excludes)
        yetus_debug "excludes=${excludes}"
        if [[ -n "${excludes}" ]]; then
          eval "${__resultvar}='-Dtest.exclude.pattern=${excludes}'"
        fi
        rm excludes
      else
        yetus_error "Wget error $? in fetching excludes file from url" \
             "${FLAKY_URL}. Ignoring and proceeding."
      fi
  fi
}

###################################################
# Below here are our one-off tests specific to hbase.
# TODO break them into individual files so it's easier to maintain them?

# TODO line length check? could ignore all java files since checkstyle gets them.

###################################################

add_test_type refguide

function refguide_initialize
{
  maven_add_install refguide
}

function refguide_filefilter
{
  local filename=$1

  # we only generate ref guide on master branch now
  if [[ "${PATCH_BRANCH}" = master ]]; then
    if [[ ${filename} =~ src/main/asciidoc ]] ||
       [[ ${filename} =~ src/main/xslt ]] ||
       [[ ${filename} =~ hbase-common/src/main/resources/hbase-default\.xml ]]; then
      add_test refguide
    fi
  fi
}

function refguide_rebuild
{
  local repostatus=$1
  local logfile="${PATCH_DIR}/${repostatus}-refguide.log"
  declare -i count
  declare pdf_output

  if ! verify_needed_test refguide; then
    return 0
  fi

  big_console_header "Checking we can create the ref guide on ${repostatus}"

  start_clock

  # disabled because "maven_executor" needs to return both command and args
  # shellcheck disable=2046
  echo_and_redirect "${logfile}" \
    $(maven_executor) clean site --batch-mode \
      -pl . \
      -Dtest=NoUnitTests -DHBasePatchProcess -Prelease \
      -Dmaven.javadoc.skip=true -Dcheckstyle.skip=true -Dspotbugs.skip=true

  count=$(${GREP} -c '\[ERROR\]' "${logfile}")
  if [[ ${count} -gt 0 ]]; then
    add_vote_table -1 refguide "${repostatus} has ${count} errors when building the reference guide."
    add_footer_table refguide "@@BASE@@/${repostatus}-refguide.log"
    return 1
  fi

  if ! mv target/site "${PATCH_DIR}/${repostatus}-site"; then
    add_vote_table -1 refguide "${repostatus} failed to produce a site directory."
    add_footer_table refguide "@@BASE@@/${repostatus}-refguide.log"
    return 1
  fi

  if [[ ! -f "${PATCH_DIR}/${repostatus}-site/book.html" ]]; then
    add_vote_table -1 refguide "${repostatus} failed to produce the html version of the reference guide."
    add_footer_table refguide "@@BASE@@/${repostatus}-refguide.log"
    return 1
  fi

  pdf_output="apache_hbase_reference_guide.pdf"

  if [[ ! -f "${PATCH_DIR}/${repostatus}-site/${pdf_output}" ]]; then
    add_vote_table -1 refguide "${repostatus} failed to produce the pdf version of the reference guide."
    add_footer_table refguide "@@BASE@@/${repostatus}-refguide.log"
    return 1
  fi

  add_vote_table 0 refguide "${repostatus} has no errors when building the reference guide. See footer for rendered docs, which you should manually inspect."
  if [[ -n "${ASF_NIGHTLIES_GENERAL_CHECK_BASE}" ]]; then
    add_footer_table refguide "${ASF_NIGHTLIES_GENERAL_CHECK_BASE}/${repostatus}-site/book.html"
  else
    add_footer_table refguide "@@BASE@@/${repostatus}-site/book.html"
  fi
  return 0
}

add_test_type shadedjars


function shadedjars_initialize
{
  yetus_debug "initializing shaded client checks."
  maven_add_install shadedjars
}

## @description  only run the test if java changes.
## @audience     private
## @stability    evolving
## @param        filename
function shadedjars_filefilter
{
  local filename=$1

  if [[ ${filename} =~ \.java$ ]] || [[ ${filename} =~ pom.xml$ ]]; then
    add_test shadedjars
  fi
}

## @description test the shaded client artifacts
## @audience private
## @stability evolving
## @param repostatus
function shadedjars_rebuild
{
  local repostatus=$1
  local logfile="${PATCH_DIR}/${repostatus}-shadedjars.txt"

  if ! verify_needed_test shadedjars; then
    return 0
  fi

  big_console_header "Checking shaded client builds on ${repostatus}"

  start_clock

  local -a maven_args=('clean' 'verify' '-fae' '--batch-mode'
    '-pl' 'hbase-shaded/hbase-shaded-check-invariants' '-am'
    '-DskipTests' '-DHBasePatchProcess' '-Prelease'
    '-Dmaven.javadoc.skip=true' '-Dcheckstyle.skip=true' '-Dspotbugs.skip=true')
  # If we have HADOOP_PROFILE specified and we're on branch-2.x, pass along
  # the hadoop.profile system property. Ensures that Hadoop2 and Hadoop3
  # logic is not both activated within Maven.
  if [[ -n "${HADOOP_PROFILE}" ]] && [[ "${PATCH_BRANCH}" = *"branch-2"* ]] ; then
    maven_args+=("-Dhadoop.profile=${HADOOP_PROFILE}")
  fi

  # disabled because "maven_executor" needs to return both command and args
  # shellcheck disable=2046
  echo_and_redirect "${logfile}" $(maven_executor) "${maven_args[@]}"

  count=$(${GREP} -c '\[ERROR\]' "${logfile}")
  if [[ ${count} -gt 0 ]]; then
    add_vote_table -1 shadedjars "${repostatus} has ${count} errors when building our shaded downstream artifacts."
    add_footer_table shadedjars "@@BASE@@/${repostatus}-shadedjars.txt"
    return 1
  fi

  add_vote_table +1 shadedjars "${repostatus} has no errors when building our shaded downstream artifacts."
  return 0
}

###################################################

add_test_type hadoopcheck

## @description  hadoopcheck file filter
## @audience     private
## @stability    evolving
## @param        filename
function hadoopcheck_filefilter
{
  local filename=$1

  if [[ ${filename} =~ \.java$ ]] || [[ ${filename} =~ pom\.xml$ ]]; then
    add_test hadoopcheck
  fi
}

## @description  Parse args to detect if QUICK_HADOOPCHECK mode is enabled.
## @audience     private
## @stability    evolving
function hadoopcheck_parse_args
{
  declare i

  for i in "$@"; do
    case ${i} in
      --quick-hadoopcheck)
        delete_parameter "${i}"
        QUICK_HADOOPCHECK=true
      ;;
    esac
  done
}

## @description  Adds QUICK_HADOOPCHECK env variable to DOCKER_EXTRAARGS.
## @audience     private
## @stability    evolving
function hadoopcheck_docker_support
{
  DOCKER_EXTRAARGS=("${DOCKER_EXTRAARGS[@]}" "--env=QUICK_HADOOPCHECK=${QUICK_HADOOPCHECK}")
}

## @description  hadoopcheck test
## @audience     private
## @stability    evolving
## @param        repostatus
function hadoopcheck_rebuild
{
  local repostatus=$1
  local hadoopver
  local logfile
  local count
  local result=0
  local hbase_hadoop2_versions
  local hbase_hadoop3_versions
  local savejavahome=${JAVA_HOME}

  if [[ "${repostatus}" = branch ]]; then
    return 0
  fi

  if ! verify_needed_test hadoopcheck; then
    return 0
  fi

  big_console_header "Compiling against various Hadoop versions"

  start_clock

  # All supported Hadoop versions that we want to test the compilation with
  # See the Hadoop section on prereqs in the HBase Reference Guide
  if [[ "${PATCH_BRANCH}" = *"branch-2"* ]]; then
    yetus_info "Setting Hadoop 2 versions to test based on branch-2.5+ rules."
    hbase_hadoop2_versions="2.10.2"
  else
    yetus_info "Setting Hadoop 2 versions to null on master/feature branch rules since we do not support hadoop 2 for hbase 3.x any more."
    hbase_hadoop2_versions=""
  fi

  if [[ "${PATCH_BRANCH}" = *"branch-2.5"* ]]; then
    # TODO remove this on non 2.5 branches ?
    yetus_info "Setting Hadoop 3 versions to test based on branch-2.5 rules"
    if [[ "${QUICK_HADOOPCHECK}" == "true" ]]; then
      hbase_hadoop3_versions="3.2.4 3.3.6 3.4.2"
    else
      hbase_hadoop3_versions="3.2.3 3.2.4 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.4.0 3.4.1 3.4.2"
    fi
  else
    yetus_info "Setting Hadoop 3 versions to test based on branch-2.6+/master/feature branch rules"
    # Isn't runnung these tests with the default Hadoop version redundant ?
    if [[ "${QUICK_HADOOPCHECK}" == "true" ]]; then
      hbase_hadoop3_versions="3.3.6 3.4.2"
    else
      hbase_hadoop3_versions="3.3.5 3.3.6 3.4.0 3.4.1 3.4.2"
    fi
  fi

  export MAVEN_OPTS="${MAVEN_OPTS}"
  for hadoopver in ${hbase_hadoop2_versions}; do
    logfile="${PATCH_DIR}/patch-javac-${hadoopver}.txt"
    # alawys use java8 to build with hadoop 2.x
    if [[ -n "${JAVA8_HOME}" ]]; then
      yetus_info "Switching to java 8 for building against hadoop 2.x"
      export JAVA_HOME=${JAVA8_HOME}
    fi
    # disabled because "maven_executor" needs to return both command and args
    # shellcheck disable=2046
    echo_and_redirect "${logfile}" \
      $(maven_executor) clean install \
        -DskipTests -DHBasePatchProcess \
        -Dhadoop-two.version="${hadoopver}"
    export JAVA_HOME=${savejavahome}
    count=$(${GREP} -c '\[ERROR\]' "${logfile}")
    if [[ ${count} -gt 0 ]]; then
      add_vote_table -1 hadoopcheck "${BUILDMODEMSG} causes ${count} errors with Hadoop v${hadoopver}."
      add_footer_table hadoopcheck "@@BASE@@/patch-javac-${hadoopver}.txt"
      ((result=result+1))
    fi
  done

  hadoop_profile=""
  if [[ "${PATCH_BRANCH}" == *"branch-2"* ]]; then
    hadoop_profile="-Dhadoop.profile=3.0"
  fi
  for hadoopver in ${hbase_hadoop3_versions}; do
    logfile="${PATCH_DIR}/patch-javac-${hadoopver}.txt"
    # disabled because "maven_executor" needs to return both command and args
    # shellcheck disable=2046
    echo_and_redirect "${logfile}" \
      $(maven_executor) clean install \
        -DskipTests -DHBasePatchProcess \
        -Dhadoop-three.version="${hadoopver}" \
        ${hadoop_profile}
    count=$(${GREP} -c '\[ERROR\]' "${logfile}")
    if [[ ${count} -gt 0 ]]; then
      add_vote_table -1 hadoopcheck "${BUILDMODEMSG} causes ${count} errors with Hadoop v${hadoopver}."
      add_footer_table hadoopcheck "@@BASE@@/patch-javac-${hadoopver}.txt"
      ((result=result+1))
    fi
  done

  if [[ ${result} -gt 0 ]]; then
    return 1
  fi

  if [[ -n "${hbase_hadoop3_versions}" ]]; then
    if [[ -n "${hbase_hadoop2_versions}" ]]; then
      add_vote_table +1 hadoopcheck "Patch does not cause any errors with Hadoop ${hbase_hadoop2_versions} or ${hbase_hadoop3_versions}."
    else
      add_vote_table +1 hadoopcheck "Patch does not cause any errors with Hadoop ${hbase_hadoop3_versions}."
    fi
  else
    add_vote_table +1 hadoopcheck "Patch does not cause any errors with Hadoop ${hbase_hadoop2_versions}."
  fi

  logfile="${PATCH_DIR}/patch-install-after-hadoopcheck.txt"
  echo_and_redirect "${logfile}" \
    $(maven_executor) clean install \
      -DskipTests -DHBasePatchProcess

  return 0
}

######################################

# TODO if we need the protoc check, we probably need to check building all the modules that rely on hbase-protocol
add_test_type hbaseprotoc

function hbaseprotoc_initialize
{
  # So long as there are inter-module dependencies on the protoc modules, we
  # need to run a full `mvn install` before a patch can be tested.
  yetus_debug "initializing HBase Protoc plugin."
  maven_add_install hbaseprotoc
}

## @description  hbaseprotoc file filter
## @audience     private
## @stability    evolving
## @param        filename
function hbaseprotoc_filefilter
{
  local filename=$1

  if [[ ${filename} =~ \.proto$ ]]; then
    add_test hbaseprotoc
  fi
}

## @description  check hbase proto compilation
## @audience     private
## @stability    evolving
## @param        repostatus
function hbaseprotoc_rebuild
{
  declare repostatus=$1
  declare i=0
  declare fn
  declare module
  declare logfile
  declare count
  declare result

  if [[ "${repostatus}" = branch ]]; then
    return 0
  fi

  if ! verify_needed_test hbaseprotoc; then
    return 0
  fi

  big_console_header "HBase protoc plugin: ${BUILDMODE}"

  start_clock

  personality_modules patch hbaseprotoc
  # Need to run 'install' instead of 'compile' because shading plugin
  # is hooked-up to 'install'; else hbase-protocol-shaded is left with
  # half of its process done.
  modules_workers patch hbaseprotoc install -DskipTests -DHBasePatchProcess

  # shellcheck disable=SC2153
  until [[ $i -eq "${#MODULE[@]}" ]]; do
    if [[ ${MODULE_STATUS[${i}]} == -1 ]]; then
      ((result=result+1))
      ((i=i+1))
      continue
    fi
    module=${MODULE[$i]}
    fn=$(module_file_fragment "${module}")
    logfile="${PATCH_DIR}/patch-hbaseprotoc-${fn}.txt"

    count=$(${GREP} -c '\[ERROR\]' "${logfile}")

    if [[ ${count} -gt 0 ]]; then
      module_status ${i} -1 "patch-hbaseprotoc-${fn}.txt" "Patch generated "\
        "${count} new protoc errors in ${module}."
      ((result=result+1))
    fi
    ((i=i+1))
  done

  modules_messages patch hbaseprotoc true
  if [[ ${result} -gt 0 ]]; then
    return 1
  fi
  return 0
}

######################################

add_test_type hbaseanti

## @description  hbaseanti file filter
## @audience     private
## @stability    evolving
## @param        filename
function hbaseanti_filefilter
{
  local filename=$1

  if [[ ${filename} =~ \.java$ ]]; then
    add_test hbaseanti
  fi
}

## @description  hbaseanti patch file check
## @audience     private
## @stability    evolving
## @param        filename
function hbaseanti_patchfile
{
  local patchfile=$1
  local warnings
  local result

  if [[ "${BUILDMODE}" = full ]]; then
    return 0
  fi

  if ! verify_needed_test hbaseanti; then
    return 0
  fi

  big_console_header "Checking for known anti-patterns"

  start_clock

  warnings=$(${GREP} -c 'new TreeMap<byte.*()' "${patchfile}")
  if [[ ${warnings} -gt 0 ]]; then
    add_vote_table -1 hbaseanti "" "The patch appears to have anti-pattern where BYTES_COMPARATOR was omitted."
    ((result=result+1))
  fi

  if [[ ${result} -gt 0 ]]; then
    return 1
  fi

  add_vote_table +1 hbaseanti "" "Patch does not have any anti-patterns."
  return 0
}

######################################

add_test_type spotless

## @description  spotless file filter
## @audience     private
## @stability    evolving
## @param        filename
function spotless_filefilter
{
  # always add spotless check as it can format almost all types of files
  add_test spotless
}
## @description run spotless:check to check format issues
## @audience private
## @stability evolving
## @param repostatus
function spotless_rebuild
{
  local repostatus=$1
  local logfile="${PATCH_DIR}/${repostatus}-spotless.txt"
  local linecommentsfile="${PATCH_DIR}/${repostatus}-spotless-linecomments.txt"

  if ! verify_needed_test spotless; then
    return 0
  fi

  big_console_header "Checking spotless on ${repostatus}"

  start_clock

  local -a maven_args=('spotless:check')

  # disabled because "maven_executor" needs to return both command and args
  # shellcheck disable=2046
  echo_and_redirect "${logfile}" $(maven_executor) "${maven_args[@]}"

  count=$(${GREP} -c '\[ERROR\]' "${logfile}")
  if [[ ${count} -gt 0 ]]; then
    # Generate file-level annotations for GitHub Actions
    if [[ -n "${BUGLINECOMMENTS}" ]]; then
      # Extract files with violations: lines like "[ERROR]     src/path/to/file.java"
      # with leading whitespace after [ERROR]
      ${GREP} '^\[ERROR\][[:space:]]\+[^[:space:]]' "${logfile}" \
        | ${SED} 's/^\[ERROR\][[:space:]]*//g' \
        | while read -r file; do
            echo "${file}:1:Spotless formatting required, run mvn spotless:apply"
          done > "${linecommentsfile}"
      if [[ -s "${linecommentsfile}" ]]; then
        bugsystem_linecomments_queue spotless "${linecommentsfile}"
      fi
    fi

    add_vote_table_v2 -1 spotless \
      "@@BASE@@/${repostatus}-spotless.txt" \
      "${repostatus} has ${count} errors when running spotless:check, run spotless:apply to fix."
    return 1
  fi

  add_vote_table_v2 +1 spotless "" "${repostatus} has no errors when running spotless:check."
  return 0
}

######################################

## @description  process the javac output for generating WARNING/ERROR
## @audience     private
## @stability    evolving
## @param        input filename
## @param        output filename
# Override the default javac_logfilter so that we can do a sort before outputing the WARNING/ERROR.
# This is because that the output order of the error prone warnings is not stable, so the diff
# method will report unexpected errors if we do not sort it. Notice that a simple sort will cause
# line number being sorted by lexicographical so the output maybe a bit strange to human but it is
# really hard to sort by file name first and then line number and column number in shell...
function hbase_javac_logfilter
{
  declare input=$1
  declare output=$2

  ${GREP} -E '\[(ERROR|WARNING)\] /.*\.java:' "${input}" | sort > "${output}"
}

## This is named so that yetus will check us right after running tests.
## Essentially, we check for normal failures and then we look for zombies.
#function hbase_unit_logfilter
#{
#  declare testtype="unit"
#  declare input=$1
#  declare output=$2
#  declare processes
#  declare process_output
#  declare zombies
#  declare zombie_count=0
#  declare zombie_process
#
#  yetus_debug "in hbase-specific unit logfilter."
#
#  # pass-through to whatever is counting actual failures
#  if declare -f ${BUILDTOOL}_${testtype}_logfilter >/dev/null; then
#    "${BUILDTOOL}_${testtype}_logfilter" "${input}" "${output}"
#  elif declare -f ${testtype}_logfilter >/dev/null; then
#    "${testtype}_logfilter" "${input}" "${output}"
#  fi
#
#  start_clock
#  if [ -n "${BUILD_ID}" ]; then
#    yetus_debug "Checking for zombie test processes."
#    processes=$(jps -v | "${GREP}" surefirebooter | "${GREP}" -e "hbase.build.id=${BUILD_ID}")
#    if [ -n "${processes}" ] && [ "$(echo "${processes}" | wc -l)" -gt 0 ]; then
#      yetus_warn "Found some suspicious process(es). Waiting a bit to see if they're just slow to stop."
#      yetus_debug "${processes}"
#      sleep 30
#      #shellcheck disable=SC2016
#      for pid in $(echo "${processes}"| ${AWK} '{print $1}'); do
#        # Test our zombie still running (and that it still an hbase build item)
#        process_output=$(ps -p "${pid}" | tail +2 | "${GREP}" -e "hbase.build.id=${BUILD_ID}")
#        if [[ -n "${process_output}" ]]; then
#          yetus_error "Zombie: ${process_output}"
#          ((zombie_count = zombie_count + 1))
#          zombie_process=$(jstack "${pid}" | "${GREP}" -e "\.Test" | "${GREP}" -e "\.java"| head -3)
#          zombies="${zombies} ${zombie_process}"
#        fi
#      done
#    fi
#    if [ "${zombie_count}" -ne 0 ]; then
#      add_vote_table -1 zombies "There are ${zombie_count} zombie test(s)"
#      populate_test_table "zombie unit tests" "${zombies}"
#    else
#      yetus_info "Zombie check complete. All test runs exited normally."
#      stop_clock
#    fi
#  else
#    add_vote_table -0 zombies "There is no BUILD_ID env variable; can't check for zombies."
#  fi
#
#}
