#!/usr/bin/env bash

# Copyright 2023 The Bazel Authors. All rights reserved.
#
# Licensed 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.

set -eu

# Outputs the major version of gcov.
# Returns 1 if gcov is not installed or a version before 7.0 was found.
function get_gcov_version() {
  local -r gcov_location=$(which gcov)
  if [[ ! -x ${gcov_location:-/usr/bin/gcov} ]]; then
    echo "gcov not installed."
    return 1
  fi
  # Extract gcov's version: the output of `gcov --version` contains the
  # version as a set of major-minor-patch numbers, of which we extract
  # the major version.
  # gcov --version outputs a line like:
  #   gcov (Debian 7.3.0-5) 7.3.0
  # llvm-cov gcov --version outputs a line like:
  #   LLVM version 9.0.1
  local version=$("${gcov_location}" --version | sed -n -E -e 's/^.*\s([0-9]+)\.[0-9]+\.[0-9]+\s?.*$/\1/p')
  if [ "$version" -lt 7 ]; then
      echo "gcov versions before 7.0 is not supported."
      return 1
  fi

  # Disable llvm-cov for now - coverage tests on OSX don't work
  # TODO(cmita): Fix and enable.
  "$gcov_location" -version | grep "LLVM" && \
      echo "gcov LLVM version not supported." && return 1
  echo "$version"
}

# Returns 0 if gcov is not installed or if a version before 7.0 was found.
# Returns 1 otherwise.
# Inverts the return value of get_gcov_version
function is_gcov_missing_or_wrong_version() {
  if get_gcov_version; then
    return 1
  else
    return 0
  fi
}

# Asserts if the given expected coverage result is included in the given output
# file.
#
# - expected_coverage The expected result that must be included in the output.
# - output_file       The location of the coverage output file.
function assert_coverage_result() {
    local expected_coverage="${1}"; shift
    local output_file="${1}"; shift

    # Replace newlines with commas to facilitate the assertion.
    local expected_coverage_no_newlines="$( echo "$expected_coverage" | tr '\n' ',' )"
    local output_file_no_newlines="$( cat "$output_file" | tr '\n' ',' )"
    # strip any trailing commas
    expected_coverage_no_newlines="$( echo "$expected_coverage_no_newlines" | sed 's/,*$//')"
    output_file_no_newlines="$( echo "$output_file_no_newlines" | sed 's/,*$//' )"

    (echo "$output_file_no_newlines" \
        | grep -F "$expected_coverage_no_newlines") \
        || fail "Expected coverage result
<$expected_coverage>
was not found in actual coverage report:
<$( cat "$output_file" )>"
}

# Asserts if the given expected coverage result is included in the given output
# file, accounting for the fact that branch coverage is disabled for gcov v7
#
# - expected coverage The expected result that must be included in the output.
# - output_file       The location of the coverage output file.
function assert_cc_coverage_result() {
  local expected_coverage="${1}"; shift
  local output_file="${1}"; shift

  # we disable branch coverage when using gcov 7 so we should strip all branch
  # information from the "expected" result before checking.
  local gcov_version="$(get_gcov_version)"
  if [[ "$gcov_version" -le 7 ]]; then
    expected_coverage=$(echo "$expected_coverage" | grep -v "^BR")
  fi
  assert_coverage_result "$expected_coverage" "$output_file"
}

# Asserts if the given expected coverage result is included in the given output
# file, accounting for the fact that branch coverage is not output prior to LLVM
# version 12
#
# - expected coverage The expected result that must be included in the output.
# - output_file       The location of the coverage output file.
function assert_llvm_cc_coverage_result() {
  local expected_coverage="${1}"; shift
  local output_file="${1}"; shift

  # LLVM does not output branch coverage before version 12
  local -r clang_version=$(clang --version | grep -o "clang version [0-9]*" | cut -d " " -f 3)
  if [ "$clang_version" -lt 12 ];  then
    expected_coverage=$(echo "$expected_coverage" | grep -v "^BR")
  fi

  assert_coverage_result "$expected_coverage" "$output_file"

}

# Returns the path of the code coverage report that was generated by Bazel by
# looking at the current $TEST_log. The method fails if TEST_log does not
# contain any coverage report for a passed test.
function get_coverage_file_path_from_test_log() {
  local ending_part="$(sed -n -e '/PASSED/,$p' "$TEST_log")"

  local coverage_file_path=$(grep -Eo "/[/a-zA-Z0-9+\.\_\-]+\.dat$" <<< "$ending_part")
  [[ -e "$coverage_file_path" ]] || fail "Coverage output file does not exist!"
  echo "$coverage_file_path"
}

