xref: /freebsd/contrib/netbsd-tests/bin/ps/t_ps.sh (revision 59c8e88e72633afbc47a4ace0d2170d00d51f7dc)
1# $NetBSD: t_ps.sh,v 1.2 2014/01/16 04:16:32 mlelstv Exp $
2#
3# Copyright (c) 2007 The NetBSD Foundation, Inc.
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
16# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
19# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25# POSSIBILITY OF SUCH DAMAGE.
26#
27
28# the implementation of "ps" to test
29: ${TEST_PS:="ps"}
30# tab and newline characters
31tab="$(printf '\t')"
32# nl="$(printf '\n')" doesn't work
33nl='
34'
35
36#
37# Parse the "keywords" file into a load of shell variables
38#
39setup_keywords()
40{
41	# Set variables representing the header text
42	# for all normal keywords (except aliases), and
43	# for regular expressions to match the text in left- or
44	# right-justified columns.
45	# For example, head_text_p_cpu="%CPU" head_regexp_p_cpu=" *%CPU".
46	while read keyword heading flag
47	do
48		case "$keyword" in
49		''|\#*)	continue
50			;;
51		esac
52		[ x"$flag" = x"ALIAS" ] && continue
53		kvar="${keyword}"
54		case "${keyword}" in
55		%*)	kvar="p_${keyword#%}"
56			;;
57		esac
58		eval head_text_${kvar}=\'"${heading}"\'
59		case "${flag}" in
60		'')	# right justified
61			eval head_regexp_${kvar}=\'" *${heading}"\'
62			;;
63		LJUST)	# left justified
64			eval head_regexp_${kvar}=\'"${heading} *"\'
65			;;
66		*)	atf_fail "unknown flag in keywords"
67			;;
68		esac
69	done <"$(atf_get_srcdir)/keywords"
70
71	# Now do the aliases.
72	while read keyword heading flag
73	do
74		case "$keyword" in
75		''|\#*)	continue
76			;;
77		esac
78		[ x"$flag" != x"ALIAS" ] && continue
79		kvar="${keyword}"
80		avar="${heading}"
81		case "${keyword}" in
82		%*)	kvar="p_${keyword#%}"
83			;;
84		esac
85		case "${heading}" in
86		%*)	avar="p_${heading#%}"
87			;;
88		esac
89		eval head_text_${kvar}=\"\$head_text_${avar}\"
90		eval head_regexp_${kvar}=\"\$head_regexp_${avar}\"
91	done <"$(atf_get_srcdir)/keywords"
92
93	# default sets of keywords
94	default_keywords='pid tty stat time command'
95	j_keywords='user pid ppid pgid sess jobc state tt time command'
96	l_keywords='uid pid ppid cpu pri nice vsz rss wchan state tt time command'
97	s_keywords='uid pid ppid cpu lid nlwp pri nice vsz rss wchan lstate tt ltime command'
98	u_keywords='user pid %cpu %mem vsz rss tt state start time command'
99	v_keywords='pid state time sl re pagein vsz rss lim tsiz %cpu %mem command'
100}
101
102# Convert a list of keywords like "pid comm" to a regexp
103# like " *PID COMMAND *"
104heading_keywords_to_regexp()
105{
106	local keywords="$1"
107	local regexp
108	regexp="$(echo "$keywords" | \
109		sed -E -e 's/\%/p_/g' -e 's/(^| )/\1\$head_regexp_/g')"
110	eval regexp=\""${regexp}"\"
111	regexp="^${regexp}\$"
112	echo "$regexp"
113}
114
115#
116# Check that a string matches a regexp; use the specified id
117# in error or success messages.
118#
119check_regexp() {
120	local id="$1" string="$2" regexp="$3"
121	if ! expr "$string" : "$regexp" >/dev/null
122	then
123		atf_fail "${id}: expected [${regexp}], got [${string}]"
124		false
125	fi
126}
127
128#
129# Run "ps $args -p $$"; check that only one line is printed,
130# without a preceding header line.
131#
132check_no_heading_line()
133{
134	local args="$1"
135	local output="$(eval "${TEST_PS} $args -p $$")"
136	case "$output" in
137	*"$nl"*)
138		local firstline="${output%%${nl}*}"
139		atf_fail "check_no_heading_line [$args] got [$firstline]"
140		;;
141	*)
142		;;
143	esac
144}
145
146#
147# Run "ps $args"; check that the heading matches the expected regexp.
148#
149check_heading_regexp()
150{
151	args="$1"
152	regexp="$2"
153	actual="$( eval "${TEST_PS} $args" | sed -e 1q )"
154	check_regexp "heading [$args]" "${actual}" "${regexp}"
155}
156
157#
158# Run "ps $args"; check that the heading matches a regexp constructed
159# from the specified keywords.
160#
161check_heading_keywords()
162{
163	args="$1"
164	keywords="$2"
165	check_heading_regexp "$args" "$(heading_keywords_to_regexp "$keywords")"
166}
167
168#
169# Try several variations on "ps $flag", "ps -$flag", etc.,
170# and check that the heading always has the correct keywords.
171#
172check_heading_variations()
173{
174	flag="$1"
175	keywords="$2"
176	for args in "$flag" "-$flag" "-$flag$flag -$flag"; do
177		check_heading_keywords "$args" "$keywords"
178	done
179}
180
181atf_test_case default_columns
182default_columns_head()
183{
184	atf_set "descr" "Checks that the default set of columns is correct" \
185	                "and also check that the columns printed by the -j," \
186	                "-l, -s, -u and -v flags alone are correct"
187}
188default_columns_body()
189{
190	setup_keywords
191	check_heading_keywords '' "$default_keywords"
192	check_heading_variations 'j' "$j_keywords"
193	check_heading_variations 'l' "$l_keywords"
194	check_heading_variations 's' "$s_keywords"
195	check_heading_variations 'u' "$u_keywords"
196	check_heading_variations 'v' "$v_keywords"
197}
198
199atf_test_case minus_O
200minus_O_head()
201{
202	atf_set "descr" "Checks that 'ps -O foo' inserts columns just after" \
203	                "the pid column"
204}
205minus_O_body()
206{
207	setup_keywords
208	check_heading_keywords '-O %cpu,%mem' \
209		"$(echo "${default_keywords}" | sed -e 's/pid/pid %cpu %mem/')"
210	check_heading_keywords '-O %cpu -O %mem' \
211		"$(echo "${default_keywords}" | sed -e 's/pid/pid %cpu %mem/')"
212	check_heading_keywords '-O%cpu -O%mem' \
213		"$(echo "${default_keywords}" | sed -e 's/pid/pid %cpu %mem/')"
214}
215
216atf_test_case minus_o
217minus_o_head()
218{
219	atf_set "descr" "Checks simple cases of 'ps -o foo' to control which" \
220	                "columns are printed; this does not test header" \
221	                "overriding via 'ps -o foo=BAR'"
222}
223minus_o_body()
224{
225	setup_keywords
226	# Keywords for "-o name" override the default display
227	check_heading_keywords '-o pid,%cpu,%mem' \
228		"pid %cpu %mem"
229	check_heading_keywords '-o pid -o %cpu,%mem' \
230		"pid %cpu %mem"
231	check_heading_keywords '-opid -o %cpu,%mem' \
232		"pid %cpu %mem"
233	# Space works like comma
234	check_heading_keywords '-opid -o "%cpu %mem"' \
235		"pid %cpu %mem"
236	# Check missing pid
237	check_heading_keywords '-o comm' \
238		"comm"
239	# Check pid present but not first
240	check_heading_keywords '-o comm,pid' \
241		"comm pid"
242}
243
244atf_test_case override_heading_simple
245override_heading_simple_head()
246{
247	atf_set "descr" "Tests simple uses of header overriding via" \
248	                "'ps -o foo=BAR'.  This does not test columns " \
249	                "with null headings, or headings with embedded" \
250	                "space, ',' or '='."
251}
252override_heading_simple_body()
253{
254	setup_keywords
255	check_heading_regexp '-o pid=PPP -o comm' \
256		'^ *PPP '"${head_text_comm}"'$' # no trailing space
257	check_heading_regexp '-o pid=PPP -o comm=CCC' \
258		'^ *PPP CCC$'
259	check_heading_regexp '-o pid,comm=CCC' \
260		'^'"${head_regexp_pid}"' CCC$'
261	check_heading_regexp '-o pid -o comm=CCC' \
262		'^'"${head_regexp_pid}"' CCC$'
263	# Check missing pid
264	check_heading_regexp '-o comm=CCC' \
265		'^CCC$'
266	# Check pid present but not first
267	check_heading_regexp '-o comm=CCC -o pid=PPP' \
268		'^CCC  *PPP$'
269	check_heading_regexp '-o comm,pid=PPP' \
270		'^'"${head_regexp_comm}"'  *PPP$'
271}
272
273atf_test_case override_heading_embedded_specials
274override_heading_embedded_specials_head()
275{
276	atf_set "descr" "Tests header overriding with embedded space," \
277	                "',' or '='.  Everything after the first '='" \
278	                "is part of the heading."
279}
280override_heading_embedded_specials_body()
281{
282	setup_keywords
283	# Check embedded "," or "=" in override header.
284	check_heading_regexp '-o comm,pid==' \
285		'^'"${head_regexp_comm}"'  *=$'
286	check_heading_regexp '-o comm,pid=,' \
287		'^'"${head_regexp_comm}"'  *,$'
288	check_heading_regexp '-o pid=PPP,comm' \
289		'^ *PPP,comm$' # not like '-o pid=PPP -o comm'
290	check_heading_regexp '-o pid=PPP,comm=CCC' \
291		'^ *PPP,comm=CCC$' # not like '-o pid=PPP -o comm=CCC'
292	check_heading_regexp '-o comm,pid=PPP,QQQ' \
293		'^'"${head_regexp_comm}"'  *PPP,QQQ$'
294	check_heading_regexp '-o comm,pid=ppid,tty=state' \
295		'^'"${head_regexp_comm}"'  *ppid,tty=state$'
296	# Check embedded space or tab in override header.
297	check_heading_regexp '-o comm,pid="PPP QQQ"' \
298		'^'"${head_regexp_comm}"'  *PPP QQQ$'
299	check_heading_regexp '-o comm,pid="PPP${tab}QQQ"' \
300		'^'"${head_regexp_comm}"'  *PPP'"${tab}"'QQQ$'
301}
302
303atf_test_case override_heading_some_null
304override_heading_some_null_head()
305{
306	atf_set "descr" "Tests simple uses of null column headings" \
307	                "overriding via 'ps -o foo=BAR -o baz='.  This" \
308	                "does not test the case where all columns have" \
309	                "null headings."
310}
311override_heading_some_null_body()
312{
313	setup_keywords
314	check_heading_regexp '-o pid=PPP -o comm=' \
315		'^ *PPP *$'
316	check_heading_regexp '-o pid= -o comm=CCC' \
317		'^ * CCC$'
318	check_heading_regexp '-o pid -o comm=' \
319		'^'"${head_regexp_pid}"' *$'
320	# Check missing pid
321	check_heading_regexp '-o ppid= -o comm=CCC' \
322		'^ * CCC$'
323	check_heading_regexp '-o ppid=PPP -o comm=' \
324		'^ *PPP *$'
325	# Check pid present but not first
326	check_heading_regexp '-o comm= -o pid=PPP' \
327		'^ * PPP$'
328	check_heading_regexp '-o comm,pid=' \
329		'^'"${head_regexp_comm}"' *$'
330	# A field with a null custom heading retains a minimum width
331	# derived from the default heading.  This does not apply
332	# to a field with a very short (non-null) custom heading.
333	#
334	# We choose "holdcnt" as a column whose width is likely to be
335	# determined entirely by the header width, because the values
336	# are likely to be very small.
337	check_heading_regexp '-o holdcnt -o holdcnt -o holdcnt' \
338		'^HOLDCNT HOLDCNT HOLDCNT$'
339	check_heading_regexp '-o holdcnt -o holdcnt= -o holdcnt' \
340		'^HOLDCNT         HOLDCNT$'
341	check_heading_regexp '-o holdcnt -o holdcnt=HH -o holdcnt' \
342		'^HOLDCNT HH HOLDCNT$'
343}
344
345atf_test_case override_heading_all_null
346override_heading_all_null_head()
347{
348	atf_set "descr" "Tests the use of 'ps -o foo= -o bar=' (with a" \
349	                "null heading for every column).  The heading" \
350	                "should not be printed at all in this case."
351}
352override_heading_all_null_body()
353{
354	setup_keywords
355	# A heading with a space is not a null heading,
356	# so should not be suppressed
357	check_heading_regexp '-o comm=" "' \
358		'^ *$'
359	# Null headings should be suppressed
360	check_no_heading_line '-o pid= -o comm='
361	check_no_heading_line '-o pid= -o comm='
362	# Check missing pid
363	check_no_heading_line '-o ppid='
364	check_no_heading_line '-o comm='
365	check_no_heading_line '-o command='
366	check_no_heading_line '-o ppid= -o comm='
367	check_no_heading_line '-o comm= -o ppid='
368	# Check pid present but not first
369	check_no_heading_line '-o comm= -o pid='
370	check_no_heading_line '-o ppid= -o pid= -o command='
371}
372
373atf_test_case duplicate_column
374duplicate_column_head()
375{
376	atf_set "descr" "Tests the use of -o options to display the" \
377	                "same column more than once"
378}
379duplicate_column_body()
380{
381	setup_keywords
382	# two custom headers
383	check_heading_regexp '-o pid=PPP -o pid=QQQ' \
384		'^ *PPP  *QQQ$'
385	# one custom header, before and after default header
386	check_heading_regexp '-o pid=PPP -o pid' \
387		'^ *PPP '"${head_regexp_pid}"'$'
388	check_heading_regexp '-o pid -o pid=QQQ' \
389		'^'"${head_regexp_pid}"'  *QQQ$'
390	# custom headers both before and after default header
391	check_heading_regexp '-o pid=PPP -o pid -o pid=QQQ' \
392		'^ *PPP '"${head_regexp_pid}"'  *QQQ$'
393}
394
395atf_init_test_cases() {
396	atf_add_test_case default_columns
397	atf_add_test_case minus_O
398	atf_add_test_case minus_o
399	atf_add_test_case override_heading_simple
400	atf_add_test_case override_heading_embedded_specials
401	atf_add_test_case override_heading_some_null
402	atf_add_test_case override_heading_all_null
403	atf_add_test_case duplicate_column
404}
405