1#!/bin/ksh
2#
3# This file and its contents are supplied under the terms of the
4# Common Development and Distribution License ("CDDL"), version 1.0.
5# You may only use this file in accordance with the terms of version
6# 1.0 of the CDDL.
7#
8# A full copy of the text of the CDDL should have accompanied this
9# source.  A copy of the CDDL is also available via the Internet at
10# http://www.illumos.org/license/CDDL.
11#
12
13#
14# Copyright (c) 2016, 2017 by Delphix. All rights reserved.
15#
16
17. $STF_SUITE/include/libtest.shlib
18
19ZCP_ROOT=$STF_SUITE/tests/functional/channel_program
20
21#
22# Note: In case of failure (log_fail) in this function
23# we delete the file passed as <input file> so the
24# test suite doesn't leak temp files on failures. So it
25# is expected that <input file> is a temp file and not
26# an installed file.
27#
28# <exitcode> <expected error string> <input file> <zfs program args>
29# e.g. log_program 0 "" tmp.7a12V $POOL foo.zcp arg1 arg2
30function log_program
31{
32	typeset expectexit=$1
33	shift
34	typeset expecterror=$1
35	shift
36	typeset tmpin=$1
37	shift
38	typeset cmdargs=$@ tmpout=$(mktemp) tmperr=$(mktemp)
39
40	# Expected output/error filename is the same as the .zcp name
41	typeset basename
42	if [[ $2 != "-" ]]; then
43		basename=${2%.*}
44	fi
45
46	log_note "running: zfs program $cmdargs:"
47
48	zfs program $cmdargs >$tmpout 2>$tmperr
49	typeset ret=$?
50
51	log_note "input:\n$(cat $tmpin)"
52	log_note "output:\n$(cat $tmpout)"
53	log_note "error:\n$(cat $tmperr)"
54
55	#
56	# Verify correct return value
57	#
58	if [[ $ret -ne $expectexit ]]; then
59		rm $tmpout $tmperr $tmpin
60		log_fail "return mismatch: expected $expectexit, got $ret"
61	fi
62
63	#
64	# Check the output or reported error for successful or error returns,
65	# respectively.
66	#
67	if [[ -f "$basename.out" ]] && [[ $expectexit -eq 0 ]]; then
68
69		outdiff=$(diff "$basename.out" "$tmpout")
70		if [[ $? -ne 0 ]]; then
71			output=$(cat $tmpout)
72			rm $tmpout $tmperr $tmpin
73			log_fail "Output mismatch. Expected:\n" \
74				"$(cat $basename.out)\nBut got:\n$output\n" \
75				"Diff:\n$outdiff"
76		fi
77
78	elif [[ -f "$basename.err" ]] && [[ $expectexit -ne 0 ]]; then
79
80		outdiff=$(diff "$basename.err" "$tmperr")
81		if [[ $? -ne 0 ]]; then
82			outputerror=$(cat $tmperr)
83			rm $tmpout $tmperr $tmpin
84			log_fail "Error mismatch. Expected:\n" \
85				"$(cat $basename.err)\nBut got:\n$outputerror\n" \
86				"Diff:\n$outdiff"
87		fi
88
89	elif [[ -n $expecterror ]] && [[ $expectexit -ne 0 ]]; then
90
91		grep -q "$expecterror" $tmperr
92		if [[ $? -ne 0 ]]; then
93			outputerror=$(cat $tmperr)
94			rm $tmpout $tmperr $tmpin
95			log_fail "Error mismatch. Expected to contain:\n" \
96				"$expecterror\nBut got:\n$outputerror\n"
97		fi
98
99	elif [[ $expectexit -ne 0 ]]; then
100		#
101		# If there's no expected output, error reporting is allowed to
102		# vary, but ensure that we didn't fail silently.
103		#
104		if [[ -z "$(cat $tmperr)" ]]; then
105			rm $tmpout $tmperr $tmpin
106			log_fail "error with no stderr output"
107		fi
108	fi
109
110	#
111	# Clean up all temp files except $tmpin which is
112	# reused for the second invocation of log_program.
113	#
114	rm $tmpout $tmperr
115}
116
117#
118# Even though the command's arguments are passed correctly
119# to the log_must_program family of wrappers the majority
120# of the time, zcp scripts passed as HERE documents can
121# make things trickier (see comment within the fucntion
122# below) in the ordering of the commands arguments and how
123# they are passed. Thus, with this function we reconstruct
124# them to ensure that they are passed properly.
125#
126function log_program_construct_args
127{
128	typeset tmpin=$1
129	shift
130
131	args=""
132	i=0
133	while getopts "nt:m:" opt; do
134		case $opt in
135			t) args=$args" -t $OPTARG"; i=$(($i + 2)) ;;
136			m) args=$args" -m $OPTARG"; i=$(($i + 2)) ;;
137			n) args=$args" -n"; i=$(($i + 1)) ;;
138		esac
139	done
140	shift $i
141
142	pool=$1
143	shift
144
145	#
146	# Catch HERE document if it exists and save it within our
147	# temp file. The reason we do this is that since the
148	# log_must_program wrapper calls zfs-program twice (once
149	# for open context and once for syncing) the HERE doc
150	# is consumed in the first invocation and the second one
151	# does not have a program to run.
152	#
153	cat > $tmpin
154
155	#
156	# If $tmpin has contents it means that we consumed a HERE
157	# doc and $1 currently holds "-" (a dash). If there is no
158	# HERE doc and $tmpin is empty, then we copy the contents
159	# of the original channel program to $tmpin.
160	#
161	[[ -s $tmpin ]] || cp $1 $tmpin
162	shift
163
164	lua_args=$@
165
166	echo "$args $pool $tmpin $lua_args"
167}
168
169#
170# Program should complete successfully
171# when run in either context.
172#
173function log_must_program
174{
175	typeset tmpin=$(mktemp)
176
177	program_args=$(log_program_construct_args $tmpin $@)
178
179	log_program 0 "" $tmpin "-n $program_args"
180	log_program 0 "" $tmpin "$program_args"
181
182	rm $tmpin
183}
184#
185# Program should error as expected in
186# the same way in both contexts.
187#
188function log_mustnot_checkerror_program
189{
190	typeset expecterror=$1
191	shift
192	typeset tmpin=$(mktemp)
193
194	program_args=$(log_program_construct_args $tmpin $@)
195
196	log_program 1 "$expecterror" $tmpin "-n $program_args"
197	log_program 1 "$expecterror" $tmpin "$program_args"
198
199	rm $tmpin
200}
201
202#
203# Program should fail when run in either
204# context.
205#
206function log_mustnot_program
207{
208	log_mustnot_checkerror_program "" $@
209}
210
211
212#
213# Program should error as expected in
214# open context but complete successfully
215# in syncing context.
216#
217function log_mustnot_checkerror_program_open
218{
219	typeset expecterror=$1
220	shift
221	typeset tmpin=$(mktemp)
222
223	program_args=$(log_program_construct_args $tmpin $@)
224
225	log_program 1 "$expecterror" $tmpin "-n $program_args"
226	log_program 0 "" $tmpin "$program_args"
227
228	rm $tmpin
229}
230
231#
232# Program should complete successfully
233# when run in syncing context but fail
234# when attempted to run in open context.
235#
236function log_must_program_sync
237{
238	log_mustnot_checkerror_program_open "requires passing sync=TRUE" $@
239}
240