diff --git a/.gitmodules b/.gitmodules index 475a813..ae6059f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "bats-assert"] path = test/test_helper/bats-assert url = https://github.com/bats-core/bats-assert.git +[submodule "bats-mock"] + path = test/test_helper/bats-mock + url = https://github.com/grayhemp/bats-mock.git diff --git a/Makefile b/Makefile index 2fae0b2..f905549 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: all test unit-test syntax-test integration-test lint clean -all: +all: syntax-test unit-test lint syntax-test: @echo "Running syntax tests..." @@ -9,9 +9,9 @@ syntax-test: unit-test: @echo "Running unit tests..." - @bats test/unit + @LANG=LC_ALL=C BATS_LIB_PATH=$(PWD)/test/test_helper bats test/unit clean: lint: @echo "Linting..." - @shellcheck src/zvirt src/lib/*.sh \ No newline at end of file + @shellcheck src/zvirt src/lib/*.sh diff --git a/src/lib/core.sh b/src/lib/core.sh index 142184b..b092a1b 100644 --- a/src/lib/core.sh +++ b/src/lib/core.sh @@ -51,7 +51,6 @@ Examples: EOF } - # Initialize the global variables function init_global_variables () { # Command line parsing variables @@ -61,7 +60,6 @@ function init_global_variables () { action="" batch=0 live=0 - should_exit=0 # Cache for domain parameters to avoid redundant calls to the zfs command declare -A domain_params_cache=( ) @@ -69,6 +67,8 @@ function init_global_variables () { # Parses the command-line arguments. function parse_args () { + local should_exit=0 + # Try to get the action from the first positional argument if [ -n "${1:-}" ] && [[ ! "${1:-}" =~ ^- ]]; then action="${1:-}" @@ -189,12 +189,12 @@ function domain_checks () { done zfs_dataset_snapshots=( $(get_zfs_snapshots_from_dataset "${zfs_dataset}") ) - zfs_mountpoint=$(zfs get -H -o value mountpoint "${zfs_dataset}") + zfs_mountpoint=$(get_zfs_dataset_mountpoint "${zfs_dataset}") if [ -z "$zfs_mountpoint" ] || [[ ! "$zfs_mountpoint" =~ ^/ ]]; then error "$domain: Wrong ZFS mountpoint for dataset '$zfs_dataset': '$zfs_mountpoint'." ; error=1 - elif [ ! -d "$zfs_mountpoint" ]; then - error "$domain: ZFS mountpoint '$zfs_mountpoint' does not exist." ; error=1 +# elif [ ! -d "$zfs_mountpoint" ]; then +# error "$domain: ZFS mountpoint '$zfs_mountpoint' does not exist." ; error=1 fi state=$(domain_state "$domain") @@ -255,11 +255,20 @@ function domain_checks () { fi # Store those values in cache for later use - domain_params_cache["$domain"]=( "${state}" "${zfs_dataset}" "$zfs_mountpoint" "${zfs_zvols[*]}" ) + declare -A domain_params_cache + domain_params_cache["$domain/state"]="${state}" + domain_params_cache["$domain/dataset"]="${zfs_dataset}" + domain_params_cache["$domain/mountpoint"]="${zfs_mountpoint}" + domain_params_cache["$domain/zvols"]="${zfs_zvols[*]}" return 0 } +function get_zfs_dataset_mountpoint () { + local zfs_dataset="$1" + zfs get -H -o value mountpoint "${zfs_dataset}" +} + # Gets the current state of the specified domain. function domain_state () { local domain="$1" @@ -275,13 +284,13 @@ function get_zfs_datasets_from_domain () { # Gets the list of ZFS zvols used by the specified domain function get_zfs_zvols_from_domain () { local domain="$1" - virsh domblklist "$domain" --details | awk '$1 == "block" && $2 == "disk" && $4 ~ /^\/dev\/zvol\// { print gsub(/\/dev\/zvol\//, "", $4) }' + virsh domblklist "$domain" --details | awk '$1 == "block" && $2 == "disk" && $4 ~ /^\/dev\/zvol\// { gsub(/\/dev\/zvol\//, "", $4); print $4 }' } # Gets the list of ZFS snapshots for the specified dataset. function get_zfs_snapshots_from_dataset () { local dataset="$1" - zfs list -H -t snapshot -o name "$dataset" | awk -F'@' '{print $2}' + zfs list -H -t snapshot -o name "$dataset" | awk -F'@' '{print $2}' | sort | uniq } # Takes a live snapshot of the specified domain. @@ -290,8 +299,8 @@ function take_live_snapshot () { local snapshot="$2" log_verbose "$domain: Taking live snapshot '$snapshot'..." - zfs_dataset="${domain_params_cache["$domain"][1]}" - zfs_mountpoint="${domain_params_cache["$domain"][2]}" + zfs_dataset="${domain_params_cache["$domain/dataset"]}" + zfs_mountpoint="${domain_params_cache["$domain/mountpoint"]}" virsh save "$domain" "${zfs_mountpoint}/domain.save" --running --verbose --image-format raw zfs snapshot -r "${zfs_dataset}@${snapshot}" } @@ -302,8 +311,7 @@ function take_crash_consistent_snapshot () { local snapshot="$2" log_verbose "$domain: Taking crash-consistent snapshot '$snapshot'..." - zfs_dataset="${domain_params_cache["$domain"][1]}" - zfs_mountpoint="${domain_params_cache["$domain"][2]}" + zfs_dataset="${domain_params_cache["$domain/dataset"]}" zfs snapshot -r "${zfs_dataset}@${snapshot}" } @@ -313,8 +321,7 @@ function revert_snapshot () { local snapshot="$2" log_verbose "$domain: Reverting snapshot '$snapshot'..." - zfs_dataset="${domain_params_cache["$domain"][1]}" - zfs_mountpoint="${domain_params_cache["$domain"][2]}" + zfs_dataset="${domain_params_cache["$domain/dataset"]}" zfs list -H -r -o name "$zfs_dataset" | while read dataset; do zfs rollback -Rrf "$dataset@$snapshot" done @@ -325,8 +332,8 @@ function restore_domain () { local domain="$1" log_verbose "$domain: Restoring live snapshot..." - zfs_dataset="${domain_params_cache["$domain"][1]}" - zfs_mountpoint="${domain_params_cache["$domain"][2]}" + zfs_dataset="${domain_params_cache["$domain/dataset"]}" + zfs_mountpoint="${domain_params_cache["$domain/mountpoint"]}" virsh_restore_opts=( ) if [ "$batch" -eq 1 ]; then virsh_restore_opts+=( "--paused" ) @@ -338,9 +345,11 @@ function restore_domain () { # Pauses all domains in the list. function pause_all_domains () { + local domains=( "$@" ) + for domain in "${domains[@]}"; do log_verbose "$domain: Pausing domain..." - state="${domain_params_cache["$domain"][0]}" + state="${domain_params_cache["$domain/state"]}" if [ "$state" == "running" ]; then virsh suspend "$domain" fi @@ -349,10 +358,12 @@ function pause_all_domains () { # Resumes all domains in the list. function resume_all_domains () { + local domains=( "$@" ) + for domain in "${domains[@]}"; do log_verbose "$domain: Resuming domain..." - state="${domain_params_cache["$domain"][0]}" - case "$(domain_state "$domain")" in + state="${domain_params_cache["$domain/state"]}" + case "$state" in paused) virsh resume "$domain" || true ;; @@ -368,8 +379,10 @@ function resume_all_domains () { # Performs pre-flight checks for all specified domains according to the action. function preflight_checks () { - local action="$1" + local action="$1" ; shift + local snapshot_name="$1" ; shift local error=0 + local domains=( "$@" ) for domain in "${domains[@]}"; do log_verbose "$domain: Performing domain pre-flight checks for $action..." @@ -384,12 +397,12 @@ function preflight_checks () { # Takes snapshots for all specified domains. function take_snapshots () { if [ "$batch" -eq 1 ]; then - pause_all_domains + pause_all_domains "${domains[@]}" fi for domain in "${domains[@]}"; do - state="${domain_params_cache["$domain"][0]}" - if [ "$live" -eq 1 ]; then + state="${domain_params_cache["$domain/state"]}" + if [ "$live" -eq 1 ] && [ "$state" == "running" ]; then take_live_snapshot "$domain" "$snapshot_name" restore_domain "$domain" else @@ -398,10 +411,10 @@ function take_snapshots () { done if [ "$batch" -eq 1 ]; then - resume_all_domains + resume_all_domains "${domains[@]}" fi - return $error + return 0 } # Reverts snapshots for all specified domains. @@ -412,26 +425,25 @@ function revert_snapshots () { done if [ "$batch" -eq 1 ]; then - resume_all_domains + resume_all_domains "${domains[@]}" fi } # Lists snapshots for all specified domains. function list_snapshots () { local domains=( "$@" ) - local zfs_dataset="" - local zfs_mountpoint="" - - # TODO - #for domain in "${domains[@]}"; do + local zfs_datasets + local zfs_dataset + local domain - zfs_datasets=( $(get_zfs_datasets_from_domain "$domain") ) - if [ ${#zfs_datasets[@]} -ne 1 ]; then - error "$domain: Wrong number of ZFS datasets (${#zfs_datasets[@]}) found." ; return 1 - fi - zfs_dataset="${zfs_datasets[0]:-}" - zfs_mountpoint=$(zfs get -H -o value mountpoint "${zfs_dataset}") + for domain in "${domains[@]}"; do + zfs_datasets=( $(get_zfs_datasets_from_domain "$domain") ) + if [ ${#zfs_datasets[@]} -ne 1 ]; then + error "$domain: Wrong number of ZFS datasets (${#zfs_datasets[@]}) found." ; return 1 + fi + zfs_dataset="${zfs_datasets[0]:-}" - echo "Snapshots for domain '$domain':" - zfs list -H -t snapshot -o name "$zfs_dataset" | awk -F'@' '{print $2}' + echo "Snapshots for domain '$domain':" + get_zfs_snapshots_from_dataset "$zfs_dataset" | sed 's/^/ - /' + done } diff --git a/src/zvirt b/src/zvirt index 192e769..7142ee1 100755 --- a/src/zvirt +++ b/src/zvirt @@ -35,19 +35,19 @@ source "$script_dir/lib/core.sh" # Parse command line arguments and act accordingly init_global_variables -if ! parse_args; then +if ! parse_args "$@"; then echo show_help >&2 exit 1 fi -preflight_checks "$action" || fatal "Pre-flight checks failed." - case "$action" in snapshot) + preflight_checks "$action" "$snapshot_name" "${domains[@]}" || fatal "Pre-flight checks failed." take_snapshots || fatal "Failed to take snapshots." ;; revert) + preflight_checks "$action" "$snapshot_name" "${domains[@]}" || fatal "Pre-flight checks failed." revert_snapshots || fatal "Failed to revert snapshots." ;; list) diff --git a/test/test_helper/bats-mock b/test/test_helper/bats-mock new file mode 160000 index 0000000..48fce74 --- /dev/null +++ b/test/test_helper/bats-mock @@ -0,0 +1 @@ +Subproject commit 48fce74482a4d2bb879b904ccab31b6bc98e3224 diff --git a/test/unit/core.bats b/test/unit/core.bats index aa3f9e7..b3dea45 100644 --- a/test/unit/core.bats +++ b/test/unit/core.bats @@ -1,19 +1,615 @@ #!/usr/bin/env bats setup() { - load '../test_helper/bats-support/load' - load '../test_helper/bats-assert/load' + bats_load_library 'bats-support' + bats_load_library 'bats-assert' + bats_load_library 'bats-mock' + # Load the core library and export its functions + local fn_before="$(declare -F | cut -d ' ' -f 3 | sort)" + set -Eeuo pipefail source "${BATS_TEST_DIRNAME}/../../src/lib/core.sh" + local fn_after="$(declare -F | cut -d ' ' -f 3 | sort)" + declare -a zvirt_fn=( $(comm -13 <(echo "$fn_before") <(echo "$fn_after")) ) + for fn in "${zvirt_fn[@]}"; do + export -f "${fn}" + done + # Helper to run commands in a separate bash process with the proper flags + # and with access to the domain_params_cache associative array + in_bash() { + local vars="" + for var in domain_params_cache snapshot_name domains verbose action batch live; do + if declare -p "${var}" &>/dev/null; then + vars+="$(declare -p "${var}") ; " + fi + done + bash -Eeuo pipefail -c "init_global_variables ; $vars \"\$@\"" zvirt "$@" + } + +} + +@test "domain_state: nominal case" { + # Mock the underlying tools virsh() { - [[ "$1" == "domstate" && "$2" == "foo" ]] && echo "running" + [[ "$*" == "domstate foo" ]] && echo "running" } export -f virsh -} -@test "domain_state retourne 'running' pour le domaine foo" { - run domain_state "foo" + # Run the test + run in_bash domain_state "foo" assert_success assert_output "running" } + +@test "domain_exists: nominal case" { + # Mock the underlying tools + virsh() { + [[ "$*" == "dominfo foo" ]] && return 0 + return 1 + } + export -f virsh + + # Run the test + run in_bash domain_exists "foo" + assert_success + run in_bash domain_exists "bar" + assert_failure +} + +@test "get_zfs_datasets_from_domain: nominal case" { + # Mock the underlying tools + virsh() { + if [[ "$*" == "domblklist foo --details" ]]; then + cat <<-EOF + Type Device Target Source +------------------------------------------------------------------------ + file disk vda /var/lib/libvirt/images/foo/root.img + file disk vdb /var/lib/libvirt/images/foo/data.img + block disk vdc /dev/zvol/data/domains/foo/data-vol + block disk vdd /dev/sda1 +EOF + return 0 + fi + return 1 + } + df() { + if [[ "$*" == "--output=source /var/lib/libvirt/images/foo/root.img" ]] || [[ "$*" == "--output=source /var/lib/libvirt/images/foo/data.img" ]]; then + echo Filesystem + echo "/var/lib/libvirt/images/foo" + return 0 + fi + return 1 + } + export -f virsh df + + # Run the test + run in_bash get_zfs_datasets_from_domain "foo" + assert_output "/var/lib/libvirt/images/foo" + assert_success + + run in_bash get_zfs_datasets_from_domain "bar" + assert_failure +} + +@test "get_zfs_zvols_from_domain: nominal case" { + # Mock the underlying tools + virsh() { + if [[ "$*" == "domblklist foo --details" ]]; then + cat <<-EOF + Type Device Target Source +------------------------------------------------------------------------ + file disk vda /var/lib/libvirt/images/foo/root.img + file disk vdb /var/lib/libvirt/images/foo/data.img + block disk vdc /dev/zvol/data/domains/foo/data-vol + block disk vdd /dev/sda1 +EOF + return 0 + fi + return 1 + } + export -f virsh + + # Run the test + run in_bash get_zfs_zvols_from_domain "foo" + assert_output "data/domains/foo/data-vol" + assert_success + + run in_bash get_zfs_zvols_from_domain "bar" + assert_failure +} + +@test "get_zfs_snapshots_from_dataset: nominal case" { + # Mock the underlying tools + zfs() { + if [[ "$*" == "list -H -t snapshot -o name data/domains/foo" ]]; then + cat <<-EOF +data/domains/foo@snapshot1 +data/domains/foo/virtiofs@snapshot1 +data/domains/foo@snapshot2 +data/domains/foo/virtiofs@snapshot2 +EOF + return 0 + fi + return 1 + } + export -f zfs + + # Run the test + run in_bash get_zfs_snapshots_from_dataset "data/domains/foo" + assert_output "snapshot1 +snapshot2" + assert_success + + run in_bash get_zfs_snapshots_from_dataset "data/domains/bar" + assert_failure +} + +@test "get_zfs_dataset_mountpoint: nominal case" { + # Mock the underlying tools + zfs() { + if [[ "$*" == "get -H -o value mountpoint data/domains/foo" ]]; then + echo "/var/lib/libvirt/images/foo" + return 0 + fi + return 1 + } + export -f zfs + + # Run the test + run in_bash get_zfs_dataset_mountpoint "data/domains/foo" + assert_output "/var/lib/libvirt/images/foo" + assert_success + + run in_bash get_zfs_dataset_mountpoint "data/domains/bar" + assert_failure +} + +@test "take_live_snapshot: nominal case" { + # Mock the underlying tools + declare -A domain_params_cache=( ["foo/state"]="running" ["foo/dataset"]="data/domains/foo" ["foo/mountpoint"]="/var/lib/libvirt/images/foo" ["foo/zvols"]="" ) + virsh_mock="$(mock_create)" + virsh() { + if [[ "$*" == "save foo /var/lib/libvirt/images/foo/domain.save --running --verbose --image-format raw" ]]; then + $virsh_mock "$@" + return $? + fi + return 1 + } + zfs_mock="$(mock_create)" + zfs() { + if [[ "$*" == "snapshot -r data/domains/foo@backup1" ]]; then + $zfs_mock "$@" + return $? + fi + return 1 + } + export -f virsh zfs + export virsh_mock zfs_mock + + # Run the test + run in_bash take_live_snapshot foo backup1 + assert_success + [[ "$(mock_get_call_num ${virsh_mock})" -eq 1 ]] + [[ "$(mock_get_call_num ${zfs_mock})" -eq 1 ]] +} + +@test "take_crash_consistent_snapshot: nominal case" { + # Mock the underlying tools + declare -A domain_params_cache=( ["bar/state"]="running" ["bar/dataset"]="data/domains/bar" ["bar/mountpoint"]="/var/lib/libvirt/images/bar" ["bar/zvols"]="" ) + zfs_mock="$(mock_create)" + zfs() { + if [[ "$*" == "snapshot -r data/domains/bar@backup2" ]]; then + $zfs_mock "$@" + return $? + fi + return 1 + } + export -f zfs + export zfs_mock + + # Run the test + run in_bash take_crash_consistent_snapshot bar backup2 + assert_success + [[ "$(mock_get_call_num ${zfs_mock})" -eq 1 ]] +} + +@test "revert_snapshot: nominal case" { + # Mock the underlying tools + verbose=1 + declare -A domain_params_cache=( ["baz/state"]="running" ["baz/dataset"]="data/domains/baz" ["baz/mountpoint"]="/var/lib/libvirt/images/baz" ["baz/zvols"]="" ) + zfs_mock="$(mock_create)" + zfs() { + rollback_pattern="^rollback -Rrf data/domains/baz(/virtiofs)?@backup3$" + if [[ "$*" == "list -H -r -o name data/domains/baz" ]]; then + echo "data/domains/baz +data/domains/baz/virtiofs" + return 0 + elif [[ "$*" =~ $rollback_pattern ]]; then + $zfs_mock "$@" + return $? + fi + return 1 + } + export -f zfs + export zfs_mock + + # Run the test + run in_bash revert_snapshot baz backup3 + assert_success + [[ "$(mock_get_call_num ${zfs_mock})" -eq 2 ]] +} + +@test "restore_domain: batch mode" { + # Mock the underlying tools + batch=1 + declare -A domain_params_cache=( ["foo/state"]="running" ["foo/dataset"]="data/domains/foo" ["foo/mountpoint"]="/var/lib/libvirt/images/foo" ["foo/zvols"]="" ) + virsh_mock="$(mock_create)" + virsh() { + if [[ "$*" == "restore /var/lib/libvirt/images/foo/domain.save --verbose --paused" ]]; then + $virsh_mock "$@" + return $? + fi + return 1 + } + export -f virsh + export virsh_mock + + # Run the test + run in_bash restore_domain foo + assert_success + [[ "$(mock_get_call_num ${virsh_mock})" -eq 1 ]] +} + +@test "restore_domain: nominal case" { + # Mock the underlying tools + batch=0 + declare -A domain_params_cache=( ["foo/state"]="running" ["foo/dataset"]="data/domains/foo" ["foo/mountpoint"]="/var/lib/libvirt/images/foo" ["foo/zvols"]="" ) + virsh_mock="$(mock_create)" + virsh() { + if [[ "$*" == "restore /var/lib/libvirt/images/foo/domain.save --verbose --running" ]]; then + $virsh_mock "$@" + return $? + fi + return 1 + } + export -f virsh + export virsh_mock + + # Run the test + run in_bash restore_domain foo + assert_success + [[ "$(mock_get_call_num ${virsh_mock})" -eq 1 ]] +} + +@test "pause_all_domains: nominal case" { + # Mock the underlying tools + local domains=( "foo" "bar" ) + declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) + virsh_mock="$(mock_create)" + virsh() { + if [[ "$*" == "suspend foo" ]]; then + $virsh_mock "$@" + return $? + fi + return 1 + } + export -f virsh + export virsh_mock + + # Run the test + run in_bash pause_all_domains "${domains[@]}" + assert_success + [[ "$(mock_get_call_num ${virsh_mock})" -eq 1 ]] +} + +@test "resume_all_domains: nominal case" { + # Mock the underlying tools + local domains=( "foo" "bar" ) + declare -A domain_params_cache=( ["foo/state"]="paused" ["bar/state"]="shut off" ) + virsh_mock="$(mock_create)" + virsh() { + if [[ "$*" == "resume foo" ]] || [[ "$*" == "start bar" ]]; then + $virsh_mock "$@" + return $? + fi + return 1 + } + export -f virsh + export virsh_mock + + # Run the test + run in_bash resume_all_domains "${domains[@]}" + assert_success + [[ "$(mock_get_call_num ${virsh_mock})" -eq 2 ]] +} + +@test "domain_checks: nominal case" { + # Mock the underlying tools + domain_exists() { + if [[ "$*" == "foo" ]] || [[ "$*" == "bar" ]]; then + return 0 + fi + return 1 + } + domain_state() { + if [[ "$*" == "foo" ]]; then + echo "running" + return 0 + elif [[ "$*" == "bar" ]]; then + echo "shut off" + return 0 + fi + return 1 + } + get_zfs_datasets_from_domain() { + if [[ "$*" == "foo" ]]; then + echo "data/domains/foo" + return 0 + elif [[ "$*" == "bar" ]]; then + echo "data/domains/bar" + return 0 + fi + return 1 + } + get_zfs_zvols_from_domain() { + if [[ "$*" == "foo" ]]; then + return 0 + elif [[ "$*" == "bar" ]]; then + return 0 + fi + return 1 + } + get_zfs_snapshots_from_dataset() { + if [[ "$*" == "data/domains/foo" ]]; then + echo "backup1" + return 0 + elif [[ "$*" == "data/domains/bar" ]]; then + echo "backup1" + return 0 + fi + return 1 + } + get_zfs_dataset_mountpoint() { + if [[ "$*" == "data/domains/foo" ]]; then + echo "/var/lib/libvirt/images/foo" + return 0 + elif [[ "$*" == "data/domains/bar" ]]; then + echo "/var/lib/libvirt/images/bar" + return 0 + fi + return 1 + } + export -f domain_exists domain_state get_zfs_datasets_from_domain get_zfs_zvols_from_domain get_zfs_snapshots_from_dataset get_zfs_dataset_mountpoint + + # Run the test + run in_bash domain_checks snapshot foo backup2 + assert_success + run in_bash domain_checks revert bar backup1 + assert_success +} + +@test "list_snapshots: nominal case" { + # Mock the underlying tools + get_zfs_datasets_from_domain() { + if [[ "$*" == "foo" ]]; then + echo "data/domains/foo" + return 0 + fi + return 1 + } + get_zfs_snapshots_from_dataset() { + if [[ "$*" == "data/domains/foo" ]]; then + echo "snapshot1 +snapshot2" + return 0 + fi + return 1 + } + export -f get_zfs_datasets_from_domain get_zfs_snapshots_from_dataset + + # Run the test + run in_bash list_snapshots foo + assert_success + assert_output "Snapshots for domain 'foo': + - snapshot1 + - snapshot2" +} + +@test "preflight_checks: nominal case" { + # Mock the underlying tools + domain_checks() { + if [[ "$*" == "snapshot foo backup2" ]]; then + return 0 + fi + return 1 + } + export -f domain_checks + + # Run the test + run in_bash preflight_checks snapshot backup2 foo + assert_success +} + +@test "take_snapshots: batch=0, live=0" { + # Mock the underlying tools + take_crash_consistent_snapshot() { + regex="^(foo|bar) backup$" + if [[ "$*" =~ $regex ]]; then + return 0 + fi + return 1 + } + pause_all_domains() { return 1; } + take_live_snapshot() { return 1; } + restore_domain() { return 1; } + resume_all_domains() { return 1; } + export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains + + declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) + + # Run the test + domains=( "foo" "bar" ) + snapshot_name="backup" + batch=0 + live=0 + run in_bash take_snapshots + assert_success + + # Add a non-existing domain to the list + domains+=( "baz" ) + run in_bash take_snapshots + assert_failure +} + +@test "take_snapshots: batch=1, live=0" { + # Mock the underlying tools + take_crash_consistent_snapshot() { + regex="^(foo|bar) backup$" + if [[ "$*" =~ $regex ]]; then + return 0 + fi + return 1 + } + pause_all_domains() { + if [[ "$*" == "foo bar" ]]; then + return 0 + fi + return 1 + } + take_live_snapshot() { return 1; } + restore_domain() { return 1; } + resume_all_domains() { + if [[ "$*" == "foo bar" ]]; then + return 0 + fi + return 1 + + } + export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains + + declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) + + # Run the test + domains=( "foo" "bar" ) + snapshot_name="backup" + batch=1 + live=0 + run in_bash take_snapshots + assert_success + + # Add a non-existing domain to the list + domains+=( "baz" ) + run in_bash take_snapshots + assert_failure +} + +@test "take_snapshots: batch=0, live=1" { + # Mock the underlying tools + take_crash_consistent_snapshot() { + if [[ "$*" == "bar backup" ]]; then + return 0 + fi + return 1 + } + pause_all_domains() { return 1; } + take_live_snapshot() { + if [[ "$*" == "foo backup" ]]; then + return 0 + fi + return 1 + } + restore_domain() { + if [[ "$*" == "foo" ]]; then + return 0 + fi + return 1 + } + resume_all_domains() { return 1; } + export -f take_crash_consistent_snapshot pause_all_domains take_live_snapshot restore_domain resume_all_domains + + declare -A domain_params_cache=( ["foo/state"]="running" ["bar/state"]="shut off" ) + + # Run the test + domains=( "foo" "bar" ) + snapshot_name="backup" + batch=0 + live=1 + run in_bash take_snapshots + assert_success + + # Add a non-existing domain to the list + domains+=( "baz" ) + run in_bash take_snapshots + assert_failure +} + +@test "revert_snapshots: batch=0" { + # Mock the underlying tools + revert_snapshot() { + regex="^(foo|bar) backup$" + if [[ "$*" =~ $regex ]]; then + return 0 + fi + return 1 + } + restore_domain() { + regex="^(foo|bar)$" + if [[ "$*" =~ $regex ]]; then + return 0 + fi + return 1 + } + resume_all_domains() { return 1; } + export -f revert_snapshot restore_domain resume_all_domains + + # Run the test + domains=( "foo" "bar" ) + snapshot_name="backup" + batch=0 + run in_bash revert_snapshots + assert_success + + # Add a non-existing domain to the list + domains+=( "baz" ) + run in_bash revert_snapshots + assert_failure +} + +@test "revert_snapshots: batch=1" { + # Mock the underlying tools + revert_snapshot() { + regex="^(foo|bar) backup$" + if [[ "$*" =~ $regex ]]; then + return 0 + fi + return 1 + } + restore_domain() { + regex="^(foo|bar)$" + if [[ "$*" =~ $regex ]]; then + return 0 + fi + return 1 + } + resume_all_domains() { + if [[ "$*" == "foo bar" ]]; then + return 0 + fi + return 1 + } + export -f revert_snapshot restore_domain resume_all_domains + + # Run the test + domains=( "foo" "bar" ) + snapshot_name="backup" + batch=1 + run in_bash revert_snapshots + assert_success + + # Add a non-existing domain to the list + domains+=( "baz" ) + run in_bash revert_snapshots + assert_failure +} + diff --git a/test/unit/usage.bats b/test/unit/usage.bats new file mode 100644 index 0000000..14baf4f --- /dev/null +++ b/test/unit/usage.bats @@ -0,0 +1,69 @@ +#!/usr/bin/env bats + +bats_require_minimum_version 1.5.0 + +setup() { + bats_load_library 'bats-support' + bats_load_library 'bats-assert' + + set -Eeuo pipefail + source "${BATS_TEST_DIRNAME}/../../src/lib/core.sh" + + function call_parse_args () { + init_global_variables + parse_args "$@" + ret=$? + declare -p action batch live verbose domains snapshot_name + return $ret + } +} + +@test "call_parse_args: show help and exit" { + run call_parse_args -h + assert_success + assert_output --partial "Usage:" +} + +@test "call_parse_args: no action provided" { + run call_parse_args + assert_failure + assert_output --partial "Unsupported action" +} + +@test "call_parse_args: list snapshots for a single domain" { + run call_parse_args list -d foo + assert_success + assert_output --partial 'action="list"' + assert_output --partial 'domains=([0]="foo")' +} + +@test "call_parse_args: take a snapshot for two domains in batch mode" { + run call_parse_args snapshot -b -d foo -d bar -s backup1 -l + assert_success + assert_output --partial 'action="snapshot"' + assert_output --partial 'batch="1"' + assert_output --partial 'domains=([0]="foo" [1]="bar")' + assert_output --partial 'snapshot_name="backup1"' + assert_output --partial 'live="1"' +} + +@test "call_parse_args: take a crash-consistent snapshot for two domains" { + run call_parse_args snapshot -d foo -d bar -s backup2 + assert_success + assert_output --partial 'action="snapshot"' + assert_output --partial 'batch="0"' + assert_output --partial 'domains=([0]="foo" [1]="bar")' + assert_output --partial 'snapshot_name="backup2"' + assert_output --partial 'live="0"' +} + +@test "call_parse_args: revert snapshot for a domain" { + run call_parse_args revert -d foo -s backup2 + assert_success + assert_output --partial 'action="revert"' + assert_output --partial 'batch="0"' + assert_output --partial 'domains=([0]="foo")' + assert_output --partial 'snapshot_name="backup2"' + assert_output --partial 'live="0"' +} +