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 2022 Oxide Computer Company
15#
16
17#
18# This contains a number of basic tests for ar(1). When adding something
19# to ar or fixing a bug, please expand this!
20#
21
22unalias -a
23set -o pipefail
24
25ar_arg0="$(basename $0)"
26ar_data="$(dirname $0)"
27ar_data0="$ar_data/ar_test0.o"
28ar_data1="$ar_data/ar_test1.o"
29ar_prog=/usr/bin/ar
30ar_tmpdir=/tmp/ar.$$
31
32ar_f01="$ar_tmpdir/test01.a"
33ar_f10="$ar_tmpdir/test10.a"
34
35ar_t01="ar_test0.o
36ar_test1.o"
37ar_t10="ar_test1.o
38ar_test0.o"
39
40strip_prog=/usr/bin/strip
41dump_prog=/usr/bin/dump
42nm_prog=/usr/bin/nm
43
44ar_exit=0
45
46function warn
47{
48	typeset msg="$*"
49	[[ -z "$msg" ]] && msg="failed"
50	print -u2 "TEST FAILED: $ar_arg0: $msg"
51	ar_exit=1
52}
53
54function compare_files
55{
56	typeset base="$1"
57	typeset comp="$2"
58	typeset err=0
59
60	if ! $dump_prog -g $comp | sed 1d > $comp.dump; then
61		warn "failed to dump -g $comp"
62		err=1
63	fi
64
65	if ! $nm_prog -P -tx $comp > $comp.nm; then
66		warn "failed to nm $comp"
67		err=1
68	fi
69
70	if ! diff $base.dump $comp.dump; then
71		warn "$base.dump and $comp.dump don't match"
72		err=1
73	fi
74
75	if ! diff $base.nm $comp.nm; then
76		warn "$base.dump and $comp.dump don't match"
77		err=1
78	fi
79
80	return $err
81}
82
83#
84# To set things up, we first go and create two basic archives that we
85# will then use as the basis for comaring various operations later.
86#
87function setup_baseline
88{
89	if ! $ar_prog cr $ar_f01 $ar_data0 $ar_data1; then
90		warn "failed to create basic archive $ar_f01"
91	fi
92
93	if ! $ar_prog cr $ar_f10 $ar_data1 $ar_data0; then
94		warn "failed to create basic archive $ar_f10"
95	fi
96
97	if ! $dump_prog -g $ar_f01 | sed 1d > $ar_f01.dump; then
98		warn "failed to dump archive $ar_f01"
99	fi
100
101	if ! $dump_prog -g $ar_f10 | sed 1d > $ar_f10.dump; then
102		warn "failed to dump archive $ar_f10"
103	fi
104
105	if ! $nm_prog -P -tx $ar_f01 > $ar_f01.nm; then
106		warn "failed to nm archive $ar_f01"
107	fi
108
109	if ! $nm_prog -P -tx $ar_f10 > $ar_f10.nm; then
110		warn "failed to nm archive $ar_f01"
111	fi
112
113	print "TEST PASSED: basic archive creation"
114}
115
116function strip_archive
117{
118	typeset file="$1"
119	typeset output=
120
121	if ! $strip_prog $file 2>/dev/null; then
122		warn "failed to strip $alt"
123		return 1
124	fi
125
126	output=$($dump_prog -g $file)
127	if [[ -n "$output" ]]; then
128		warn "stripped file $file somehow has an ar header"
129		return 1
130	fi
131
132	return 0
133}
134
135#
136# Validate that stripping and regenerating a symbol table actually
137# works.
138#
139function test_s
140{
141	typeset alt="$ar_tmpdir/s.a"
142	typeset output=
143
144	if ! cp $ar_f01 $alt; then
145		warn "failed to copy file"
146		return
147	fi
148
149	if ! strip_archive $alt; then
150		return
151	fi
152
153	if ! $ar_prog s $alt; then
154		warn "restore symbol table with ar s"
155	fi
156
157	if compare_files "$ar_f01" "$alt"; then
158		print "TEST PASSED: restoring stripped archive with -s"
159	fi
160
161	if ! strip_archive $alt; then
162		return
163	fi
164
165	if ! $ar_prog st $alt >/dev/null; then
166		warn "restore symbol table with ar st"
167	fi
168
169	if compare_files "$ar_f01" "$alt"; then
170		print "TEST PASSED: restoring stripped archive with -st"
171	fi
172
173	if ! strip_archive $alt; then
174		return
175	fi
176
177	output=$($ar_prog sv $alt 2>&1)
178	if [[ "$output" == "ar: writing $alt" ]]; then
179		print "TEST PASSED: ar -sv has proper output"
180	else
181		warn "ar -sv has unexpected output: $output"
182	fi
183
184	if compare_files "$ar_f01" "$alt"; then
185		print "TEST PASSED: restoring stripped archive with -sv"
186	fi
187}
188
189#
190# Ensure that use of -s and -r still works. This is a regression test
191# for the original integration of standalone -s support.
192#
193function test_rs
194{
195	typeset alt="$ar_tmpdir/rs.a"
196
197	if ! $ar_prog rs $alt $ar_data1 $ar_data0; then
198		warn "ar -rs: did not create an archive"
199	fi
200
201	if ! compare_files $ar_f10 $alt; then
202		warn "ar -rs: did not create expected file"
203	else
204		print "TEST PASSED: ar -rs creates archives"
205	fi
206
207	rm -f $alt
208
209	if ! $ar_prog crs $alt $ar_data1 $ar_data0; then
210		warn "ar -crs: did not create an archive"
211	fi
212
213	if ! compare_files $ar_f10 $alt; then
214		warn "ar -crs: did not create expected file"
215	else
216		print "TEST PASSED: ar -crs creates archives"
217	fi
218}
219
220#
221# Verify that basic ar -r invocations ultimately end up creating what
222# we'd expect.
223#
224function test_incremental
225{
226	typeset alt="$ar_tmpdir/incr.a"
227
228	if ! $ar_prog cr $alt $ar_data0; then
229		warn "incremental archive: failed to create archive"
230		return
231	fi
232
233	if ! $ar_prog cr $alt $ar_data1; then
234		warn "incremental archive: failed to add to archive"
235		return
236	fi
237
238	if ! compare_files $ar_f01 $alt; then
239		warn "incremental archive: did not create expected file"
240	else
241		print "TEST PASSED: incremental archive creation"
242	fi
243
244}
245
246#
247# Validate that ar's various -a and -b variants work.
248#
249function test_pos
250{
251	typeset alt="$ar_tmpdir/pos.a"
252
253	if ! $ar_prog cr $alt $ar_data1; then
254		warn "positional tests: failed to create archive"
255		return;
256	fi
257
258	if ! $ar_prog -cra ar_test1.o $alt $ar_data0; then
259		warn "positional tests: -a append failed"
260		return
261	fi
262
263	if ! compare_files $ar_f10 $alt; then
264		warn "positional tests: -cra archive is incorrect"
265	else
266		print "TEST PASSED: positional tests: ar -cra"
267	fi
268
269	rm -f $alt
270
271	if ! $ar_prog cr $alt $ar_data1; then
272		warn "positional tests: failed to create archive"
273		return;
274	fi
275
276	if ! $ar_prog -crb ar_test1.o $alt $ar_data0; then
277		warn "positional tests: -b prepend failed"
278		return
279	fi
280
281	if ! compare_files $ar_f01 $alt; then
282		warn "positional tests: -crb archive is incorrect"
283	else
284		print "TEST PASSED: positional tests: ar -crb"
285	fi
286
287	rm -f $alt
288
289	if ! $ar_prog cr $alt $ar_data1; then
290		warn "positional tests: failed to create archive"
291		return;
292	fi
293
294	if ! $ar_prog -cri ar_test1.o $alt $ar_data0; then
295		warn "positional tests: -i prepend failed"
296		return
297	fi
298
299	if ! compare_files $ar_f01 $alt; then
300		warn "positional tests: -cri archive is incorrect"
301	else
302		print "TEST PASSED: positional tests: ar -cri"
303	fi
304
305}
306
307#
308# Go through and validate the various versions of ar x.
309#
310function test_x
311{
312	typeset out0="$ar_tmpdir/ar_test0.o"
313	typeset out1="$ar_tmpdir/ar_test1.o"
314	typeset output=
315
316	rm -f $out0 $out1
317
318	if ! $ar_prog x $ar_f01; then
319		warn "ar -x: failed to extract files"
320	fi
321
322	if cmp -s $out0 $ar_data0 && cmp -s $out1 $ar_data1; then
323		print "TEST PASSED: ar -x"
324	else
325		warn "ar -x: extracted files differs"
326	fi
327
328	rm -f $out0 $out1
329	echo elberth > $out0
330
331	#
332	# For some reason, ar -Cx will actually fail if you have an
333	# existing file. It seems a bit weird as it means you can't
334	# extract existing files (depdendent on order), but that's how
335	# it goes.
336	#
337	if $ar_prog Cx $ar_f01 ar_test0.o; then
338		warn "ar -Cx: failed to extract file that wasn't in fs\n"
339	fi
340
341	output=$(cat $out0)
342	if [[ "$output" != "elberth" ]]; then
343		warn "ar -Cx: overwrote pre-existing file"
344	else
345		print "TEST PASSED: ar -Cx did not overwrite existing file"
346	fi
347
348	if ! $ar_prog Cx $ar_f01 ar_test1.o; then
349		warn "ar -Cx: failed to extract file that wasn't in fs\n"
350	fi
351
352	if cmp -s $out1 $ar_data1; then
353		print "TEST PASSED: ar -Cx extracted file that didn't exist"
354	else
355		warn "ar -Cx: failed to extract file that exists"
356	fi
357}
358
359#
360# Variant of -x that ensures we restore stripped archives.
361#
362function test_xs
363{
364	typeset alt="$ar_tmpdir/xs.a"
365	typeset out0="$ar_tmpdir/ar_test0.o"
366	typeset out1="$ar_tmpdir/ar_test1.o"
367
368	rm -f $out0 $out1
369
370	if ! cp $ar_f01 $alt; then
371		warn "failed to copy file"
372		return
373	fi
374
375	if ! strip_archive $alt; then
376		return
377	fi
378
379	if ! $ar_prog xs $alt; then
380		warn "ar -xs: failed to extract files"
381	fi
382
383	if cmp -s $out0 $ar_data0 && cmp -s $out1 $ar_data1 && \
384	    compare_files "$ar_f01" "$alt"; then
385		print "TEST PASSED: ar -xs"
386	else
387		warn "ar -xs: extracted and restore archive differ"
388	fi
389}
390
391function test_m
392{
393	typeset alt="$ar_tmpdir/pos.a"
394
395	if ! cp $ar_f01 $alt; then
396		warn "failed to copy file"
397		return
398	fi
399
400	if ! $ar_prog ma ar_test1.o $alt ar_test0.o; then
401		warn "ar -ma: failed didn't work"
402	fi
403
404	if compare_files "$ar_f10" "$alt"; then
405		print "TEST PASSED: ar -ma"
406	else
407		warn "ar -ma: did not create expected archive"
408	fi
409
410	if ! $ar_prog mb ar_test1.o $alt ar_test0.o; then
411		warn "ar -ma: failed didn't work"
412	fi
413
414	if compare_files "$ar_f01" "$alt"; then
415		print "TEST PASSED: ar -mb"
416	else
417		warn "ar -mb: did not create expected archive"
418	fi
419}
420
421function test_t
422{
423	typeset output=
424
425	output=$($ar_prog t $ar_f01)
426	if [[ "$ar_t01" != "$output" ]]; then
427		warn "ar t: mismatched output on $ar_t01, found $output"
428	else
429		print "TEST PASSED: ar -t (output 01)"
430	fi
431
432	output=$($ar_prog t $ar_f10)
433	if [[ "$ar_t10" != "$output" ]]; then
434		warn "ar t: mismatched output on $ar_f10, found $output"
435	else
436		print "TEST PASSED: ar -t (output 10)"
437	fi
438}
439
440function test_q
441{
442	typeset alt="$ar_tmpdir/q.a"
443
444	if ! $ar_prog q $alt $ar_data1 $ar_data0; then
445		warn "ar -q: did not create an archive"
446	fi
447
448	if ! compare_files $ar_f10 $alt; then
449		warn "ar -q: did not create expected file"
450	else
451		print "TEST PASSED: ar -q creates archives"
452	fi
453
454	rm -f $alt
455
456	if ! $ar_prog cq $alt $ar_data1 $ar_data0; then
457		warn "ar -rs: did not create an archive"
458	fi
459
460	if ! compare_files $ar_f10 $alt; then
461		warn "ar -cq: did not create expected file"
462	else
463		print "TEST PASSED: ar -cq creates archives"
464	fi
465
466	rm -f $alt
467
468	if ! $ar_prog cqs $alt $ar_data1 $ar_data0; then
469		warn "ar -cqs: did not create an archive"
470	fi
471
472	if ! compare_files $ar_f10 $alt; then
473		warn "ar -cqs: did not create expected file"
474	else
475		print "TEST PASSED: ar -cqs creates archives"
476	fi
477
478}
479
480function test_err
481{
482	if $ar_prog $@ 2>/dev/null 1>/dev/null; then
483		warn "should have failed with args "$@", but passed"
484	else
485		printf "TEST PASSED: invalid arguments %s\n" "$*"
486	fi
487}
488
489#
490# Before we begin execution, set up the environment such that we have a
491# standard locale and that umem will help us catch mistakes.
492#
493export LC_ALL=C.UTF-8
494export LD_PRELOAD=libumem.so
495export UMEM_DEBUG=default
496
497if ! mkdir "$ar_tmpdir"; then
498	printf "failed to make output directory %s\n" "$ar_tmpdir" >&2
499	exit 1
500fi
501
502if ! cd "$ar_tmpdir"; then
503	printf "failed to cd into output directory %s\n" "$ar_tmpdir" >&2
504	exit 1
505fi
506
507if [[ ! -d "$ar_data" ]]; then
508	printf "failed to find data directory %s\n" "$ar_data" >&2
509	exit 1
510fi
511
512if [[ -n $AR ]]; then
513	echo overwrote AR as $AR
514	ar_prog=$AR
515fi
516
517setup_baseline
518test_s
519test_rs
520test_incremental
521test_pos
522test_x
523test_xs
524test_m
525test_t
526test_q
527
528#
529# Note, there are many cases here which probably should be failures and
530# aren't (e.g. ar -mabi) because that's how the tool works today. With
531# proper regression testing of building 3rd party packages this could be
532# changed.
533#
534test_err ""
535test_err "-z"
536test_err "-d"
537test_err "-d" "$ar_tmpdir/enoent"
538test_err "-d" "$ar_f01" "foobar.exe"
539test_err "-m" "$ar_tmpdir/enoent"
540test_err "-ma" "foobar.exe" "$ar_tmpdir/enoent"
541test_err "-mb" "foobar.exe" "$ar_tmpdir/enoent"
542test_err "-mi" "foobar.exe" "$ar_tmpdir/enoent"
543test_err "-p" "$ar_tmpdir/enoent"
544test_err "-P" "$ar_tmpdir/enoent"
545test_err "-q"
546test_err "-qa" "foobar.exe" "$ar_f0.a"
547test_err "-qb" "foobar.exe" "$ar_f0.a"
548test_err "-qi" "foobar.exe" "$ar_f0.a"
549test_err "-qa" "ar_test0.o" "$ar_f0.a"
550test_err "-qb" "ar_test0.o" "$ar_f0.a"
551test_err "-qi" "ar_test0.o" "$ar_f0.a"
552test_err "-r"
553test_err "-ra" "foobar.exe"
554test_err "-ra" "foobar.exe" "$ar_tmpdir/enoent"
555test_err "-rb" "foobar.exe"
556test_err "-rb" "foobar.exe" "$ar_tmpdir/enoent"
557test_err "-ri" "foobar.exe"
558test_err "-ri" "foobar.exe" "$ar_tmpdir/enoent"
559test_err "-t"
560test_err "-t" "$ar_tmpdir/enoent"
561test_err "-x"
562test_err "-x" "$ar_tmpdir/enoent"
563test_err "-s"
564test_err "-s" "$ar_tmpdir/enoent"
565test_err "-s" "$ar_f01" "$ar_f10"
566test_err "-sz" "$ar_f01"
567test_err "-rd"
568test_err "-xd"
569test_err "-qp"
570
571if (( ar_exit == 0 )); then
572	printf "All tests passed successfully!\n"
573fi
574
575cd - >/dev/null
576rm -rf "$ar_tmpdir"
577exit $ar_exit
578