-
Notifications
You must be signed in to change notification settings - Fork 0
/
tmlib.sh
265 lines (224 loc) · 7.23 KB
/
tmlib.sh
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# tmlib.sh
# Utilities useful for writing tmtest testfiles.
# This file is covered by the MIT License.
# DO NOT EDIT THIS FILE! Edit /etc/tmtest.conf or ~/.tmtestrc instead.
# This file is replaced when you reinstall tmtest and your changes
# will be lost!
# TODO: is there any way to get rid of MKFILE_EMPTY? Can't MKFILE notice
# if read would block and, if so, just create an empty file.?
# tmlib functions:
#
# ASSERT: Stop a test if a condition fails
# TRAP: Execute a command when an exception happens
# ATEXIT: Ensure a command runs even if the test fails (usually to clean up).
# MKFILE_EMPTY: create an empty temporary file.
# MKFILE: Creates a temporary file with the given contents.
# MKDIR: create a temporary directory
# INDENT: indent some output.
# REPLACE: replaces literal text with other literal text (no regexes).
#
# ASSERT
#
# If you include this file from either a config file or your
# test file (". assert.sh" or "source assert.sh"),
# you can then use asserts in your test scripts.
# usage:
# . assert.sh
# assert 42 -eq $((0x2A)) # true, so test continues as normal
# assert 1 -eq 2 # false, so the test is aborted.
ASSERT ()
{
if [ ! $* ]; then
msg=''
if [ ${BASH_VERSINFO[0]} -ge 3 ]; then
# bash2 doesn't provide BASH_SOURCE or BASH_LINENO
msg=" on ${BASH_SOURCE[1]} line ${BASH_LINENO[0]}"
fi
ABORT assertion failed$msg: \"$*\"
fi
}
#
# TRAP
#
# This function makes trap behave more like atexit(3), where you can
# install multiple commands to execute for the same condition.
# I'm surprised that Bash doesn't do this by default.
#
# NOTE: you must not mix use of TRAP and trap on the same conndition.
# The builtin trap will remove TRAP's condition, and all the commands
# installed using TRAP will not be run.
#
# Call this routine exactly like the trap builtin: "TRAP cmd cond"
#
# Example:
#
# TRAP "echo debug!" DEBUG
#
TRAP ()
{
# install the trap if this is the first time TRAP is called for
# the given condition. (Is there any way to get rid of "local var"??)
local var=TRAP_$2
if [ ! -n "${!var}" ]; then
trap "eval \"\$TRAP_$2\"" $2
fi
# This just adds $1 to the front of the string given by TRAP_$2.
# In Perl: $TRAP{$2} = $1.($TRAP{$2} ? "; " : "").$TRAP{$2}
eval TRAP_$2=\"$1'${'TRAP_$2':+; }$'TRAP_$2\"
}
#
# ATEXIT
#
# This behaves just like atexit(3) call. Supply a command to be executed
# when the shell exits. Commands are executed in the reverse order that
# they are supplied.
#
# Example: (will produce "BA" on stdout when the test ends)
#
# ATEXIT echo A
# ATEXIT echo -n B
ATEXIT ()
{
TRAP "$*" EXIT
}
#
# MKFILE
#
# Creates a file and assigns the new filename to the given variable.
# Fills in the new file with the supplied data. Ensures that it is
# deleted when the test ends.
#
# argument 1: varname, the name of the variable that will contain the new filename.
# argument 2: filename, (optional) the name/fullpath to give the file.
#
# You need to be aware that if you supply an easily predictable filename
# (such as a PID), you are exposing your users to symlink attacks. You
# should never supply a filename unless you know EXACTLY what you are doing.
#
# Examples:
#
# create a new file with a random name in $TMPDIR or /tmp:
#
# MKFILE fn <<-EOL
# Initial file contents.
# EOL
# cat "$fn" <-- prints "Initial file contents."
#
# create a new empty file with the given name (open to symlink attack,
# DO NOT USE UNLESS YOU ARE SURE WHAT YOU ARE DOING).
#
# MKFILE ttf /tmp/$mydir/tt1 < /dev/null
#
MKFILE ()
{
local name=${2-`mktemp -t tmtest.XXXXXX || ABORT MKFILE: could not mktemp`}
eval "$1='$name'"
cat > "$name"
ATEXIT "rm '$name'"
}
#
# MKFILE_EMPTY
# TODO: this call is deprecated and will go away.
#
# I can't figure out how to get bash to bail instead of blocking.
# Therefore, if you just want to create an empty file, you either
# call MKFILE piped from /dev/null or just call MKFILE_EMPTY.
#
MKFILE_EMPTY ()
{
MKFILE "$*" < /dev/null
}
TOUCH ()
{
while [ "$1" != "" ]; do
touch $1
ATEXIT "rm '$1'"
shift
done
}
#
# MKDIR
#
# Like MKFILE, but creates a directory instead of a file. If you
# supply a directory name, and that directory already exists, then
# MKDIR ensures it is deleted when the script ends. The directory
# will not be deleted if it still contains any files.
#
# argument 1: varname, the name of the variable that will contain the new directory name.
# argument 2: dirname, (optional) the name/fullpath to give the directory.
#
# NOTE: unless you really know what you are doing, specifying argument2
# is a major security risk. Always use the single argument version.
# The one exception is if you're creating a directory inside another
# directory that was created with the single arg.
#
# Examples:
#
# create a new directory with a random name in $TMPDIR or /tmp:
#
# MKDIR dn
# cd "$dn"
#
# TODO: should emulate mkdir -p too. Right now tmtest forces you to
# call MKDIR for each dir you want to create. Too wordy.
#
MKDIR ()
{
local name=${2}
if [ -z "$name" ]; then
name=`mktemp -d -t tmtest.XXXXXX || ABORT MKDIR: could not mktemp`
else
[ -d $name ] || mkdir --mode 0700 $name || ABORT "MKDIR: could not 'mkdir \"$name\"'"
fi
eval "$1='$name'"
ATEXIT "rmdir '$name'"
}
#
# INDENT
#
# Indents the output the given number of spaces.
# Note that this only works with stdout! You'll have to combine
# the stdout and stderr streams if you want to indent stderr.
#
# By default this script indents each line with four spaces.
# Pass an argument to tell this function what to put before
# each line.
#
# Examples:
#
# echo hi | INDENT "\t" # indents stdout with a tab char
# cat /tmp 2>&1 | INDENT # indents both stdout and stderr
# exec > >(INDENT) # indents all further script output
#
INDENT ()
{
# sed appears more binary transparent than bash's builtins so I'm
# using it instead of builtin read. It might even be faster.
sed -e "s/^/${1- }/"
# even though it would probably be faster to do it with the
# Bash built-in, the following mucks things up. Bash is sloppy.
# while read LINE; do
# echo $'\t'"$LINE"
# done
}
#
# REPLACE
#
# Replaces all occurrences of the first argument with the second argument.
# Takes any number of arguments:
# REPLACE abc ABC def DEF ghi GHI
# converts the first nine characters of the alphabet to upper case
# All non-control characters are safe: quotes, slashes, etc.
# Use sed if you want to replace with regexes.
# NOTE: replace does not work if a newline is embedded in either argument.
#
# Three layers of escaping! (bash, perlvar, perlre) This is insane.
# I wish sed or awk would work with raw strings instead of regexes.
# Why isn't a replace utility a part of Gnu coreutils?
#
REPLACE()
{
# unfortunately bash can't handle this substitution itself because it
# must work on ' and \ simultaneously. Send it to perl for processing.
( while [ "$1" != "" ]; do echo "$1"; shift; done; echo; cat) | perl -e "my %ops; while(<>) { chomp; last if \$_ eq ''; \$_ = quotemeta(\$_); \$ops{\$_} = <>; chomp(\$ops{\$_}); warn 'odd number of arguments to REPLACE', last if \$ops{\$_} eq ''; } while(<>) { for my \$k (keys %ops) { s/\$k/\$ops{\$k}/g } print or die \"REPLACE: Could not print: \$!\\\\n\"; }"
}