Repository navigation
Expand file tree
/
Copy pathinstall_exe_test.sh
More file actions
160 lines (147 loc) · 6.06 KB
/
Copy pathinstall_exe_test.sh
File metadata and controls
160 lines (147 loc) · 6.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# shellcheck disable=SC1091
. ./assert.sh
. ./is_command.sh
. ./echoerr.sh
. ./log.sh
. ./mktmpdir.sh
. ./uname_os.sh
. ./install_exe.sh
test_copies_and_makes_executable() {
d=$(mktmpdir)
echo '#!/bin/sh' >"$d/src"
assertTrue "install_exe '$d/src' '$d/dst'" "install_exe: succeeds"
assertTrue "[ -x '$d/dst' ]" "install_exe: destination is executable"
assertEquals "$(cat "$d/src")" "$(cat "$d/dst")" "install_exe: contents copied"
rm -rf "$d"
}
# the source may be non-executable; the destination must still end up 0755
test_sets_mode_regardless_of_source() {
d=$(mktmpdir)
echo 'x' >"$d/src"
chmod 0600 "$d/src"
install_exe "$d/src" "$d/dst" >/dev/null 2>&1
# On Windows the mode bits are an emulation over NTFS ACLs and chmod does
# not reliably change them -- the same reason mktmpdir_test.sh does not
# assert 0700 there. It does not matter in practice: executability on
# Windows comes from the .exe suffix, which execute() appends, not from a
# mode bit. So assert what is meaningful on each platform.
case "$(uname_os)" in
windows)
assert_skip "install_exe: mode bits are emulated on Windows"
assertTrue "[ -f '$d/dst' ] && [ -r '$d/dst' ]" "install_exe: destination exists and is readable"
;;
*)
# ls -l is the portable way to read a mode; these paths are ones we just
# created, so the SC2012 filename concerns do not apply
# shellcheck disable=SC2012
mode=$(ls -l "$d/dst" | cut -c1-10)
assertEquals "-rwxr-xr-x" "$mode" "install_exe: destination is 0755 even from a 0600 source"
;;
esac
rm -rf "$d"
}
# overwriting a running binary fails with ETXTBSY on some systems, so the
# destination is unlinked first
test_overwrites_existing() {
d=$(mktmpdir)
printf 'old\n' >"$d/src"
install_exe "$d/src" "$d/dst" >/dev/null 2>&1
# A hard link to the destination distinguishes the two implementations:
# if install_exe unlinks first, the link keeps the OLD contents; if it
# truncates in place, the link would show the new ones. That is the actual
# consequence of unlinking, and unlike inode identity it is portable --
# Linux happily reuses a just-freed inode number, so comparing inodes
# reported "not unlinked" on ext4 while passing on APFS.
ln "$d/dst" "$d/hardlink"
printf 'new\n' >"$d/src"
assertTrue "install_exe '$d/src' '$d/dst'" "install_exe: overwrites an existing destination"
assertEquals "new" "$(cat "$d/dst")" "install_exe: destination has the new contents"
assertEquals "old" "$(cat "$d/hardlink")" "install_exe: destination is unlinked, not truncated in place"
rm -rf "$d"
}
# replacing a binary while a copy of it is executing
test_overwrites_while_running() {
# needs to actually execute the copied file, which requires a real x bit
if [ "$(uname_os)" = windows ]; then
assert_skip "install_exe: cannot exec a shell script without a real x bit on Windows"
return 0
fi
d=$(mktmpdir)
printf '#!/bin/sh\nsleep 2\n' >"$d/src"
install_exe "$d/src" "$d/dst" >/dev/null 2>&1
"$d/dst" &
_pid=$!
printf '#!/bin/sh\necho replaced\n' >"$d/src"
assertTrue "install_exe '$d/src' '$d/dst'" "install_exe: replaces a binary that is executing"
kill "$_pid" 2>/dev/null
wait "$_pid" 2>/dev/null
rm -rf "$d"
}
test_missing_source_fails() {
d=$(mktmpdir)
assertFalse "install_exe '$d/nope' '$d/dst' 2>/dev/null" "install_exe: missing source fails"
assertFalse "[ -e '$d/dst' ]" "install_exe: nothing created when the source is missing"
# cp would fail on its own; the point of the explicit check is a message
# that names the function and the file
msg=$(install_exe "$d/nope" "$d/dst" 2>&1 >/dev/null)
case "$msg" in
*install_exe*"$d/nope"*) assertTrue "true" "install_exe: names the function and the missing source" ;;
*) assertTrue "false" "install_exe: unhelpful message [$msg]" ;;
esac
rm -rf "$d"
}
test_bad_args_fail() {
assertFalse "install_exe" "install_exe: no arguments fails"
d=$(mktmpdir)
echo x >"$d/src"
assertFalse "install_exe '$d/src'" "install_exe: a missing destination fails"
rm -rf "$d"
}
test_unwritable_destination_fails() {
d=$(mktmpdir)
echo x >"$d/src"
mkdir "$d/ro"
chmod 0500 "$d/ro"
# root ignores the permission bits, so only assert where it is meaningful.
#
# The guard PROBES the capability rather than asking `test -w`, because the
# two disagree. NetBSD's test(1) deliberately does not use access(2): it
# stats the file and reads st_mode directly, and for root it treats "any of
# the three write bits set" as writable (bin/test/test.c, test_access).
# Mode 0500 has no write bit at all, so `[ -w ]` says no while root writes
# happily -- the assertion then ran as root and failed. Linux, macOS and
# FreeBSD use access()/eaccess(), where root gets a plain yes, which is why
# every other leg skipped correctly and only NetBSD went red.
#
# touch is an external POSIX utility, so a redirection failure here cannot
# take the shell down the way a redirect onto the special builtin `:` can.
if touch "$d/ro/probe" 2>/dev/null; then
rm -f "$d/ro/probe"
assert_skip "install_exe: this process can write to a 0500 directory (root?); check is meaningless"
else
assertFalse "install_exe '$d/src' '$d/ro/dst'" "install_exe: unwritable destination fails"
fi
chmod 0700 "$d/ro"
rm -rf "$d"
}
# install(1) must not be required: it is not POSIX and Solaris/illumos ship an
# incompatible SVR4 version
test_does_not_use_install_1() {
d=$(mktmpdir)
mkdir "$d/fakebin"
printf '#!/bin/sh\necho "install: invalid usage" >&2\nexit 1\n' >"$d/fakebin/install"
chmod 0755 "$d/fakebin/install"
echo '#!/bin/sh' >"$d/src"
got=$(PATH="$d/fakebin:$PATH" sh -c ". ./echoerr.sh; . ./log.sh; . ./install_exe.sh
install_exe '$d/src' '$d/dst' >/dev/null 2>&1; echo \$?")
assertEquals "0" "$got" "install_exe: works with a broken SVR4-style install(1) on PATH"
rm -rf "$d"
}
test_copies_and_makes_executable
test_sets_mode_regardless_of_source
test_overwrites_existing
test_overwrites_while_running
test_missing_source_fails
test_bad_args_fail
test_unwritable_destination_fails
test_does_not_use_install_1