Skip to content

Commit

Permalink
lib/fileutil: Add @go.mirror_directory
Browse files Browse the repository at this point in the history
Part of #186. Prerequisite for the upcoming `@go.create_gzipped_tarball`
in the upcoming `lib/archive`.
  • Loading branch information
mbland committed Sep 9, 2017
1 parent a06e295 commit d98b335
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 0 deletions.
43 changes: 43 additions & 0 deletions lib/fileutil
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
#
# @go.copy_files_safely
# Safely copy files into a target directory, preserving relative directories
#
# @go.mirror_directory
# Mirror one local directory to another using `tar`

. "$_GO_USE_MODULES" 'diff' 'log' 'path'

Expand Down Expand Up @@ -154,6 +157,46 @@
return "$result"
}

# Mirror one local directory to another using `tar`
#
# Useful for creating a directory from which to create an archive, or for
# overwriting files unconditionally. Performs safety checks to ensure the
# source and destination directories aren't the same and that the source
# directory exists. Automatically creates the destination directory if it
# doesn't exist.
#
# Uses only `tar` features that are portable across platform variants. More
# portable than `rsync`, which isn't installed by default on some systems; and
# can be faster than `cp -a`.
#
# Arguments:
# src_dir: Original directory path
# dest_dir: Mirrored directory path
# ...: Paths relative to `src_dir` to include in the mirror
@go.mirror_directory() {
local src_dir="$1"
local dest_dir="$2"
local real_src
local real_dest

@go.realpath 'real_src' "$src_dir"
@go.realpath 'real_dest' "$dest_dir"

if [[ "$real_src" == "$real_dest" ]]; then
@go.log FATAL "Real source and destination dirs are the same:"$'\n'\
" source: $src_dir"$'\n'\
" dest: $dest_dir"$'\n'\
" real: $real_src"
elif [[ ! -d "$src_dir" ]]; then
@go.log FATAL "Source directory $src_dir doesn't exist"
elif [[ ! -d "$dest_dir" ]] && ! mkdir -p "$dest_dir"; then
@go.log FATAL "Failed to create destination directory $dest_dir"
elif ! tar -cf - -C "$src_dir" "${@:3}" | tar -xf - -C "$dest_dir" ||
[[ "${PIPESTATUS[0]}" != '0' ]]; then
@go.log FATAL "Failed to mirror files from $src_dir to $dest_dir"
fi
}

# --------------------------------
# IMPLEMENTATION - HERE BE DRAGONS
#
Expand Down
153 changes: 153 additions & 0 deletions tests/fileutil/mirror-directory.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#! /usr/bin/env bats

load ../environment

SRC_DIR="$TEST_GO_ROOTDIR/src"
DEST_DIR="$TEST_GO_ROOTDIR/dest"
TEST_FILES=

setup() {
test_filter
@go.create_test_go_script \
'. "$_GO_USE_MODULES" "fileutil"' \
'@go.mirror_directory "$@"'
mkdir -p "$SRC_DIR"
TEST_FILES=('foo' 'bar' 'baz')
}

teardown() {
@go.remove_test_go_rootdir
}

create_test_source_files() {
set "$BATS_DISABLE_SHELL_OPTIONS"
printf '%s\n' 'foo' >"$SRC_DIR/foo"
printf '%s\n' 'bar' >"$SRC_DIR/bar"
printf '%s\n' 'baz' >"$SRC_DIR/baz"
restore_bats_shell_options
}

validate_test_dest_dir() {
set "$BATS_DISABLE_SHELL_OPTIONS"
local f
local result='0'

for f in "${TEST_FILES[@]}"; do
if [[ ! -f "$DEST_DIR/$f" ]]; then
printf 'Failed to create: %s\n' "$DEST_DIR/$f" >&2
result='1'
fi
done
restore_bats_shell_options "$result"
}

@test "$SUITE: mirrors specific files from one directory to another" {
skip_if_system_missing 'tar'
create_test_source_files
mkdir -p "$DEST_DIR"

run "$TEST_GO_SCRIPT" "$SRC_DIR" "$DEST_DIR" "${TEST_FILES[@]}"
assert_success
validate_test_dest_dir
}

@test "$SUITE: creates destination directory if it doesn't exist" {
skip_if_system_missing 'tar'
create_test_source_files

run "$TEST_GO_SCRIPT" "$SRC_DIR" "$DEST_DIR" "${TEST_FILES[@]}"
assert_success
validate_test_dest_dir
}

@test "$SUITE: only copy selected files" {
skip_if_system_missing 'tar'
local ignored_file="$DEST_DIR/${TEST_FILES[1]}"

unset 'TEST_FILES[1]'
create_test_source_files

run "$TEST_GO_SCRIPT" "$SRC_DIR" "$DEST_DIR" "${TEST_FILES[@]}"
assert_success
validate_test_dest_dir

if [[ -f "$ignored_file" ]]; then
fail "$ignored_file copied to destination when it should've been ignored"
fi
}

@test "$SUITE: logs FATAL if the source directory doesn't exist" {
rmdir "$SRC_DIR"
run "$TEST_GO_SCRIPT" "$SRC_DIR" "$DEST_DIR" "${TEST_FILES[@]}"
assert_failure
assert_output_matches "FATAL.* Source directory $SRC_DIR doesn't exist"
}

@test "$SUITE: logs FATAL if the destination directory can't be created" {
create_test_source_files
stub_program_in_path 'mkdir' \
'exit 1'

run "$TEST_GO_SCRIPT" "$SRC_DIR" "$DEST_DIR" "${TEST_FILES[@]}"
restore_program_in_path 'mkdir'
assert_failure
assert_output_matches \
"FATAL.* Failed to create destination directory $DEST_DIR"
}

@test "$SUITE: logs FATAL if the input tar fails" {
stub_program_in_path 'tar' \
'if [[ "$1" == "-cf" ]]; then' \
' printf "CREATE FAILED\n" >&2' \
' exit 1' \
'fi' \

run "$TEST_GO_SCRIPT" "$SRC_DIR" "$DEST_DIR" "${TEST_FILES[@]}"
restore_program_in_path 'tar'
assert_failure
assert_line_matches '0' 'CREATE FAILED'
assert_line_matches '1' \
"FATAL.* Failed to mirror files from $SRC_DIR to $DEST_DIR"
}

@test "$SUITE: logs FATAL if the output tar fails" {
stub_program_in_path 'tar' \
'if [[ "$1" == "-xf" ]]; then' \
' printf "EXTRACT FAILED\n" >&2' \
' exit 1' \
'fi'

run "$TEST_GO_SCRIPT" "$SRC_DIR" "$DEST_DIR" "${TEST_FILES[@]}"
restore_program_in_path 'tar'
assert_failure
assert_line_matches '0' 'EXTRACT FAILED'
assert_line_matches '1' \
"FATAL.* Failed to mirror files from $SRC_DIR to $DEST_DIR"

}

@test "$SUITE: logs FATAL if the real source and dest dirs are the same" {
skip_if_system_missing 'ln'
if [[ "$OSTYPE" == 'msys' ]]; then
skip "ln doesn't work like it normally does on MSYS2"
fi

local same_dir="$TEST_GO_ROOTDIR/same-dir"

. "$_GO_USE_MODULES" 'path'
@go.realpath 'same_dir' "$same_dir"

# Remove SRC_DIR so the link isn't created inside SRC_DIR, but replaces it.
rmdir "$SRC_DIR"
ln -s "$same_dir" "$SRC_DIR"
ln -s "$same_dir" "$DEST_DIR"
ls -lR "$TEST_GO_ROOTDIR" >&2

run "$TEST_GO_SCRIPT" "$SRC_DIR" "$DEST_DIR" "${TEST_FILES[@]}"
assert_failure
assert_line_matches '0' \
'FATAL.* Real source and destination dirs are the same:' \
assert_line_matches '1' " source: $SRC_DIR"
assert_line_matches '2' " dest: $DEST_DIR"
assert_line_matches '3' " real: $same_dir"
}

0 comments on commit d98b335

Please sign in to comment.