#!/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 2019 Robert Mustacchi
# Copyright 2020 Joyent, Inc.
#

#
# Basic tests of sleep(1). sleep is a little hard to test, especially
# for longer running cases. Therefore to test it, we basically take
# advantage of our knowledge of how it is implemented. We see that it
# properly is sleeping for the right amount of time by looking at the
# call to nanosleep in libc and make sure that the structures time is
# what we expect.
#

unalias -a
set -o pipefail

#
# Set the locale for the start of the test to be C.UTF-8 to make sure
# that we have a good starting point and correct fractional
# interpretation.
#
export LC_ALL=C.UTF-8

sleep_arg0="$(basename $0)"
sleep_prog=/usr/bin/sleep
sleep_dir="$(dirname $0)"
sleep_dscript=$sleep_dir/sleep.d
sleep_awk=$sleep_dir/sleep.awk
sleep_exit=0

#
# This is the factor by which we're going to basically say that the slp
# microstate has to complete within. Because the system will usually
# have a bit of additional latency, we will usually be greater than that
# as well. This determines how much we should actually do that by.
#
sleep_factor=1.5

warn()
{
	typeset msg="$*"
	[[ -z "$msg" ]] && msg="failed"
	echo "TEST FAILED: $sleep_arg0: $msg" >&2
}

sleep_bound()
{
	typeset min=$1
	typeset test="sleep $min: bounding"

	ptime -m $sleep_prog $min 2>&1 | nawk -f $sleep_awk min=$min \
	    factor=$sleep_factor
	if [[ $? -ne 42 ]]; then
		warn "$test"
		sleep_exit=1
	else
		printf "TEST PASSED: %s\n" "$test"
	fi
}

sleep_one()
{
	typeset arg=$1
	typeset secs=$2
	typeset nsecs=$3
	typeset test="sleep $arg: $secs secs $nsecs ns"

	if ! dtrace -qws $sleep_dscript -c "$sleep_prog $arg" $secs $nsecs; then
		warn "$test"
		sleep_exit=1
	else
		printf "TEST PASSED: %s\n" "$test"
	fi
}

sleep_err()
{
	typeset test="negative test: sleep $*"

	if $sleep_prog $* 2>/dev/null; then
		warn "$test"
		sleep_exit=1
	else
		printf "TEST PASSED: %s\n" "$test"
	fi
}

if [[ -n $SLEEP ]]; then
	sleep_prog=$SLEEP
fi

#
# First test basic integer values. Both in base 10 and hex.
#
sleep_one 1 1 0
sleep_one 23 23 0
sleep_one 0xff 0xff 0
sleep_one 123456789 123456789 0
sleep_one 1e8 100000000 0

#
# Fractional values.
#
sleep_one 2.5 2 500000000
sleep_one 0.9 0 900000000
sleep_one 34.0051 34 5100000
sleep_one 0x654.100 0x654 62500000

#
# Large values that are basically the same as infinity. The current
# implementation will do a sleep in groups of INT32_MAX at a time. So
# make sure our large values are the same.
#
sleep_one Inf 0x7fffffff 0
sleep_one +Inf 0x7fffffff 0
sleep_one 1e100 0x7fffffff 0
sleep_one 0x123456789abc 0x7fffffff 0

#
# That all of our suffixes for time increments work and make sense.
#
sleep_one 1s 1 0
sleep_one 1m 60 0
sleep_one 1h 3600 0
sleep_one 1d 86400 0
sleep_one 1w 604800 0
sleep_one 1y 31536000 0

sleep_one 3.5s 3 500000000
sleep_one 3.6d 311040 0
sleep_one 2.001y 63103536 0

#
# Now we need to go through and use ptime -m to get the slp time for
# things and make sure it is always greater than what we asked for and
# less than a bound.
#
sleep_bound 0.01
sleep_bound 0.1
sleep_bound 0.25
sleep_bound 0.5
sleep_bound 0.75

#
# The next set of tests are negative tests that make sure that sleep
# does not correctly execute in these cases.
#
sleep_err \"\"
sleep_err 1 2 3
sleep_err 1@23
sleep_err 0,56
sleep_err "hello"
sleep_err s
sleep_err 1z
sleep_err -- -0.3

#
# Test a locale that uses a ',' character (de_DE.UTF-8 is one) as the
# decimal point to make sure that sleep is correctly using LC_NUMERIC.
export LC_ALL=de_DE.UTF-8
sleep_err 21.45
sleep_one 2,5 2 500000000
sleep_one 34,0051 34 5100000
sleep_one 3,6d 311040 0
export LC_ALL=C.UTF-8

exit $sleep_exit