#!/usr/bin/ksh
#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source.  A copy of the CDDL is also available via the Internet at
# http://www.illumos.org/license/CDDL.
#

#
# Copyright 2021 Oxide Computer Company
#

#
# This contains a number of basic tests for ar(1). When adding something
# to ar or fixing a bug, please expand this!
#

unalias -a
set -o pipefail

ar_arg0="$(basename $0)"
ar_data="$(dirname $0)"
ar_data0="$ar_data/ar_test0.o"
ar_data1="$ar_data/ar_test1.o"
ar_prog=/usr/bin/ar
ar_tmpdir=/tmp/ar.$$

ar_f01="$ar_tmpdir/test01.a"
ar_f10="$ar_tmpdir/test10.a"

ar_t01="ar_test0.o
ar_test1.o"
ar_t10="ar_test1.o
ar_test0.o"

strip_prog=/usr/bin/strip
dump_prog=/usr/bin/dump
nm_prog=/usr/bin/nm

ar_exit=0

function warn
{
	typeset msg="$*"
	[[ -z "$msg" ]] && msg="failed"
	print -u2 "TEST FAILED: $ar_arg0: $msg"
	ar_exit=1
}

function compare_files
{
	typeset base="$1"
	typeset comp="$2"
	typeset err=0

	if ! $dump_prog -g $comp | sed 1d > $comp.dump; then
		warn "failed to dump -g $comp"
		err=1
	fi

	if ! $nm_prog -P -tx $comp > $comp.nm; then
		warn "failed to nm $comp"
		err=1
	fi

	if ! diff $base.dump $comp.dump; then
		warn "$base.dump and $comp.dump don't match"
		err=1
	fi

	if ! diff $base.nm $comp.nm; then
		warn "$base.dump and $comp.dump don't match"
		err=1
	fi

	return $err
}

#
# To set things up, we first go and create two basic archives that we
# will then use as the basis for comaring various operations later.
#
function setup_baseline
{
	if ! $ar_prog cr $ar_f01 $ar_data0 $ar_data1; then
		warn "failed to create basic archive $ar_f01"
	fi

	if ! $ar_prog cr $ar_f10 $ar_data1 $ar_data0; then
		warn "failed to create basic archive $ar_f10"
	fi

	if ! $dump_prog -g $ar_f01 | sed 1d > $ar_f01.dump; then
		warn "failed to dump archive $ar_f01"
	fi

	if ! $dump_prog -g $ar_f10 | sed 1d > $ar_f10.dump; then
		warn "failed to dump archive $ar_f10"
	fi

	if ! $nm_prog -P -tx $ar_f01 > $ar_f01.nm; then
		warn "failed to nm archive $ar_f01"
	fi

	if ! $nm_prog -P -tx $ar_f10 > $ar_f10.nm; then
		warn "failed to nm archive $ar_f01"
	fi

	print "TEST PASSED: basic archive creation"
}

function strip_archive
{
	typeset file="$1"
	typeset output=

	if ! $strip_prog $file 2>/dev/null; then
		warn "failed to strip $alt"
		return 1
	fi

	output=$($dump_prog -g $file)
	if [[ -n "$output" ]]; then
		warn "stripped file $file somehow has an ar header"
		return 1
	fi

	return 0
}

#
# Validate that stripping and regenerating a symbol table actually
# works.
#
function test_s
{
	typeset alt="$ar_tmpdir/s.a"
	typeset output=

	if ! cp $ar_f01 $alt; then
		warn "failed to copy file"
		return
	fi

	if ! strip_archive $alt; then
		return
	fi

	if ! $ar_prog s $alt; then
		warn "restore symbol table with ar s"
	fi

	if compare_files "$ar_f01" "$alt"; then
		print "TEST PASSED: restoring stripped archive with -s"
	fi

	if ! strip_archive $alt; then
		return
	fi

	if ! $ar_prog st $alt >/dev/null; then
		warn "restore symbol table with ar st"
	fi

	if compare_files "$ar_f01" "$alt"; then
		print "TEST PASSED: restoring stripped archive with -st"
	fi

	if ! strip_archive $alt; then
		return
	fi

	output=$($ar_prog sv $alt 2>&1)
	if [[ "$output" == "ar: writing $alt" ]]; then
		print "TEST PASSED: ar -sv has proper output"
	else
		warn "ar -sv has unexpected output: $output"
	fi

	if compare_files "$ar_f01" "$alt"; then
		print "TEST PASSED: restoring stripped archive with -sv"
	fi
}

#
# Ensure that use of -s and -r still works. This is a regression test
# for the original integration of standalone -s support.
#
function test_rs
{
	typeset alt="$ar_tmpdir/rs.a"

	if ! $ar_prog rs $alt $ar_data1 $ar_data0; then
		warn "ar -rs: did not create an archive"
	fi

	if ! compare_files $ar_f10 $alt; then
		warn "ar -rs: did not create expected file"
	else
		print "TEST PASSED: ar -rs creates archives"
	fi

	rm -f $alt

	if ! $ar_prog crs $alt $ar_data1 $ar_data0; then
		warn "ar -crs: did not create an archive"
	fi

	if ! compare_files $ar_f10 $alt; then
		warn "ar -crs: did not create expected file"
	else
		print "TEST PASSED: ar -crs creates archives"
	fi
}

#
# Verify that basic ar -r invocations ultimately end up creating what
# we'd expect.
#
function test_incremental
{
	typeset alt="$ar_tmpdir/incr.a"

	if ! $ar_prog cr $alt $ar_data0; then
		warn "incremental archive: failed to create archive"
		return
	fi

	if ! $ar_prog cr $alt $ar_data1; then
		warn "incremental archive: failed to add to archive"
		return
	fi

	if ! compare_files $ar_f01 $alt; then
		warn "incremental archive: did not create expected file"
	else
		print "TEST PASSED: incremental archive creation"
	fi

}

#
# Validate that ar's various -a and -b variants work.
#
function test_pos
{
	typeset alt="$ar_tmpdir/pos.a"

	if ! $ar_prog cr $alt $ar_data1; then
		warn "positional tests: failed to create archive"
		return;
	fi

	if ! $ar_prog -cra ar_test1.o $alt $ar_data0; then
		warn "positional tests: -a append failed"
		return
	fi

	if ! compare_files $ar_f10 $alt; then
		warn "positional tests: -cra archive is incorrect"
	else
		print "TEST PASSED: positional tests: ar -cra"
	fi

	rm -f $alt

	if ! $ar_prog cr $alt $ar_data1; then
		warn "positional tests: failed to create archive"
		return;
	fi

	if ! $ar_prog -crb ar_test1.o $alt $ar_data0; then
		warn "positional tests: -b prepend failed"
		return
	fi

	if ! compare_files $ar_f01 $alt; then
		warn "positional tests: -crb archive is incorrect"
	else
		print "TEST PASSED: positional tests: ar -crb"
	fi

	rm -f $alt

	if ! $ar_prog cr $alt $ar_data1; then
		warn "positional tests: failed to create archive"
		return;
	fi

	if ! $ar_prog -cri ar_test1.o $alt $ar_data0; then
		warn "positional tests: -i prepend failed"
		return
	fi

	if ! compare_files $ar_f01 $alt; then
		warn "positional tests: -cri archive is incorrect"
	else
		print "TEST PASSED: positional tests: ar -cri"
	fi

}

#
# Go through and validate the various versions of ar x.
#
function test_x
{
	typeset out0="$ar_tmpdir/ar_test0.o"
	typeset out1="$ar_tmpdir/ar_test1.o"
	typeset output=

	rm -f $out0 $out1

	if ! $ar_prog x $ar_f01; then
		warn "ar -x: failed to extract files"
	fi

	if cmp -s $out0 $ar_data0 && cmp -s $out1 $ar_data1; then
		print "TEST PASSED: ar -x"
	else
		warn "ar -x: extracted files differs"
	fi

	rm -f $out0 $out1
	echo elberth > $out0

	#
	# For some reason, ar -Cx will actually fail if you have an
	# existing file. It seems a bit weird as it means you can't
	# extract existing files (depdendent on order), but that's how
	# it goes.
	#
	if $ar_prog Cx $ar_f01 ar_test0.o; then
		warn "ar -Cx: failed to extract file that wasn't in fs\n"
	fi

	output=$(cat $out0)
	if [[ "$output" != "elberth" ]]; then
		warn "ar -Cx: overwrote pre-existing file"
	else
		print "TEST PASSED: ar -Cx did not overwrite existing file"
	fi

	if ! $ar_prog Cx $ar_f01 ar_test1.o; then
		warn "ar -Cx: failed to extract file that wasn't in fs\n"
	fi

	if cmp -s $out1 $ar_data1; then
		print "TEST PASSED: ar -Cx extracted file that didn't exist"
	else
		warn "ar -Cx: failed to extract file that exists"
	fi
}

#
# Variant of -x that ensures we restore stripped archives.
#
function test_xs
{
	typeset alt="$ar_tmpdir/xs.a"
	typeset out0="$ar_tmpdir/ar_test0.o"
	typeset out1="$ar_tmpdir/ar_test1.o"

	rm -f $out0 $out1

	if ! cp $ar_f01 $alt; then
		warn "failed to copy file"
		return
	fi

	if ! strip_archive $alt; then
		return
	fi

	if ! $ar_prog xs $alt; then
		warn "ar -xs: failed to extract files"
	fi

	if cmp -s $out0 $ar_data0 && cmp -s $out1 $ar_data1 && \
	    compare_files "$ar_f01" "$alt"; then
		print "TEST PASSED: ar -xs"
	else
		warn "ar -xs: extracted and restore archive differ"
	fi
}

function test_m
{
	typeset alt="$ar_tmpdir/pos.a"

	if ! cp $ar_f01 $alt; then
		warn "failed to copy file"
		return
	fi

	if ! $ar_prog ma ar_test1.o $alt ar_test0.o; then
		warn "ar -ma: failed didn't work"
	fi

	if compare_files "$ar_f10" "$alt"; then
		print "TEST PASSED: ar -ma"
	else
		warn "ar -ma: did not create expected archive"
	fi

	if ! $ar_prog mb ar_test1.o $alt ar_test0.o; then
		warn "ar -ma: failed didn't work"
	fi

	if compare_files "$ar_f01" "$alt"; then
		print "TEST PASSED: ar -mb"
	else
		warn "ar -mb: did not create expected archive"
	fi
}

function test_t
{
	typeset output=

	output=$($ar_prog t $ar_f01)
	if [[ "$ar_t01" != "$output" ]]; then
		warn "ar t: mismatched output on $ar_t01, found $output"
	else
		print "TEST PASSED: ar -t (output 01)"
	fi

	output=$($ar_prog t $ar_f10)
	if [[ "$ar_t10" != "$output" ]]; then
		warn "ar t: mismatched output on $ar_f10, found $output"
	else
		print "TEST PASSED: ar -t (output 10)"
	fi
}

function test_err
{
	if $ar_prog $@ 2>/dev/null 1>/dev/null; then
		warn "should have failed with args "$@", but passed"
	else
		printf "TEST PASSED: invalid arguments %s\n" "$*"
	fi
}

#
# Before we begin execution, set up the environment such that we have a
# standard locale and that umem will help us catch mistakes.
#
export LC_ALL=C.UTF-8
export LD_PRELOAD=libumem.so
export UMEM_DEBUG=default

if ! mkdir "$ar_tmpdir"; then
	printf "failed to make output directory %s\n" "$ar_tmpdir" >&2
	exit 1
fi

if ! cd "$ar_tmpdir"; then
	printf "failed to cd into output directory %s\n" "$ar_tmpdir" >&2
	exit 1
fi

if [[ ! -d "$ar_data" ]]; then
	printf "failed to find data directory %s\n" "$ar_data" >&2
	exit 1
fi

if [[ -n $AR ]]; then
	echo overwrote AR as $AR
	ar_prog=$AR
fi

setup_baseline
test_s
test_rs
test_incremental
test_pos
test_x
test_xs
test_m
test_t

#
# Note, there are many cases here which probably should be failures and
# aren't (e.g. ar -mabi) because that's how the tool works today. With
# proper regression testing of building 3rd party packages this could be
# changed.
#
test_err ""
test_err "-z"
test_err "-d"
test_err "-d" "$ar_tmpdir/enoent"
test_err "-d" "$ar_f01" "foobar.exe"
test_err "-m" "$ar_tmpdir/enoent"
test_err "-ma" "foobar.exe" "$ar_tmpdir/enoent"
test_err "-mb" "foobar.exe" "$ar_tmpdir/enoent"
test_err "-mi" "foobar.exe" "$ar_tmpdir/enoent"
test_err "-p" "$ar_tmpdir/enoent"
test_err "-P" "$ar_tmpdir/enoent"
test_err "-q"
test_err "-qa" "foobar.exe" "$ar_f0.a"
test_err "-qb" "foobar.exe" "$ar_f0.a"
test_err "-qi" "foobar.exe" "$ar_f0.a"
test_err "-qa" "ar_test0.o" "$ar_f0.a"
test_err "-qb" "ar_test0.o" "$ar_f0.a"
test_err "-qi" "ar_test0.o" "$ar_f0.a"
test_err "-r"
test_err "-ra" "foobar.exe"
test_err "-ra" "foobar.exe" "$ar_tmpdir/enoent"
test_err "-rb" "foobar.exe"
test_err "-rb" "foobar.exe" "$ar_tmpdir/enoent"
test_err "-ri" "foobar.exe"
test_err "-ri" "foobar.exe" "$ar_tmpdir/enoent"
test_err "-t"
test_err "-t" "$ar_tmpdir/enoent"
test_err "-x"
test_err "-x" "$ar_tmpdir/enoent"
test_err "-s"
test_err "-s" "$ar_tmpdir/enoent"
test_err "-s" "$ar_f01" "$ar_f10"
test_err "-sz" "$ar_f01"
test_err "-rd"
test_err "-xd"
test_err "-qp"

if (( ar_exit == 0 )); then
	printf "All tests passed successfully!\n"
fi

cd - >/dev/null
rm -rf "$ar_tmpdir"
exit $ar_exit
