mirror of
https://github.com/levogevo/add-synced-lyrics.git
synced 2026-07-21 21:45:21 +00:00
add coverage
This commit is contained in:
+2
-1
@@ -1,2 +1,3 @@
|
|||||||
test-dir*
|
test-dir*
|
||||||
*.json
|
*.json
|
||||||
|
coverage*
|
||||||
+232
-50
@@ -3,35 +3,122 @@
|
|||||||
# "${test}" invokes test_ functions
|
# "${test}" invokes test_ functions
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
|
|
||||||
set -u
|
set -eu
|
||||||
|
|
||||||
readonly THIS_DIR="$(dirname "$(readlink -f "$0")")"
|
bash_basename() {
|
||||||
readonly TEST_DIR="${THIS_DIR}/test-dir"
|
local tmp
|
||||||
|
path="${1:-}"
|
||||||
|
suffix="${2:-''}"
|
||||||
|
|
||||||
|
tmp=${path%"${path##*[!/]}"}
|
||||||
|
tmp=${tmp##*/}
|
||||||
|
tmp=${tmp%"${suffix/"$tmp"/}"}
|
||||||
|
|
||||||
|
printf '%s\n' "${tmp:-/}"
|
||||||
|
}
|
||||||
|
|
||||||
|
bash_dirname() {
|
||||||
|
# Usage: dirname "path"
|
||||||
|
local tmp=${1:-.}
|
||||||
|
|
||||||
|
[[ $tmp != *[!/]* ]] && {
|
||||||
|
printf '/\n'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp=${tmp%%"${tmp##*[!/]}"}
|
||||||
|
|
||||||
|
[[ $tmp != */* ]] && {
|
||||||
|
printf '.\n'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp=${tmp%/*}
|
||||||
|
tmp=${tmp%%"${tmp##*[!/]}"}
|
||||||
|
|
||||||
|
printf '%s\n' "${tmp:-/}"
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly PROGPATH="$(readlink -f "$0")"
|
||||||
|
readonly PROGDIR="$(bash_dirname "${PROGPATH}")"
|
||||||
|
readonly PROGNAME="$(bash_basename "${PROGPATH}")"
|
||||||
|
|
||||||
|
# to normalize paths
|
||||||
|
cd "${PROGDIR}"
|
||||||
|
|
||||||
|
readonly TEST_DIR="test-dir"
|
||||||
# lyrics exist for this song
|
# lyrics exist for this song
|
||||||
readonly GOOD_SONG="${TEST_DIR}/back burner.opus"
|
readonly GOOD_SONG="${TEST_DIR}/back burner.opus"
|
||||||
readonly GOOD_LYRICS="${GOOD_SONG//.opus/.lrc}"
|
readonly GOOD_LYRICS="${GOOD_SONG//.opus/.lrc}"
|
||||||
# lyrics do not exist for this song
|
# lyrics do not exist for this song
|
||||||
readonly BAD_SONG="${TEST_DIR}/subdir/avalanche.opus"
|
readonly BAD_SONG="${TEST_DIR}/subdir/avalanche.opus"
|
||||||
readonly BAD_LYRICS="${BAD_SONG//.opus/.lrc}"
|
readonly BAD_LYRICS="${BAD_SONG//.opus/.lrc}"
|
||||||
readonly PROG="${THIS_DIR}/add-synced-lyrics.sh"
|
readonly PROG="add-synced-lyrics.sh"
|
||||||
|
# coverage
|
||||||
|
readonly COVERAGE_DIR="coverage"
|
||||||
|
readonly COVERAGE_FILE="${COVERAGE_DIR}/coverage"
|
||||||
|
|
||||||
|
run_test_cmd() {
|
||||||
|
local expectedRetval="${1:-}"
|
||||||
|
|
||||||
|
shift 1
|
||||||
|
local cmd=("$@")
|
||||||
|
|
||||||
|
# check for empty args for setup tests to run
|
||||||
|
[[ -z "${expectedRetval}" && -z "${cmd[*]}" ]] && return
|
||||||
|
|
||||||
|
# check a value was given
|
||||||
|
if [[ ! "${expectedRetval}" =~ [0-9] ]]; then
|
||||||
|
echo "expectedRetval not given"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local output
|
||||||
|
output="$("${cmd[@]}")"
|
||||||
|
local actualRetval=$?
|
||||||
|
|
||||||
|
declare -g CMD_OUTPUT="${output}"
|
||||||
|
declare -g CMD_RETURN="${actualRetval}"
|
||||||
|
if [[ ${expectedRetval} -ne ${actualRetval} ]]; then
|
||||||
|
echo "${expectedRetval} != ${actualRetval} for ${cmd[*]}"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
test_bad_input() {
|
test_bad_input() {
|
||||||
# incomplete args
|
# incomplete args
|
||||||
"${PROG}" && return 1
|
run_test_cmd \
|
||||||
"${PROG}" --file && return 1
|
1 \
|
||||||
"${PROG}" --dir && return 1
|
add-synced-lyrics
|
||||||
"${PROG}" --recurse && return 1
|
run_test_cmd \
|
||||||
|
1 \
|
||||||
|
add-synced-lyrics --file
|
||||||
|
run_test_cmd \
|
||||||
|
1 \
|
||||||
|
add-synced-lyrics --dir
|
||||||
|
run_test_cmd \
|
||||||
|
1 \
|
||||||
|
add-synced-lyrics --recurse
|
||||||
|
|
||||||
# incorrect mixmatch
|
# incorrect mixmatch
|
||||||
"${PROG}" --file "${PROG}" --recurse && return 1
|
run_test_cmd \
|
||||||
"${PROG}" --file "${PROG}" --dir "${THIS_DIR}" && return 1
|
1 \
|
||||||
"${PROG}" --file "${PROG}" --dir "${THIS_DIR}" --recurse && return 1
|
add-synced-lyrics --file "${PROGPATH}" --recurse
|
||||||
|
run_test_cmd \
|
||||||
|
1 \
|
||||||
|
add-synced-lyrics --file "${PROGPATH}" --dir "${PROGDIR}"
|
||||||
|
run_test_cmd \
|
||||||
|
1 \
|
||||||
|
add-synced-lyrics --file "${PROGPATH}" --dir "${PROGDIR}" --recurse
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
test_determine_inputs() (
|
test_determine_inputs() (
|
||||||
eval "$("${PROG}" --eval)"
|
eval "$(add-synced-lyrics --eval)"
|
||||||
set_default_options
|
set_default_options
|
||||||
|
|
||||||
DIR="${TEST_DIR}"
|
DIR="${TEST_DIR}"
|
||||||
@@ -48,12 +135,12 @@ test_determine_inputs() (
|
|||||||
echo "comparing bash vs fd"
|
echo "comparing bash vs fd"
|
||||||
diff \
|
diff \
|
||||||
<(printf '%s\n' "${bashInputs[@]}") \
|
<(printf '%s\n' "${bashInputs[@]}") \
|
||||||
<(printf '%s\n' "${fdInputs[@]}") || return 1
|
<(printf '%s\n' "${fdInputs[@]}")
|
||||||
|
|
||||||
echo "comparing fd vs find"
|
echo "comparing fd vs find"
|
||||||
diff \
|
diff \
|
||||||
<(printf '%s\n' "${fdInputs[@]}") \
|
<(printf '%s\n' "${fdInputs[@]}") \
|
||||||
<(printf '%s\n' "${findInputs[@]}") || return 1
|
<(printf '%s\n' "${findInputs[@]}")
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
)
|
)
|
||||||
@@ -63,47 +150,55 @@ test_determine_inputs_recurse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test_file_dry() {
|
test_file_dry() {
|
||||||
"${PROG}" --file "${GOOD_SONG}" --dry-run
|
add-synced-lyrics --file "${GOOD_SONG}" --dry-run
|
||||||
}
|
}
|
||||||
|
|
||||||
test_directory_dry() {
|
test_directory_dry() {
|
||||||
"${PROG}" --dir "${TEST_DIR}" --dry-run
|
add-synced-lyrics --dir "${TEST_DIR}" --dry-run
|
||||||
}
|
}
|
||||||
|
|
||||||
test_directory_recurse_dry() {
|
test_directory_recurse_dry() {
|
||||||
"${PROG}" --dir "${TEST_DIR}" --recurse --dry-run
|
add-synced-lyrics --dir "${TEST_DIR}" --recurse --dry-run
|
||||||
}
|
}
|
||||||
|
|
||||||
test_good_file() {
|
test_good_file() {
|
||||||
"${PROG}" --file "${GOOD_SONG}" || return 1
|
add-synced-lyrics --file "${GOOD_SONG}"
|
||||||
test -f "${GOOD_LYRICS}"
|
test -f "${GOOD_LYRICS}"
|
||||||
}
|
}
|
||||||
|
|
||||||
test_bad_file() {
|
test_bad_file() {
|
||||||
"${PROG}" --file "${BAD_SONG}" && return 1
|
run_test_cmd \
|
||||||
! test -f "${BAD_LYRICS}"
|
1 \
|
||||||
|
add-synced-lyrics --file "${BAD_SONG}"
|
||||||
|
run_test_cmd \
|
||||||
|
1 \
|
||||||
|
test -f "${BAD_LYRICS}"
|
||||||
}
|
}
|
||||||
|
|
||||||
test_directory_recurse() {
|
test_directory_recurse() {
|
||||||
setup_test_data || return 1
|
setup_test_data
|
||||||
# this test should fail since it includes the bad file
|
# this test should fail since it includes the bad file
|
||||||
"${PROG}" --dir "${TEST_DIR}" --recurse && return 1
|
run_test_cmd \
|
||||||
|
1 \
|
||||||
return 0
|
add-synced-lyrics --dir "${TEST_DIR}" --recurse
|
||||||
}
|
}
|
||||||
|
|
||||||
test_directory_recurse_ignore() {
|
test_directory_recurse_ignore() {
|
||||||
setup_test_data || return 1
|
setup_test_data
|
||||||
"${PROG}" --dir "${TEST_DIR}" --recurse --ignore && return 1
|
run_test_cmd \
|
||||||
test -f "${GOOD_LYRICS}" || return 1
|
1 \
|
||||||
test -f "${BAD_LYRICS}" && return 1
|
add-synced-lyrics --dir "${TEST_DIR}" --recurse --ignore
|
||||||
|
test -f "${GOOD_LYRICS}"
|
||||||
|
run_test_cmd \
|
||||||
|
1 \
|
||||||
|
test -f "${BAD_LYRICS}"
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_test_data() {
|
setup_test_data() {
|
||||||
test -d "${TEST_DIR}" && rm -rf "${TEST_DIR}"
|
test -d "${TEST_DIR}" && rm -rf "${TEST_DIR}"
|
||||||
mkdir -p "${TEST_DIR}/subdir" || return 1
|
mkdir -p "${TEST_DIR}/subdir"
|
||||||
ffmpegCmd=(
|
ffmpegCmd=(
|
||||||
ffmpeg
|
ffmpeg
|
||||||
-hide_banner
|
-hide_banner
|
||||||
@@ -114,14 +209,13 @@ setup_test_data() {
|
|||||||
)
|
)
|
||||||
"${ffmpegCmd[@]}" \
|
"${ffmpegCmd[@]}" \
|
||||||
-metadata ISRC=USTN10700139 \
|
-metadata ISRC=USTN10700139 \
|
||||||
"${GOOD_SONG}" || return 1
|
"${GOOD_SONG}"
|
||||||
"${ffmpegCmd[@]}" \
|
"${ffmpegCmd[@]}" \
|
||||||
-metadata ISRC=US5261822978 \
|
-metadata ISRC=US5261822978 \
|
||||||
"${BAD_SONG}" || return 1
|
"${BAD_SONG}"
|
||||||
}
|
}
|
||||||
|
|
||||||
TESTS=(
|
TESTS=(
|
||||||
setup_test_data
|
|
||||||
test_determine_inputs
|
test_determine_inputs
|
||||||
test_determine_inputs_recurse
|
test_determine_inputs_recurse
|
||||||
test_bad_input
|
test_bad_input
|
||||||
@@ -136,27 +230,115 @@ TESTS=(
|
|||||||
|
|
||||||
# make sure no test is accidentally missed
|
# make sure no test is accidentally missed
|
||||||
while read -r line; do
|
while read -r line; do
|
||||||
IFS=' ' read -r _ _ test <<<"${line}"
|
IFS=' ' read -r _ _ function <<<"${line}"
|
||||||
if [[ "${TESTS[*]}" != *"${test}"* ]]; then
|
if [[ "${TESTS[*]}" != *"${function}"* &&
|
||||||
echo "missing ${test} from \$TESTS"
|
"${function}" == 'test_'* ]]; then
|
||||||
|
echo "missing ${function} from \$TESTS"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
done <<<"$(declare -F)"
|
done <<<"$(declare -F)"
|
||||||
|
|
||||||
for test in "${TESTS[@]}"; do
|
# generate the coverage function for use with eval
|
||||||
if ! testOutput="$(
|
# to not redefine constants
|
||||||
(
|
gen_coverage_function() {
|
||||||
set -x
|
# shellcheck disable=SC2016
|
||||||
"${test}"
|
echo 'coverage_function () {
|
||||||
) 2>&1
|
local source line
|
||||||
)"; then
|
source="${BASH_SOURCE[1]}" '"
|
||||||
echo
|
source=\"\${source//'${PROGDIR}/'/}\" "'
|
||||||
echo "!! ${test} failed !!"
|
line="${BASH_LINENO[0]}"
|
||||||
echo "${testOutput}"
|
if [[ "${source}" == *".sh" &&
|
||||||
exit 1
|
"${source}" != *"'"${PROGNAME}"'" ]]; then
|
||||||
else
|
{
|
||||||
echo "${test} passed"
|
# minus one because shebang is not included
|
||||||
|
echo "${source} $((line - 1))"' ">>${COVERAGE_FILE}" '
|
||||||
|
}
|
||||||
fi
|
fi
|
||||||
done
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
exit 0
|
setup_coverage() {
|
||||||
|
test -d "${COVERAGE_DIR}" && rm -rf "${COVERAGE_DIR}"
|
||||||
|
mkdir "${COVERAGE_DIR}"
|
||||||
|
: >"${COVERAGE_FILE}"
|
||||||
|
eval "$(gen_coverage_function)"
|
||||||
|
}
|
||||||
|
|
||||||
|
add-synced-lyrics() {
|
||||||
|
export -f coverage_function
|
||||||
|
PS4='$(coverage_function)' bash -x "${PROG}" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
line_is_covered() {
|
||||||
|
local line="$1"
|
||||||
|
local lineNum="$2"
|
||||||
|
|
||||||
|
# empty lines are covered
|
||||||
|
[[ "${#line}" -eq 0 ]] && return 0
|
||||||
|
|
||||||
|
# commented lines are covered
|
||||||
|
read -r firstToken _ <<<"${line}"
|
||||||
|
[[ "${firstToken}" == '#'* ]] && return 0
|
||||||
|
|
||||||
|
# line is explicitly in coverage file is covered
|
||||||
|
grep -q "${coverageFile} ${lineNum}" "${COVERAGE_FILE}" && return 0
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
analyze_coverage() {
|
||||||
|
sort -u "${COVERAGE_FILE}" >"${COVERAGE_FILE}.tmp"
|
||||||
|
mv "${COVERAGE_FILE}.tmp" "${COVERAGE_FILE}"
|
||||||
|
|
||||||
|
mapfile -t coverageFiles < <(cut -d ' ' -f1 "${COVERAGE_FILE}" | sort -u)
|
||||||
|
|
||||||
|
local RED='\e[0;31m' GREEN='\e[0;32m' NC='\e[0m'
|
||||||
|
|
||||||
|
local fileDir fileLen color line linesCovered
|
||||||
|
for coverageFile in "${coverageFiles[@]}"; do
|
||||||
|
fileDir="${COVERAGE_DIR}/$(bash_dirname "${coverageFile}")"
|
||||||
|
test -d "${fileDir}" || mkdir -p "${fileDir}"
|
||||||
|
mapfile -t fileContents <"${coverageFile}"
|
||||||
|
fileLen="${#fileContents[@]}"
|
||||||
|
linesCovered=0
|
||||||
|
{
|
||||||
|
for ((line = 0; line < fileLen; line++)); do
|
||||||
|
if line_is_covered "${fileContents[${line}]}" "${line}"; then
|
||||||
|
color="${GREEN}"
|
||||||
|
linesCovered=$((linesCovered + 1))
|
||||||
|
else
|
||||||
|
color="${RED}"
|
||||||
|
fi
|
||||||
|
printf '%b%s%b\n' \
|
||||||
|
"${color}" \
|
||||||
|
"${fileContents[${line}]}" \
|
||||||
|
"${NC}"
|
||||||
|
done
|
||||||
|
} >"${COVERAGE_DIR}/${coverageFile}"
|
||||||
|
echo "${coverageFile} lines covered: $((linesCovered * 100 / fileLen))%"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
setup_coverage
|
||||||
|
|
||||||
|
for test in "${TESTS[@]}"; do
|
||||||
|
if ! testOutput="$(
|
||||||
|
(
|
||||||
|
set -x
|
||||||
|
"${test}"
|
||||||
|
) 2>&1
|
||||||
|
)"; then
|
||||||
|
echo
|
||||||
|
echo "!! ${test} failed !!"
|
||||||
|
echo "${testOutput}"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "${test} passed"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
analyze_coverage
|
||||||
|
else
|
||||||
|
"$@"
|
||||||
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user