-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathtests.sh
executable file
·472 lines (393 loc) · 12.9 KB
/
tests.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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
#!/bin/bash
# REPOSITORY: https://github.com/norech/42sh-tests
## Available commands:
## expect_stdout_match <command> : Command will be executed on both mysh
## and tcsh and stdout must match
## expect_stderr_match <command> : Command will be executed on both mysh
## and tcsh and stderr must match
## expect_env_match <command> : Command will be executed on both mysh
## and tcsh and environment variables must match
## expect_pwd_match <command> : Command will be executed on both mysh
## and tcsh and their PWD environment variables
## must match
## expect_stdout_equals <command> : Command will be executed on both mysh
## and tcsh and stdout must match
## expect_stdout_equals <command> <value> : Command will be executed on mysh
## and its stdout must be equal to value
## expect_stderr_equals <command> <value> : Command will be executed on mysh
## and its stderr must be equal to value
## expect_exit_code <command> <code> : Command will be executed on mysh
## and its exit code must be equal to code
## expect_signal_message_match <sig> : Signal will be sent to child process
## and stderr must be equal to the tcsh one
## Each command can be prefixed by:
## WITH_ENV="KEY=value KEY2=value2" : Specify which environment variables
## must be passed to mysh using `env` command.
## Not recommended with *_match commands.
## WITH_ENV="-i" is equivalent to `env -i ./mysh`.
## WITHOUT_COREDUMP=1 : When value is 1, disable core dump.
tests()
{
# EXECUTE COMMANDS
expect_stdout_match "ls"
expect_stdout_match "/bin/ls" # full path
expect_stdout_match "/bin/ls -a" # full path with args
expect_stdout_match "ls -a"
expect_stderr_match "egegrgrgegergre" # not existing binary
expect_stderr_match "uyiuoijuuyyiy" # not existing binary 2
WITH_ENV="PATH=" \
expect_stderr_equals "ls" "ls: Command not found." # no PATH to be found
# EXECUTE COMMANDS - relative paths
if [ -t 0 ]; then # if is a tty, avoids recursion problems
expect_stdout_match "./$(basename "$0") --helloworld" # ./tests.sh --helloworld
expect_stdout_match "../$(basename $PWD)/$(basename "$0") --helloworld" # ../parentdir/tests.sh --helloworld
fi
# FORMATTING & SPACING
expect_stdout_match " ls -a"
expect_stdout_match " ls -a"
expect_stdout_match $' ls\t\t -a'
expect_stdout_match $' ls\t\t -a\t'
expect_stdout_match $'ls -a\t'
expect_stdout_match $'ls \t-a\t'
expect_stdout_match $'ls\t-a'
expect_stdout_match $'\tls -a\t'
# SETENV
expect_env_match "setenv A b"
expect_env_match "setenv _A b"
expect_env_match "setenv AB0 b"
expect_env_match "setenv A_B0 b"
expect_env_match "setenv A_C b"
expect_env_match "setenv A" # variables can be set with one argument
expect_env_match "setenv"
expect_stderr_match "setenv -A b" # variables must start with a letter
expect_stderr_match "setenv 0A b" # variables must start with a letter
expect_stderr_match "setenv A- b" # variables must be alphanumeric
expect_stderr_match "setenv A b c" # setenv must contain 1 or 2 arguments
# ENV
expect_env_match "env"
WITH_ENV="-i" \
expect_exit_code "env" 0
# EXIT
expect_exit_code "" 0 # no command executed
expect_exit_code "exit" 0
expect_exit_code "exit 24" 24
expect_exit_code "exit 18" 18
expect_stderr_match "exit a" # Expression syntax
expect_stderr_match "exit 2a" # Badly formed number.
expect_stderr_match "exit a b" # Expression syntax
# CD
expect_stderr_match "cd -" # previous env was not set
expect_stderr_match "cd /root" # no permissions to access folder error
expect_stderr_match "cd /htyg/grrggfghfgdhgfghg" # folder not found error
expect_pwd_match "cd ~"
expect_pwd_match "cd /"
expect_pwd_match $'cd /\ncd -' # change path then go back to last path => cd -
expect_pwd_match "unsetenv PWD"
expect_pwd_match "setenv PWD /home"
# SIGNALS
for i in SIGSEGV SIGFPE SIGBUS SIGABRT
do
expect_signal_message_match "$i"
WITHOUT_COREDUMP=1 expect_signal_message_match "$i"
done
}
#------------------------------------------------------------------------------------
# Here be dragons
#------------------------------------------------------------------------------------
if [[ $1 == "--helloworld" ]]; then
echo "Hello world!"
exit 42
fi
if ! which tcsh >/dev/null; then
cat <<EOF
tcsh was not found on your system.
tcsh is required to be able to test your shell, as it is the reference shell to which your shell is compared
Please install tcsh (On Fedora, you can do this via `dnf install tcsh`)
EOF
exit 84
fi
if [[ ! -f "./mysh" ]]; then
cat <<EOF
./mysh does not exist.
It is required that a mysh executable be in the same directory as this script in order to test it (possible means of satisfying this requirement include moving this script to a directory containing a mysh executable).
EOF
exit 84
fi
# do not load any starting script
# fixes `builin: not found` errors with proprietary drivers
alias tcsh="tcsh -f"
PASSED=""
FAILED=""
pass()
{
echo "Passed"
PASSED+=1
}
fail()
{
echo "Failed: $@"
FAILED+=1
}
expect_exit_code()
{
printf "\n\n"
echo "$1"
echo "-----"
echo "Expectation: Exit code must be $2"
echo "---"
EXIT1=$2
echo "$1" | env $WITH_ENV ./mysh 2>&1
EXIT2=$?
if [[ $EXIT1 != $EXIT2 ]]; then
fail "Exit code are different (expected $EXIT1, got $EXIT2)."
return
fi
pass
}
expect_signal_message_match()
{
local without_core_dump="$WITHOUT_COREDUMP"
local signal_id="$(get_signal_id $1)"
if [[ -z $without_core_dump ]]; then
without_core_dump=0
fi
printf "\n\n"
echo "SIGNAL: $1"
if [[ "$without_core_dump" == "1" ]]; then
echo "Without core dump"
fi
echo "-----"
echo "Expectation: When executed program send a $1 signal ($signal_id), mysh stderr must match with tcsh"
echo "---"
if [[ ! -f /tmp/__minishell_segv ]]; then
build_signal_sender
fi
TCSH_OUTPUT=$(echo "/tmp/__minishell_segv $without_core_dump $signal_id" | tcsh 2>&1 1>/dev/null | clean_tcsh_stderr)
EXIT1=0 # Marvin does not like a 139 exit code (it probably thinks we crashed), so instead, check for returning 0
MYSH_OUTPUT=$(echo "/tmp/__minishell_segv $without_core_dump $signal_id" | ./mysh 2>&1 1>/dev/null)
EXIT2=$?
DIFF=$(diff --color=always <(echo "$TCSH_OUTPUT") <(echo "$MYSH_OUTPUT"))
if [[ $DIFF != "" ]]; then
echo "< tcsh > mysh"
echo
echo "$DIFF"
fail "Output are different."
return
fi
if [[ $EXIT1 != $EXIT2 ]]; then
fail "Exit code are different (expected $EXIT1, got $EXIT2). (Note: while tcsh actually returns 139, we assume it returns 0 because Marvin doesn't like it if you return 139)"
return
fi
pass
}
expect_pwd_match()
{
printf "\n\n"
echo "$@"
echo "-----"
echo "Expectation: PWD in environment variable must match with tcsh after the command"
if [[ ! -z "$WITH_ENV" ]]; then
echo "With environment variables: $WITH_ENV"
fi
echo "---"
DIFF=$(diff --color=always <(echo "$@"$'\n'"env" | tcsh 2>&1 | grep "^PWD=") <(echo "$@"$'\n'"env" | env $WITH_ENV ./mysh 2>&1 | grep "^PWD="))
if [[ $DIFF != "" ]]; then
echo "< tcsh > mysh"
echo
echo "$DIFF"
fail "Output are different."
return
fi
echo "$@" | tcsh 2>&1
EXIT1=$?
echo "$@" | env $WITH_ENV ./mysh 2>&1
EXIT2=$?
if [[ $EXIT1 != $EXIT2 ]]; then
fail "Exit code are different (expected $EXIT1, got $EXIT2)."
return
fi
pass
}
expect_env_match()
{
SAMPLE_ENV="USER=$USER GROUP=$GROUP PWD=$PWD"
printf "\n\n"
echo "$@"
echo "-----"
echo "Expectation: Env must match with tcsh after the command"
if [[ ! -z "$WITH_ENV" ]]; then
echo "With environment variables: $WITH_ENV"
fi
echo "---"
TCSH_OUTPUT="$(echo "$@"$'\n'"env" | env -i $SAMPLE_ENV tcsh 2>&1 | clean_env | clean_tcsh_stderr)"
MYSH_OUTPUT="$(echo "$@"$'\n'"env" | env -i $SAMPLE_ENV $WITH_ENV ./mysh 2>&1 | clean_env)"
DIFF=$(diff --color=always <(echo $TCSH_OUTPUT) <(echo $MYSH_OUTPUT))
if [[ $DIFF != "" ]]; then
echo "< tcsh > mysh"
echo
echo "$DIFF"
fail "Output are different."
return
fi
echo "$@" | tcsh 2>&1 >/dev/null
EXIT1=$?
echo "$@" | env $WITH_ENV ./mysh 2>&1 >/dev/null
EXIT2=$?
if [[ $EXIT1 != $EXIT2 ]]; then
fail "Exit code are different (expected $EXIT1, got $EXIT2)."
return
fi
pass
}
expect_stdout_match()
{
printf "\n\n"
echo "$@"
echo "-----"
echo "Expectation: Command stdout must match with tcsh"
if [[ ! -z "$WITH_ENV" ]]; then
echo "With environment variables: $WITH_ENV"
fi
echo "---"
DIFF=$(diff --color=always <(echo "$@" | tcsh 2>/dev/null) <(echo "$@" | env $WITH_ENV ./mysh 2>/dev/null))
if [[ $DIFF != "" ]]; then
echo "< tcsh > mysh"
echo
echo "$DIFF"
fail "Output are different."
return
fi
echo "$@" | tcsh 2>&1 >/dev/null
EXIT1=$?
echo "$@" | env $WITH_ENV ./mysh 2>&1 >/dev/null
EXIT2=$?
if [[ $EXIT1 != $EXIT2 ]]; then
fail "Exit code are different (expected $EXIT1, got $EXIT2)."
return
fi
pass
}
expect_stdout_equals()
{
printf "\n\n"
echo "$1"
echo "-----"
echo "Expectation: Command stdout must equal '$2'"
if [[ ! -z "$WITH_ENV" ]]; then
echo "With environment variables: $WITH_ENV"
fi
echo "---"
DIFF=$(diff --color=always <(echo "$2") <(echo "$(echo "$1" | env $WITH_ENV ./mysh 2>/dev/null)"))
if [[ $DIFF != "" ]]; then
echo "< expect > mysh"
echo
echo "$DIFF"
fail "Output are different."
return
fi
pass
}
expect_stderr_match()
{
printf "\n\n"
echo "$@"
echo "-----"
echo "Expectation: Command stderr must match with tcsh"
if [[ ! -z "$WITH_ENV" ]]; then
echo "With environment variables: $WITH_ENV"
fi
echo "---"
DIFF=$(diff --color=always <(echo "$(echo "$@" | tcsh 2>&1 >/dev/null | clean_tcsh_stderr)") <(echo "$(echo "$@" | env $ENV_VAR ./mysh 2>&1 >/dev/null)"))
if [[ $DIFF != "" ]]; then
echo "< tcsh > mysh"
echo
echo "$DIFF"
fail "Output are different."
return
fi
echo "$@" | tcsh &>/dev/null
EXIT1=$?
echo "$@" | env $WITH_ENV ./mysh &>/dev/null
EXIT2=$?
if [[ $EXIT1 == 1 && $EXIT2 != 84 ]]; then
fail "Exit code are different (expected 84, got $EXIT2). (Note: while tcsh actually returns 1, we assume it returns 84 because Marvin expect a 84 code for errors)"
return
fi
if [[ $EXIT1 != 1 && $EXIT1 != $EXIT2 ]]; then
fail "Exit code are different (expected $EXIT1, got $EXIT2)."
return
fi
pass
}
expect_stderr_equals()
{
printf "\n\n"
echo "$1"
echo "-----"
echo "Expectation: Command stderr must equal '$2'"
if [[ ! -z "$WITH_ENV" ]]; then
echo "With environment variables: $WITH_ENV"
fi
echo "---"
DIFF=$(diff --color=always <(echo "$2") <(echo "$(echo "$1" | env $WITH_ENV ./mysh 2>&1 >/dev/null)"))
if [[ $DIFF != "" ]]; then
echo "< expect > mysh"
echo
echo "$DIFF"
fail "Output are different."
return
fi
pass
}
clean_tcsh_stderr()
{
grep -v -e "builtin: not found" # patch for `builin: not found` with proprietary drivers
}
clean_env()
{
grep -v -e "^SHLVL=" \
-e "^HOSTTYPE=" \
-e "^VENDOR=" \
-e "^OSTYPE=" \
-e "^MACHTYPE=" \
-e "^LOGNAME=" \
-e "^HOST=" \
-e "^GROUP=" \
-e "^_="
}
get_signal_id()
{
trap -l | sed -nr 's/.*\b([0-9]+)\) '$1'.*/\1/p'
}
build_signal_sender()
{
cat <<EOF >/tmp/__minishell_segv_code.c
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
if (argc != 3)
return (84);
prctl(PR_SET_DUMPABLE, atoi(argv[1]) == 0);
kill(getpid(), atoi(argv[2]));
while (1);
}
EOF
gcc -o /tmp/__minishell_segv /tmp/__minishell_segv_code.c
}
cleanup()
{
pkill -P $$
rm /tmp/__minishell_*
exit
}
total() {
printf "\n\nTests passed: $(echo -n $PASSED | wc -m). Tests failed: $(echo -n $FAILED | wc -m).\n"
}
trap cleanup 2
tests
total
cleanup