1272343Sngie# $NetBSD: t_ps.sh,v 1.2 2014/01/16 04:16:32 mlelstv Exp $
2272343Sngie#
3272343Sngie# Copyright (c) 2007 The NetBSD Foundation, Inc.
4272343Sngie# All rights reserved.
5272343Sngie#
6272343Sngie# Redistribution and use in source and binary forms, with or without
7272343Sngie# modification, are permitted provided that the following conditions
8272343Sngie# are met:
9272343Sngie# 1. Redistributions of source code must retain the above copyright
10272343Sngie#    notice, this list of conditions and the following disclaimer.
11272343Sngie# 2. Redistributions in binary form must reproduce the above copyright
12272343Sngie#    notice, this list of conditions and the following disclaimer in the
13272343Sngie#    documentation and/or other materials provided with the distribution.
14272343Sngie#
15272343Sngie# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
16272343Sngie# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17272343Sngie# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18272343Sngie# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
19272343Sngie# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20272343Sngie# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21272343Sngie# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22272343Sngie# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23272343Sngie# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24272343Sngie# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25272343Sngie# POSSIBILITY OF SUCH DAMAGE.
26272343Sngie#
27272343Sngie
28272343Sngie# the implementation of "ps" to test
29272343Sngie: ${TEST_PS:="ps"}
30272343Sngie# tab and newline characters
31272343Sngietab="$(printf '\t')"
32272343Sngie# nl="$(printf '\n')" doesn't work
33272343Sngienl='
34272343Sngie'
35272343Sngie
36272343Sngie#
37272343Sngie# Parse the "keywords" file into a load of shell variables
38272343Sngie#
39272343Sngiesetup_keywords()
40272343Sngie{
41272343Sngie	# Set variables representing the header text
42272343Sngie	# for all normal keywords (except aliases), and
43272343Sngie	# for regular expressions to match the text in left- or
44272343Sngie	# right-justified columns.
45272343Sngie	# For example, head_text_p_cpu="%CPU" head_regexp_p_cpu=" *%CPU".
46272343Sngie	while read keyword heading flag
47272343Sngie	do
48272343Sngie		case "$keyword" in
49272343Sngie		''|\#*)	continue
50272343Sngie			;;
51272343Sngie		esac
52272343Sngie		[ x"$flag" = x"ALIAS" ] && continue
53272343Sngie		kvar="${keyword}"
54272343Sngie		case "${keyword}" in
55272343Sngie		%*)	kvar="p_${keyword#%}"
56272343Sngie			;;
57272343Sngie		esac
58272343Sngie		eval head_text_${kvar}=\'"${heading}"\'
59272343Sngie		case "${flag}" in
60272343Sngie		'')	# right justified
61272343Sngie			eval head_regexp_${kvar}=\'" *${heading}"\'
62272343Sngie			;;
63272343Sngie		LJUST)	# left justified
64272343Sngie			eval head_regexp_${kvar}=\'"${heading} *"\'
65272343Sngie			;;
66272343Sngie		*)	atf_fail "unknown flag in keywords"
67272343Sngie			;;
68272343Sngie		esac
69272343Sngie	done <"$(atf_get_srcdir)/keywords"
70272343Sngie
71272343Sngie	# Now do the aliases.
72272343Sngie	while read keyword heading flag
73272343Sngie	do
74272343Sngie		case "$keyword" in
75272343Sngie		''|\#*)	continue
76272343Sngie			;;
77272343Sngie		esac
78272343Sngie		[ x"$flag" != x"ALIAS" ] && continue
79272343Sngie		kvar="${keyword}"
80272343Sngie		avar="${heading}"
81272343Sngie		case "${keyword}" in
82272343Sngie		%*)	kvar="p_${keyword#%}"
83272343Sngie			;;
84272343Sngie		esac
85272343Sngie		case "${heading}" in
86272343Sngie		%*)	avar="p_${heading#%}"
87272343Sngie			;;
88272343Sngie		esac
89272343Sngie		eval head_text_${kvar}=\"\$head_text_${avar}\"
90272343Sngie		eval head_regexp_${kvar}=\"\$head_regexp_${avar}\"
91272343Sngie	done <"$(atf_get_srcdir)/keywords"
92272343Sngie
93272343Sngie	# default sets of keywords
94272343Sngie	default_keywords='pid tty stat time command'
95272343Sngie	j_keywords='user pid ppid pgid sess jobc state tt time command'
96272343Sngie	l_keywords='uid pid ppid cpu pri nice vsz rss wchan state tt time command'
97272343Sngie	s_keywords='uid pid ppid cpu lid nlwp pri nice vsz rss wchan lstate tt ltime command'
98272343Sngie	u_keywords='user pid %cpu %mem vsz rss tt state start time command'
99272343Sngie	v_keywords='pid state time sl re pagein vsz rss lim tsiz %cpu %mem command'
100272343Sngie}
101272343Sngie
102272343Sngie# Convert a list of keywords like "pid comm" to a regexp
103272343Sngie# like " *PID COMMAND *"
104272343Sngieheading_keywords_to_regexp()
105272343Sngie{
106272343Sngie	local keywords="$1"
107272343Sngie	local regexp
108272343Sngie	regexp="$(echo "$keywords" | \
109272343Sngie		sed -E -e 's/\%/p_/g' -e 's/(^| )/\1\$head_regexp_/g')"
110272343Sngie	eval regexp=\""${regexp}"\"
111272343Sngie	regexp="^${regexp}\$"
112272343Sngie	echo "$regexp"
113272343Sngie}
114272343Sngie
115272343Sngie#
116272343Sngie# Check that a string matches a regexp; use the specified id
117272343Sngie# in error or success messages.
118272343Sngie#
119272343Sngiecheck_regexp() {
120272343Sngie	local id="$1" string="$2" regexp="$3"
121272343Sngie	if ! expr "$string" : "$regexp" >/dev/null
122272343Sngie	then
123272343Sngie		atf_fail "${id}: expected [${regexp}], got [${string}]"
124272343Sngie		false
125272343Sngie	fi
126272343Sngie}
127272343Sngie
128272343Sngie#
129272343Sngie# Run "ps $args -p $$"; check that only one line is printed,
130272343Sngie# without a preceding header line.
131272343Sngie#
132272343Sngiecheck_no_heading_line()
133272343Sngie{
134272343Sngie	local args="$1"
135272343Sngie	local output="$(eval "${TEST_PS} $args -p $$")"
136272343Sngie	case "$output" in
137272343Sngie	*"$nl"*)
138272343Sngie		local firstline="${output%%${nl}*}"
139272343Sngie		atf_fail "check_no_heading_line [$args] got [$firstline]"
140272343Sngie		;;
141272343Sngie	*)
142272343Sngie		;;
143272343Sngie	esac
144272343Sngie}
145272343Sngie
146272343Sngie#
147272343Sngie# Run "ps $args"; check that the heading matches the expected regexp.
148272343Sngie#
149272343Sngiecheck_heading_regexp()
150272343Sngie{
151272343Sngie	args="$1"
152272343Sngie	regexp="$2"
153272343Sngie	actual="$( eval "${TEST_PS} $args" | sed -e 1q )"
154272343Sngie	check_regexp "heading [$args]" "${actual}" "${regexp}"
155272343Sngie}
156272343Sngie
157272343Sngie#
158272343Sngie# Run "ps $args"; check that the heading matches a regexp constructed
159272343Sngie# from the specified keywords.
160272343Sngie#
161272343Sngiecheck_heading_keywords()
162272343Sngie{
163272343Sngie	args="$1"
164272343Sngie	keywords="$2"
165272343Sngie	check_heading_regexp "$args" "$(heading_keywords_to_regexp "$keywords")"
166272343Sngie}
167272343Sngie
168272343Sngie#
169272343Sngie# Try several variations on "ps $flag", "ps -$flag", etc.,
170272343Sngie# and check that the heading always has the correct keywords.
171272343Sngie#
172272343Sngiecheck_heading_variations()
173272343Sngie{
174272343Sngie	flag="$1"
175272343Sngie	keywords="$2"
176272343Sngie	for args in "$flag" "-$flag" "-$flag$flag -$flag"; do
177272343Sngie		check_heading_keywords "$args" "$keywords"
178272343Sngie	done
179272343Sngie}
180272343Sngie
181272343Sngieatf_test_case default_columns
182272343Sngiedefault_columns_head()
183272343Sngie{
184272343Sngie	atf_set "descr" "Checks that the default set of columns is correct" \
185272343Sngie	                "and also check that the columns printed by the -j," \
186272343Sngie	                "-l, -s, -u and -v flags alone are correct"
187272343Sngie}
188272343Sngiedefault_columns_body()
189272343Sngie{
190272343Sngie	setup_keywords
191272343Sngie	check_heading_keywords '' "$default_keywords"
192272343Sngie	check_heading_variations 'j' "$j_keywords"
193272343Sngie	check_heading_variations 'l' "$l_keywords"
194272343Sngie	check_heading_variations 's' "$s_keywords"
195272343Sngie	check_heading_variations 'u' "$u_keywords"
196272343Sngie	check_heading_variations 'v' "$v_keywords"
197272343Sngie}
198272343Sngie
199272343Sngieatf_test_case minus_O
200272343Sngieminus_O_head()
201272343Sngie{
202272343Sngie	atf_set "descr" "Checks that 'ps -O foo' inserts columns just after" \
203272343Sngie	                "the pid column"
204272343Sngie}
205272343Sngieminus_O_body()
206272343Sngie{
207272343Sngie	setup_keywords
208272343Sngie	check_heading_keywords '-O %cpu,%mem' \
209272343Sngie		"$(echo "${default_keywords}" | sed -e 's/pid/pid %cpu %mem/')"
210272343Sngie	check_heading_keywords '-O %cpu -O %mem' \
211272343Sngie		"$(echo "${default_keywords}" | sed -e 's/pid/pid %cpu %mem/')"
212272343Sngie	check_heading_keywords '-O%cpu -O%mem' \
213272343Sngie		"$(echo "${default_keywords}" | sed -e 's/pid/pid %cpu %mem/')"
214272343Sngie}
215272343Sngie
216272343Sngieatf_test_case minus_o
217272343Sngieminus_o_head()
218272343Sngie{
219272343Sngie	atf_set "descr" "Checks simple cases of 'ps -o foo' to control which" \
220272343Sngie	                "columns are printed; this does not test header" \
221272343Sngie	                "overriding via 'ps -o foo=BAR'"
222272343Sngie}
223272343Sngieminus_o_body()
224272343Sngie{
225272343Sngie	setup_keywords
226272343Sngie	# Keywords for "-o name" override the default display
227272343Sngie	check_heading_keywords '-o pid,%cpu,%mem' \
228272343Sngie		"pid %cpu %mem"
229272343Sngie	check_heading_keywords '-o pid -o %cpu,%mem' \
230272343Sngie		"pid %cpu %mem"
231272343Sngie	check_heading_keywords '-opid -o %cpu,%mem' \
232272343Sngie		"pid %cpu %mem"
233272343Sngie	# Space works like comma
234272343Sngie	check_heading_keywords '-opid -o "%cpu %mem"' \
235272343Sngie		"pid %cpu %mem"
236272343Sngie	# Check missing pid
237272343Sngie	check_heading_keywords '-o comm' \
238272343Sngie		"comm"
239272343Sngie	# Check pid present but not first
240272343Sngie	check_heading_keywords '-o comm,pid' \
241272343Sngie		"comm pid"
242272343Sngie}
243272343Sngie
244272343Sngieatf_test_case override_heading_simple
245272343Sngieoverride_heading_simple_head()
246272343Sngie{
247272343Sngie	atf_set "descr" "Tests simple uses of header overriding via" \
248272343Sngie	                "'ps -o foo=BAR'.  This does not test columns " \
249272343Sngie	                "with null headings, or headings with embedded" \
250272343Sngie	                "space, ',' or '='."
251272343Sngie}
252272343Sngieoverride_heading_simple_body()
253272343Sngie{
254272343Sngie	setup_keywords
255272343Sngie	check_heading_regexp '-o pid=PPP -o comm' \
256272343Sngie		'^ *PPP '"${head_text_comm}"'$' # no trailing space
257272343Sngie	check_heading_regexp '-o pid=PPP -o comm=CCC' \
258272343Sngie		'^ *PPP CCC$'
259272343Sngie	check_heading_regexp '-o pid,comm=CCC' \
260272343Sngie		'^'"${head_regexp_pid}"' CCC$'
261272343Sngie	check_heading_regexp '-o pid -o comm=CCC' \
262272343Sngie		'^'"${head_regexp_pid}"' CCC$'
263272343Sngie	# Check missing pid
264272343Sngie	check_heading_regexp '-o comm=CCC' \
265272343Sngie		'^CCC$'
266272343Sngie	# Check pid present but not first
267272343Sngie	check_heading_regexp '-o comm=CCC -o pid=PPP' \
268272343Sngie		'^CCC  *PPP$'
269272343Sngie	check_heading_regexp '-o comm,pid=PPP' \
270272343Sngie		'^'"${head_regexp_comm}"'  *PPP$'
271272343Sngie}
272272343Sngie
273272343Sngieatf_test_case override_heading_embedded_specials
274272343Sngieoverride_heading_embedded_specials_head()
275272343Sngie{
276272343Sngie	atf_set "descr" "Tests header overriding with embedded space," \
277272343Sngie	                "',' or '='.  Everything after the first '='" \
278272343Sngie	                "is part of the heading."
279272343Sngie}
280272343Sngieoverride_heading_embedded_specials_body()
281272343Sngie{
282272343Sngie	setup_keywords
283272343Sngie	# Check embedded "," or "=" in override header.
284272343Sngie	check_heading_regexp '-o comm,pid==' \
285272343Sngie		'^'"${head_regexp_comm}"'  *=$'
286272343Sngie	check_heading_regexp '-o comm,pid=,' \
287272343Sngie		'^'"${head_regexp_comm}"'  *,$'
288272343Sngie	check_heading_regexp '-o pid=PPP,comm' \
289272343Sngie		'^ *PPP,comm$' # not like '-o pid=PPP -o comm'
290272343Sngie	check_heading_regexp '-o pid=PPP,comm=CCC' \
291272343Sngie		'^ *PPP,comm=CCC$' # not like '-o pid=PPP -o comm=CCC'
292272343Sngie	check_heading_regexp '-o comm,pid=PPP,QQQ' \
293272343Sngie		'^'"${head_regexp_comm}"'  *PPP,QQQ$'
294272343Sngie	check_heading_regexp '-o comm,pid=ppid,tty=state' \
295272343Sngie		'^'"${head_regexp_comm}"'  *ppid,tty=state$'
296272343Sngie	# Check embedded space or tab in override header.
297272343Sngie	check_heading_regexp '-o comm,pid="PPP QQQ"' \
298272343Sngie		'^'"${head_regexp_comm}"'  *PPP QQQ$'
299272343Sngie	check_heading_regexp '-o comm,pid="PPP${tab}QQQ"' \
300272343Sngie		'^'"${head_regexp_comm}"'  *PPP'"${tab}"'QQQ$'
301272343Sngie}
302272343Sngie
303272343Sngieatf_test_case override_heading_some_null
304272343Sngieoverride_heading_some_null_head()
305272343Sngie{
306272343Sngie	atf_set "descr" "Tests simple uses of null column headings" \
307272343Sngie	                "overriding via 'ps -o foo=BAR -o baz='.  This" \
308272343Sngie	                "does not test the case where all columns have" \
309272343Sngie	                "null headings."
310272343Sngie}
311272343Sngieoverride_heading_some_null_body()
312272343Sngie{
313272343Sngie	setup_keywords
314272343Sngie	check_heading_regexp '-o pid=PPP -o comm=' \
315272343Sngie		'^ *PPP *$'
316272343Sngie	check_heading_regexp '-o pid= -o comm=CCC' \
317272343Sngie		'^ * CCC$'
318272343Sngie	check_heading_regexp '-o pid -o comm=' \
319272343Sngie		'^'"${head_regexp_pid}"' *$'
320272343Sngie	# Check missing pid
321272343Sngie	check_heading_regexp '-o ppid= -o comm=CCC' \
322272343Sngie		'^ * CCC$'
323272343Sngie	check_heading_regexp '-o ppid=PPP -o comm=' \
324272343Sngie		'^ *PPP *$'
325272343Sngie	# Check pid present but not first
326272343Sngie	check_heading_regexp '-o comm= -o pid=PPP' \
327272343Sngie		'^ * PPP$'
328272343Sngie	check_heading_regexp '-o comm,pid=' \
329272343Sngie		'^'"${head_regexp_comm}"' *$'
330272343Sngie	# A field with a null custom heading retains a minimum width
331272343Sngie	# derived from the default heading.  This does not apply
332272343Sngie	# to a field with a very short (non-null) custom heading.
333272343Sngie	#
334272343Sngie	# We choose "holdcnt" as a column whose width is likely to be
335272343Sngie	# determined entirely by the header width, because the values
336272343Sngie	# are likely to be very small.
337272343Sngie	check_heading_regexp '-o holdcnt -o holdcnt -o holdcnt' \
338272343Sngie		'^HOLDCNT HOLDCNT HOLDCNT$'
339272343Sngie	check_heading_regexp '-o holdcnt -o holdcnt= -o holdcnt' \
340272343Sngie		'^HOLDCNT         HOLDCNT$'
341272343Sngie	check_heading_regexp '-o holdcnt -o holdcnt=HH -o holdcnt' \
342272343Sngie		'^HOLDCNT HH HOLDCNT$'
343272343Sngie}
344272343Sngie
345272343Sngieatf_test_case override_heading_all_null
346272343Sngieoverride_heading_all_null_head()
347272343Sngie{
348272343Sngie	atf_set "descr" "Tests the use of 'ps -o foo= -o bar=' (with a" \
349272343Sngie	                "null heading for every column).  The heading" \
350272343Sngie	                "should not be printed at all in this case."
351272343Sngie}
352272343Sngieoverride_heading_all_null_body()
353272343Sngie{
354272343Sngie	setup_keywords
355272343Sngie	# A heading with a space is not a null heading,
356272343Sngie	# so should not be suppressed
357272343Sngie	check_heading_regexp '-o comm=" "' \
358272343Sngie		'^ *$'
359272343Sngie	# Null headings should be suppressed
360272343Sngie	check_no_heading_line '-o pid= -o comm='
361272343Sngie	check_no_heading_line '-o pid= -o comm='
362272343Sngie	# Check missing pid
363272343Sngie	check_no_heading_line '-o ppid='
364272343Sngie	check_no_heading_line '-o comm='
365272343Sngie	check_no_heading_line '-o command='
366272343Sngie	check_no_heading_line '-o ppid= -o comm='
367272343Sngie	check_no_heading_line '-o comm= -o ppid='
368272343Sngie	# Check pid present but not first
369272343Sngie	check_no_heading_line '-o comm= -o pid='
370272343Sngie	check_no_heading_line '-o ppid= -o pid= -o command='
371272343Sngie}
372272343Sngie
373272343Sngieatf_test_case duplicate_column
374272343Sngieduplicate_column_head()
375272343Sngie{
376272343Sngie	atf_set "descr" "Tests the use of -o options to display the" \
377272343Sngie	                "same column more than once"
378272343Sngie}
379272343Sngieduplicate_column_body()
380272343Sngie{
381272343Sngie	setup_keywords
382272343Sngie	# two custom headers
383272343Sngie	check_heading_regexp '-o pid=PPP -o pid=QQQ' \
384272343Sngie		'^ *PPP  *QQQ$'
385272343Sngie	# one custom header, before and after default header
386272343Sngie	check_heading_regexp '-o pid=PPP -o pid' \
387272343Sngie		'^ *PPP '"${head_regexp_pid}"'$'
388272343Sngie	check_heading_regexp '-o pid -o pid=QQQ' \
389272343Sngie		'^'"${head_regexp_pid}"'  *QQQ$'
390272343Sngie	# custom headers both before and after default header
391272343Sngie	check_heading_regexp '-o pid=PPP -o pid -o pid=QQQ' \
392272343Sngie		'^ *PPP '"${head_regexp_pid}"'  *QQQ$'
393272343Sngie}
394272343Sngie
395272343Sngieatf_init_test_cases() {
396272343Sngie	atf_add_test_case default_columns
397272343Sngie	atf_add_test_case minus_O
398272343Sngie	atf_add_test_case minus_o
399272343Sngie	atf_add_test_case override_heading_simple
400272343Sngie	atf_add_test_case override_heading_embedded_specials
401272343Sngie	atf_add_test_case override_heading_some_null
402272343Sngie	atf_add_test_case override_heading_all_null
403272343Sngie	atf_add_test_case duplicate_column
404272343Sngie}
405