#!/usr/bin/env bash
# shellcheck disable=SC2059

# SPDX-FileCopyrightText: Copyright 2024 Dash0 Inc.
# SPDX-License-Identifier: Apache-2.0

set -eu

RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
dockerfile_injector_build=injector/test/docker/Dockerfile-build

cd "$(dirname "${BASH_SOURCE[0]}")"/../../..

if ! docker info > /dev/null 2>&1; then
  echo "This script uses docker, but it looks like Docker is not running. Please start docker and try again."
  exit 1
fi

# shellcheck source=images/instrumentation/injector/test/scripts/util
. injector/test/scripts/util

# remove all outdated injector binaries
rm -rf injector/test/bin/*

# remove outdated test app binaries
rm -f injector/test/no_environ_symbol/noenviron.*

architectures=""
if [[ -n "${ARCHITECTURES:-}" ]]; then
  architectures=("${ARCHITECTURES//,/ }")
  echo Only testing a subset of architectures: "${architectures[@]}"
fi

libc_flavors=""
if [[ -n "${LIBC_FLAVORS:-}" ]]; then
  libc_flavors=("${LIBC_FLAVORS//,/ }")
  echo Only testing a subset of libc flavors: "${libc_flavors[@]}"
fi

all_test_sets_string=$(find injector/test/scripts/ -name \*.tests -print0 | xargs -0 -n 1 basename | sort | tr '\n' ' ')
read -ra all_test_sets <<< "$all_test_sets_string"
echo Found test sets: "${all_test_sets[@]}"

test_sets=""
if [[ -n "${TEST_SETS:-}" ]]; then
  test_sets=("${TEST_SETS//,/ }")
  echo Only running a subset of test sets: "${test_sets[@]}"
fi

if [[ -n "${TEST_CASES:-}" && -n "${TEST_CASES_CONTAINING:-}"  ]]; then
  echo "Error: TEST_CASES and TEST_CASES_CONTAINING are mutually exclusive, please set at most one of them."
  exit 1
fi

if [[ -n "${TEST_CASES:-}" ]]; then
  echo Only running a subset of test cases: "$TEST_CASES"
else
  TEST_CASES=""
fi

if [[ -n "${TEST_CASES_CONTAINING:-}" ]]; then
  echo Only running test cases which contain: "$TEST_CASES_CONTAINING"
else
  TEST_CASES_CONTAINING=""
fi

global_exit_code=0
test_exit_code_last_test_set=0
summary=""

run_test_set_for_architecture_and_libc_flavor() {
  arch=$1
  libc=$2
  test_set=$3
  echo
  echo "running test set \"$test_set\" on $arch and $libc"
  set +e
  ARCH="$arch" \
    LIBC="$libc" \
    TEST_SET="$test_set" \
    TEST_CASES="$TEST_CASES" \
    TEST_CASES_CONTAINING="$TEST_CASES_CONTAINING" \
    injector/test/scripts/run-tests-for-container.sh
  test_exit_code_last_test_set=$?
  set -e
  echo
  echo ----------------------------------------
}

run_tests_for_architecture_and_libc_flavor() {
  arch=$1
  libc=$2
  echo
  echo ----------------------------------------
  echo "testing the injector library on $arch and $libc"
  echo ----------------------------------------

  test_exit_code=0
  for test_set in "${all_test_sets[@]}"; do
    local test_set_name="${test_set%.tests}"
    if [[ -n "${test_sets[0]}" ]]; then
      if [[ $(echo "${test_sets[@]}" | grep -o "$test_set_name" | wc -w) -eq 0 ]]; then
        echo "skipping test set $test_set"
        continue
      fi
    fi

    run_test_set_for_architecture_and_libc_flavor "$arch" "$libc" "$test_set"
    if [[ $test_exit_code_last_test_set -gt $test_exit_code ]]; then
      test_exit_code=$test_exit_code_last_test_set
    fi
  done

  echo
  echo ----------------------------------------
  if [ $test_exit_code != 0 ]; then
    printf "${RED}tests for %s/%s failed (see above for details)${NC}\n" "$arch" "$libc"
    global_exit_code=1
    summary="$summary\n$arch/$libc:\t${RED}failed${NC}"
  else
    printf "${GREEN}tests for %s/%s were successful${NC}\n" "$arch" "$libc"
    summary="$summary\n$arch/$libc:\t${GREEN}ok${NC}"
  fi
  echo ----------------------------------------
  echo
}

# Create a Dockerfile for building the injector in a container by re-using an excerpt from the ../Dockerfile. This makes
# sure we keep the way we build the injector binary here for this test suite and for actual production usage in the
# instrumentation image in sync.
copy_lines=false
rm -f "$dockerfile_injector_build"
echo "# DO NOT EDIT! This file is generated by images/instrumentation/injector/test/scripts/test-all.sh." >> "$dockerfile_injector_build"
while IFS= read -r line; do
  if [[ "$line" =~ ^.*injector_build_start.*$ ]]; then
    copy_lines=true
    continue
  fi
  if [[ $copy_lines = "true" && "$line" =~ ^.*injector_build_end.*$ ]]; then
    copy_lines=false
    continue
  fi
  if [[ "$line" =~ ^[[:space:]]*#.* ]]; then
    # skip comments
    continue
  fi
  if [[ $copy_lines = "true" ]]; then
    echo "$line" >> "$dockerfile_injector_build"
  fi
done < Dockerfile

if [[ ! -e "$dockerfile_injector_build" ]]; then
  printf "\nError: The file $dockerfile_injector_build has not been generated, stopping."
  exit 1
fi

declare -a all_architectures=(
  "arm64"
  "x86_64"
)
declare -a all_libc_flavors=(
  "glibc"
  "musl"
)

# Remember which container images have been built or pulled for the test run and delete them at the end via a trap.
# Otherwise, on systems where the Docker VM has limited disk space (like Docker Desktop on MacOS), the test run will
# leave behind some fairly large images and hog disk space.
rm -f injector/test/.container_images_to_be_deleted_at_end
touch injector/test/.container_images_to_be_deleted_at_end
trap cleanup_docker_images EXIT

###############################################################
# rebuild all compiled test apps
###############################################################
echo ----------------------------------------
echo building the no_environ_symbol test app binary
echo ----------------------------------------
for arch in "${all_architectures[@]}"; do
  if [[ -n "${architectures[0]}" ]]; then
    if [[ $(echo "${architectures[@]}" | grep -o "$arch" | wc -w) -eq 0 ]]; then
      echo "skipping no_environ_symbol test app build for CPU architecture $arch"
      continue
    fi
  fi
  goarch="$arch"
  if [[ "$goarch" = "x86_64" ]]; then
    goarch="amd64"
  fi
  for libc_flavor in "${all_libc_flavors[@]}"; do
    if [[ -n "${libc_flavors[0]}" ]]; then
      if [[ $(echo "${libc_flavors[@]}" | grep -o "$libc_flavor" | wc -w) -eq 0 ]]; then
        echo "skipping no_environ_symbol test app build for libc flavor $libc_flavor"
        continue
      fi
    fi
    if [[ "$libc_flavor" = "glibc" ]]; then
      no_environ_base_image="golang:1.23.7-bookworm"
    elif [[ "$libc_flavor" = "musl" ]]; then
      no_environ_base_image="golang:1.23.7-alpine3.21"
    fi
    echo "building the no_environ_symbol test app for CPU architecture $arch [GOARCH=$goarch] and libc flavor $libc_flavor (base image: $no_environ_base_image)"
    ARCH="$arch" \
      GOARCH="$goarch" \
      LIBC="$libc_flavor" \
      BASE_IMAGE="$no_environ_base_image" \
      injector/test/scripts/build-in-container.sh \
        "/workspace/noenviron" \
        "injector/test/no_environ_symbol/noenviron.$arch.$libc_flavor" \
        injector/test/no_environ_symbol/Dockerfile-build \
        "no-environ-symbol-builder-$arch-$libc_flavor" \
        injector/test/no_environ_symbol
  done
done

instrumentation_image=${INSTRUMENTATION_IMAGE:-}
if [[ -z "$instrumentation_image" ]]; then
  # build injector binary for both architectures
  echo ----------------------------------------
  echo building the injector binary locally from source
  echo ----------------------------------------
  for arch in "${all_architectures[@]}"; do
    if [[ -n "${architectures[0]}" ]]; then
      if [[ $(echo "${architectures[@]}" | grep -o "$arch" | wc -w) -eq 0 ]]; then
        echo ----------------------------------------
        echo "skipping build for CPU architecture $arch"
        echo ----------------------------------------
        continue
      fi
    fi

    ARCH="$arch" \
      injector/test/scripts/build-in-container.sh \
      "/dash0-init-container/dash0_injector.so" \
      "injector/test/bin/dash0_injector_$arch.so"
  done
else
  if is_remote_image "$instrumentation_image"; then
    echo ----------------------------------------
    printf "using injector binary from existing remote image:\n$instrumentation_image\n"
    echo ----------------------------------------
    echo "$instrumentation_image" >> injector/test/.container_images_to_be_deleted_at_end
    docker pull --platform linux/arm64 "$instrumentation_image"
    copy_binary_from_container_image \
      "$instrumentation_image" \
      linux/arm64 \
      "/dash0-init-container/dash0_injector.so" \
      "injector/test/bin/dash0_injector_arm64.so"
    docker pull --platform linux/amd64 "$instrumentation_image"
    copy_binary_from_container_image \
      "$instrumentation_image" \
      linux/amd64 \
      "/dash0-init-container/dash0_injector.so" \
      "injector/test/bin/dash0_injector_x86_64.so"
  else
    echo ----------------------------------------
    printf "using injector binary from existing local image:\n$instrumentation_image\n"
    echo ----------------------------------------
    for arch in "${all_architectures[@]}"; do
      if [[ -n "${architectures[0]}" ]]; then
        if [[ $(echo "${architectures[@]}" | grep -o "$arch" | wc -w) -eq 0 ]]; then
          echo ----------------------------------------
          echo "skipping copying injector binary from container for CPU architecture $arch"
          echo ----------------------------------------
          continue
        fi
      fi

      if [[ "$arch" = "arm64" ]]; then
        docker_platform="linux/arm64"
      elif [[ "$arch" = "x86_64" ]]; then
        docker_platform="linux/amd64"
      else
        echo "The architecture $arch is not supported."
        exit 1
      fi
      copy_binary_from_container_image \
        "$instrumentation_image" \
        "$docker_platform" \
        "/dash0-init-container/dash0_injector.so" \
        "injector/test/bin/dash0_injector_$arch.so"
    done
  fi
fi
echo

for arch in "${all_architectures[@]}"; do
  if [[ -n "${architectures[0]}" ]]; then
    if [[ $(echo "${architectures[@]}" | grep -o "$arch" | wc -w) -eq 0 ]]; then
      echo ----------------------------------------
      echo "skipping tests on CPU architecture $arch"
      echo ----------------------------------------
      continue
    fi
  fi
  for libc_flavor in "${all_libc_flavors[@]}"; do
    if [[ -n "${libc_flavors[0]}" ]]; then
      if [[ $(echo "${libc_flavors[@]}" | grep -o "$libc_flavor" | wc -w) -eq 0 ]]; then
        echo ----------------------------------------
        echo "skipping tests for libc flavor $libc_flavor"
        echo ----------------------------------------
        continue
      fi
    fi
    run_tests_for_architecture_and_libc_flavor "$arch" "$libc_flavor"
  done
done

printf "$summary\n\n"
exit $global_exit_code
