1#!/usr/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 2018 Joyent, Inc.
15#
16
17#
18# libdis test driver
19#
20# Tests are arranged by architecture. By default we'll run all of the
21# dis tests on our current architecture only. If the -p option is passed
22# to point to other correctly built gas instances, then we'll run those
23# tests, verifying that the cross-dis works.
24#
25# Each test should begin with one of the following three keywords:
26#
27#	tst	- Run both the 32-bit and 64-bit versions
28#	32	- Only run this with the gas 32-bit flag
29#	64	- Only run this with the gas 64-bit flag
30#
31# For example, tst.smap.s, would be built both 32-bit and 64-bit and compared to
32# its output file.
33#
34# Each input file should consist of a series of instructions in a function named
35# 'libdis_test'. The test suite will compile this file into an object file,
36# disassemble it, and compare it to the output file.
37#
38# For each input file, there should be a corresponding output file with the .out
39# suffix instead of the .s suffix. So, if you had tst.smap.s, you should have
40# tst.smap.out.
41#
42
43unalias -a
44dt_arg0=$(basename $0)
45dt_dis="/usr/bin/dis -qF libdis_test"
46dt_diff="/usr/bin/cmp -s"
47dt_defas="gas"
48dt_defarch=
49dt_nodefault=
50dt_tests=
51dt_tnum=0
52dt_tfail=0
53dt_tsuc=0
54dt_origwd=
55dt_root=
56dt_faildir=0
57typeset -A dt_platforms
58
59fatal()
60{
61	typeset msg="$*"
62	[[ -z "$msg" ]] && msg="failed"
63	echo "$dt_arg0: $msg" >&2
64	exit 1
65}
66
67usage()
68{
69	typeset msg="$*"
70	[[ -z "$msg" ]] || echo "$msg" 2>&1
71	cat <<USAGE >&2
72Usage: $dt_arg0  [-n] [ -p platform=pathtoas ]... [ test ]...
73
74	Runs all dis for the current platform or only specified tests if listed.
75
76	-n			Don't run default platform tests
77	-p platform=pathtoas	Run tests for platform using assembler. Should
78				either be an absolute path or a command on the
79				path.
80USAGE
81	exit 2
82}
83
84#
85# By default, tests only run for the current platform. In other words,
86# running on an x86 system only assumes that the tests in the i386
87# directory should be run. If the -p option is specified, then other
88# platforms will be run.
89#
90# Right now, we only support running this on x86 natively; however, you
91# can run tests for other platforms with the -p option.
92#
93determine_arch()
94{
95	typeset arch
96
97	arch=$(uname -p)
98	[[ $? -eq 0 ]] || fatal "failed to determine host architecture"
99	[[ "$arch" != "i386" ]] && fatal "dis tests are only supported on x86"
100	[[ -n "$dt_nodefault" ]] && return
101	dt_defarch="i386"
102	dt_platforms[$dt_defarch]=$dt_defas
103}
104
105#
106# Iterate over the set of platforms and verify that we both know about them and
107# we can find the assembler for them.
108#
109check_platforms()
110{
111	typeset key
112
113	for key in ${!dt_platforms[@]}; do
114		typeset bin
115		[[ -d $dt_root/$key ]] || fatal "encountered unknown platform: $key"
116
117		#
118		# This may be a path or something else.
119		#
120		bin=${dt_platforms[$key]}
121		[[ -x $bin ]] && continue
122		which $bin >/dev/null 2>&1 && continue
123		fatal "failed to find command as absolute path or file: $bin"
124	done
125}
126
127handle_failure()
128{
129	typeset dir reason source out
130	dir=$1
131	reason=$2
132	source=$3
133	out=$4
134	faildir=
135
136	while [[ -d failure.$dt_faildir ]]; do
137		((dt_faildir++))
138	done
139
140	faildir="failure.$dt_faildir"
141	mv $dir $faildir
142	cp $source $faildir/
143	cp $out $faildir/
144	printf "%s " "failed "
145	[[ -n $reason ]] && printf "%s " $reason
146	printf "%s\n" "$faildir"
147	((dt_tfail++))
148}
149
150#
151# Check
152#
153test_one()
154{
155	typeset gflags source cmp disfile outfile extra aserr diserr
156	dir="dis.$$"
157	gflags=$1
158	source=$2
159	cmp=$3
160	extra=$4
161
162	outfile=$dir/dis.o
163	aserr=$dir/as.stderr
164	disfile=$dir/libdis.out
165	diserr=$dir/dis.stderr
166
167	((dt_tnum++))
168	mkdir -p $dir || fatal "failed to make directory $dir"
169
170	printf "testing %s " $source
171	[[ -n $extra ]] && printf "%s " $extra
172	printf "... "
173	if ! $gas $gflags -o $outfile $source 2>$aserr >/dev/null; then
174		handle_failure $dir "(assembling)" $source $cmp
175		return
176	fi
177
178	if ! $dt_dis $outfile >$disfile 2>$diserr; then
179		handle_failure $dir "(disassembling)" $source $cmp
180		return
181	fi
182
183	if ! $dt_diff $disfile $cmp; then
184		handle_failure $dir "(comparing)" $source $cmp
185		return
186	fi
187
188	((dt_tsuc++))
189	print "passed"
190	rm -rf $dir || fatal "failed to remove directory $dir"
191}
192
193#
194# Run a single test. This may result in two actual tests (one 32-bit and one
195# 64-bit) being run.
196#
197run_single_file()
198{
199	typeset sfile base cmpfile prefix arch gas p flags
200	typeset asflags32 asflags64
201	sfile=$1
202
203	base=${sfile##*/}
204	cmpfile=${sfile%.*}.out
205	prefix=${base%%.*}
206	arch=${sfile%/*}
207	arch=${arch##*/}
208	[[ -f $cmpfile ]] || fatal "missing output file $cmpfile"
209	gas=${dt_platforms[$arch]}
210	[[ -n $gas ]] || fatal "encountered test $sfile, but missing assembler"
211
212	case "$arch" in
213	"risc-v")
214		asflags32="-march=rv32g"
215		asflags64="-march=rv64g"
216		;;
217	"risc-v-c")
218		asflags32="-march=rv32gc"
219		asflags64="-march=rv64gc"
220		;;
221	*)
222		asflags32="-32"
223		asflags64="-64"
224		;;
225	esac
226
227	case "$prefix" in
228	32)
229		test_one $asflags32 $sfile $cmpfile
230		;;
231	64)
232		test_one $asflags64 $sfile $cmpfile
233		;;
234	tst)
235		test_one $asflags32 $sfile $cmpfile "(32-bit)"
236		test_one $asflags64 $sfile $cmpfile "(64-bit)"
237		;;
238	esac
239}
240
241#
242# Iterate over all the test directories and run the specified tests
243#
244run_tests()
245{
246	typeset t
247	if [[ $# -ne 0 ]]; then
248		for t in $@; do
249			run_single_file $t
250		done
251	else
252		typeset k tests tests32 tests64
253		for k in ${!dt_platforms[@]}; do
254			tests=$(find $dt_root/$k -type f -name 'tst.*.s')
255			tests32=$(find $dt_root/$k -type f -name '32.*.s')
256			tests64=$(find $dt_root/$k -type f -name '64.*.s')
257			for t in $tests $tests32 $tests64; do
258				run_single_file $t
259			done
260		done
261	fi
262}
263
264goodbye()
265{
266	cat <<EOF
267
268--------------
269libdis Results
270--------------
271
272Tests passed: $dt_tsuc
273Tests failed: $dt_tfail
274Tests ran:    $dt_tnum
275EOF
276}
277
278
279dt_origwd=$PWD
280cd $(dirname $0) || fatal "failed to cd to test root"
281dt_root=$PWD
282cd $dt_origwd || fatal "failed to return to original dir"
283
284while getopts ":np:" c $@; do
285	case "$c" in
286	n)
287		dt_nodefault="y"
288		;;
289	p)
290		OLDIFS=$IFS
291		IFS="="
292		set -A split $OPTARG
293		IFS=$OLDIFS
294		[[ ${#split[@]} -eq 2 ]] || usage "malformed -p option: $OPTARG"
295		dt_platforms[${split[0]}]=${split[1]}
296		;;
297	:)
298		usage "option requires an argument -- $OPTARG"
299		;;
300	*)
301		usage "invalid option -- $OPTARG"
302		;;
303	esac
304done
305
306[[ -n $dt_nodefault && ${#dt_platforms[@]} -eq 0 ]] && fatal \
307    "no platforms specified to run tests for"
308
309shift $((OPTIND-1))
310
311determine_arch
312check_platforms
313run_tests
314goodbye
315
316[[ $dt_tfail -eq 0 ]]
317