diff --git a/tests/system-test.sh b/tests/system-test.sh new file mode 100755 index 0000000..74de3a5 --- /dev/null +++ b/tests/system-test.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright 2023 Cathy J. Fitzpatrick +# SPDX-License-Identifier: GPL-2.0-or-later +set -efuC -o pipefail +shopt -s inherit_errexit + +# Unlike the main git-remote-gcrypt program, this testing script requires bash +# (rather than POSIX sh) and also depends on various common system utilities +# that the git-remote-gcrypt carefully avoids using (such as mktemp(1)). +# +# The test proceeds by setting up a new repository, making some large commits +# with random data into the repository, pushing the repository to another +# remote using git-remote-gcrypt over the gitception protocol, and then cloning +# the second repository and ensuring that the data it contains is correct. +# +# The random data is obtained from /dev/urandom. This script won't work +# on systems that don't provide /dev/urandom. +# +# The following settings specify the parameters to be used for the test. +num_commits=5 +files_per_commit=3 +random_source="/dev/urandom" +random_data_per_file=5242880 # 5 MiB +default_branch="main" +test_user_name="git-remote-gcrypt" +test_user_email="git-remote-gcrypt@example.com" +pack_size_limit="12m" # If this variable is unset, there is no size limit. + +readonly num_commits files_per_commit random_source random_data_per_file \ + default_branch test_user_name test_user_email pack_size_limit + +# Pipe text into this function to indent it with four spaces. This is used +# to make the output of this script prettier. +indent() { + sed 's/^\(.*\)$/ \1/' +} + +section_break() { + echo + printf '*%.0s' {1..70} + echo $'\n' +} + +assert() { + (set +e; [[ -n ${show_command:-} ]] && set -x; "${@}") + local -r status=${?} + { [[ ${status} -eq 0 ]] && echo "Verification succeeded."; } || \ + echo "Verification failed." + return "${status}" +} + +fastfail() { + "$@" || kill -- "-$$" +} + +umask 077 +tempdir=$(mktemp -d) +readonly tempdir +# shellcheck disable=SC2064 +trap "rm -Rf -- '${tempdir}'" EXIT + +# Set up the PATH to favor the version of git-remote-gcrypt from the repository +# rather than a version that might already be installed on the user's system. +PATH=$(git rev-parse --show-toplevel):${PATH} +readonly PATH +export PATH + +# Unset any GIT_ environment variables to prevent them from affecting the test. +git_env=$(env | sed -n 's/^\(GIT_[^=]*\)=.*$/\1/p') +# shellcheck disable=SC2086 +IFS=$'\n' unset ${git_env} + +# Ensure a predictable gpg configuration. +export GNUPGHOME="${tempdir}/gpg" +mkdir "${GNUPGHOME}" +# Use a wrapper for gpg(1) to avoid cluttering the test output with unnecessary +# warnings about the obsolete `--secret-keyring` option. These warnings are +# caused by git-remote-gcrypt passing an option to gpg(1) that only makes sense +# for ancient versions of gpg(1), but addressing that (if it should be +# addressed at all) is a task best left for another day. +cat << 'EOF' > "${GNUPGHOME}/gpg" +#!/usr/bin/env bash +set -efuC -o pipefail; shopt -s inherit_errexit +args=( "${@}" ) +for ((i = 0; i < ${#}; ++i)); do + if [[ ${args[${i}]} = "--secret-keyring" ]]; then + unset "args[${i}]" "args[$(( i + 1 ))]" + break + fi +done +exec gpg "${args[@]}" +EOF +chmod +x "${GNUPGHOME}/gpg" + +# Ensure a predictable git configuration. +export GIT_CONFIG_SYSTEM=/dev/null +export GIT_CONFIG_GLOBAL="${tempdir}/gitconfig" +mkdir "${tempdir}/template" # Intentionally empty template directory. +git config --global init.defaultBranch "${default_branch}" +git config --global user.name "${test_user_name}" +git config --global user.email "${test_user_email}" +git config --global init.templateDir "${tempdir}/template" +git config --global gpg.program "${GNUPGHOME}/gpg" +[[ -n ${pack_size_limit:-} ]] && \ + git config --global pack.packSizeLimit "${pack_size_limit}" + +# Prepare the random data that we'll be writing to the repository. +total_files=$(( num_commits * files_per_commit )) +random_data_size=$(( total_files * random_data_per_file )) +random_data_file="${tempdir}/data" +head -c "${random_data_size}" "${random_source}" > "${random_data_file}" + +# Create gpg key and subkey. +echo "Step 1: Creating a new GPG key and subkey to use for testing:" +( + set -x + gpg --batch --passphrase "" --quick-generate-key \ + "${test_user_name} <${test_user_email}>" + gpg -K +) 2>&1 | indent + +### +section_break + +echo "Step 2: Creating new repository with random data:" +{ + git init -- "${tempdir}/first" + cd "${tempdir}/first" + for ((i = 0; i < num_commits; ++i)); do + for ((j = 0; j < files_per_commit; ++j)); do + file_index=$(( i * files_per_commit + j )) + random_data_index=$(( file_index * random_data_per_file )) + # shellcheck disable=SC2016 + echo "Writing random file $((file_index + 1))/${total_files}:" \ + '${tempdir}'/"first/$(( file_index )).data " + head -c "${random_data_per_file}" > "$(( file_index )).data" < \ + <(tail -c "+${random_data_index}" "${random_data_file}" || :) + if command -v base64 > /dev/null; then + # shellcheck disable=SC2312 + echo "First 24 bytes in base64:" \ + "$(fastfail head -c 24 "$(( file_index )).data" | \ + fastfail base64)" | indent + fi + done + git add -- "${tempdir}/first" + git commit -m "Commit #${i}" + done + + echo + echo "For reference, here is the commit log for the repository:" + git log --format=oneline | indent +} | indent + +### +section_break + +echo "Step 3: Creating an empty bare repository to receive pushed data:" +git init --bare -- "${tempdir}/second.git" | indent + + +### +section_break + +echo "Step 4: Pushing the first repository to the second one using gitception:" +{ + # Note that when pushing to a bare local repository, git-remote-gcrypt uses + # gitception, rather than treating the remote as a local repository. + ( + set -x + cd "${tempdir}/first" + git push -f "gcrypt::${tempdir}/second.git#${default_branch}" \ + "${default_branch}" + ) 2>&1 + + if command -v tree > /dev/null; then + echo + echo "For reference, here is the directory tree of second.git:" + tree "${tempdir}/second.git" + fi + + echo + echo "Here is the size of each object file in second.git:" + ( + cd "${tempdir}/second.git/objects" + find . -type f -exec du -sh {} + + ) | indent + + echo + echo "Note that git-pack-objects(1) will try to ensure that each object is" + echo "smaller than pack.packSizeLimit (${pack_size_limit:-unlimited}" \ + "here) but this isn't always" + echo "possible because each object contains at least one of our random" + echo "files, and each random file has a certain minimum size. As a result," + echo "pack.packSizeLimit is more of a suggestion than a hard limit." + } | indent + +### +section_break + +echo "Step 5: Cloning the second repository using gitception:" +{ + ( + set -x + git clone -b "${default_branch}" \ + "gcrypt::${tempdir}/second.git#${default_branch}" -- \ + "${tempdir}/third" + ) 2>&1 + + echo + echo "Verifying that the first and third repositories have the same" + echo "commit log as each other:" + # shellcheck disable=SC2312 + assert diff \ + <(fastfail cd "${tempdir}/first"; fastfail git log --oneline) \ + <(fastfail cd "${tempdir}/third"; fastfail git log --oneline) \ + 2>&1 | indent + + echo + echo "Verifying that the first and third repositories have the same" + echo "files in their respective working directories:" + show_command=1 assert diff -r --exclude ".git" -- \ + "${tempdir}/first" "${tempdir}/third" 2>&1 | indent +} | indent