Skip to content

Commit bd2f932

Browse files
committed
✨ added time::getSecondsToMicroseconds
1 parent 6e3ab57 commit bd2f932

18 files changed

Lines changed: 341 additions & 272 deletions

File tree

commands.d/self-test-utils

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,15 @@ function selfTestUtils_setupValetForConsistency() {
8484

8585
# shellcheck disable=SC2317
8686
function time::getProgramElapsedMicroseconds() {
87-
if [[ -z ${_FAKE_TIME:-} ]]; then
88-
_FAKE_TIME=0
89-
_TIME_FACTOR=1
87+
if [[ -z ${GLOBAL_FAKE_TIME:-} ]]; then
88+
GLOBAL_FAKE_TIME=0
89+
GLOBAL_FAKE_TIME_INCREMENT=1000000
90+
GLOBAL_FAKE_TIME_INCREMENT_INCREMENT=1000000
91+
REPLY=0
92+
return 0
9093
fi
91-
((_FAKE_TIME=_FAKE_TIME+ 1000000 * _TIME_FACTOR, _TIME_FACTOR++))
92-
REPLY="${_FAKE_TIME}"
94+
((GLOBAL_FAKE_TIME += GLOBAL_FAKE_TIME_INCREMENT, GLOBAL_FAKE_TIME_INCREMENT += GLOBAL_FAKE_TIME_INCREMENT_INCREMENT))
95+
REPLY="${GLOBAL_FAKE_TIME}"
9396
}
9497

9598
# define it so we can check if it has elements when printing the reply values of a function

docs/content/_index.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ keywords:
55
- valet
66
- bash
77
- script
8+
- sh
9+
- shell
810
- windows
911
- search
1012
- string
@@ -27,6 +29,17 @@ keywords:
2729
- bashly
2830
- options-parser
2931
- jcaillon
32+
- test
33+
- testing
34+
- approval-tests
35+
- ci/cd
36+
- production
37+
- automation
38+
- robust
39+
- ble
40+
- prompt
41+
- list
42+
- autocompletion
3043
- framework
3144
description: With Valet, you can setup and execute tests, code interactive experiences for your users, navigate and execute your scripts (called commands) from a searchable menu interface, and more! It provides libraries of functions that can be sourced to solve standard programming needs such as string, array or file manipulation, prompting the user, and so on...
3245
params:

libraries.d/core

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,17 +476,33 @@ function core::createSavedFilePath() {
476476
# - Set the correct traps.
477477
# - Initialize specific temporary files/directories location.
478478
# - Reset the elapsed time to 0.
479+
# - Reset the background processes.
479480
#
480481
# ```bash
481482
# core::initSubshell
482483
# ```
483484
function core::initSubshell() {
484-
trap::registerSubshell
485-
fs::setupSubshellTempFileGlobalVariable
485+
if ((BASH_SUBSHELL == 0)); then
486+
core::fail "This function should only be called in a subshell."
487+
fi
488+
489+
# in case we are in a subshell called within a bash::catchErrors, we reset errexit and not longer catch errors
490+
unset -v GLOBAL_ERROR_TRAP_TRY_MODE_ENABLED
491+
set -o errexit
492+
493+
# reset the background processes
494+
# shellcheck disable=SC2034
495+
declare -g -A \
496+
GLOBAL_BACKGROUND_PROCESSES=() \
497+
GLOBAL_BACKGROUND_PROCESSES_LOGS=()
498+
486499
# shellcheck disable=SC2034
487500
declare -g \
488501
GLOBAL_PROGRAM_STARTED_AT_SECOND="${EPOCHREALTIME%%[.,]*}" \
489502
GLOBAL_PROGRAM_STARTED_AT_MICROSECOND="${EPOCHREALTIME##*[.,]}"
503+
504+
trap::registerSubshell
505+
fs::setupSubshellTempFileGlobalVariable
490506
}
491507

492508
# ## core::dump

libraries.d/core-lib

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ function fs::setupTempFileGlobalVariable() {
4040
# of a subshell. We keep everything under a new directory named with the process ID;
4141
# the dir gets cleanup when the main process ends.
4242
function fs::setupSubshellTempFileGlobalVariable() {
43-
if ((BASH_SUBSHELL == 0)); then
44-
core::fail "This function should only be called in a subshell."
45-
fi
4643
TMPDIR="${GLOBAL_TEMPORARY_DIRECTORY}/job-${BASHPID}"
4744
mkdir -p "${TMPDIR}" 1>/dev/null
4845
unset -v VALET_CONFIG_RUNTIME_DIRECTORY VALET_CONFIG_TEMP_DIRECTORY XDG_RUNTIME_DIR

libraries.d/core-traps

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -424,10 +424,11 @@ function trap::killBackgroundProcesses() {
424424
fi
425425
done
426426

427-
# send SIGHUP to all background processes to terminate them
427+
# send SIGINT and SIGHUP to all background processes to terminate them
428428
for coprocName in "${!GLOBAL_BACKGROUND_PROCESSES[@]}"; do
429429
coprocPid="${GLOBAL_BACKGROUND_PROCESSES[${coprocName}]:-}"
430430
log::debug "Killing background process ${coprocName} with PID ${coprocPid}."
431+
kill -INT "${coprocPid}" &>/dev/null || :
431432
kill -HUP "${coprocPid}" &>/dev/null || :
432433
done
433434

@@ -483,17 +484,6 @@ function trap::killBackgroundProcesses() {
483484
# > - coproc and jobs will not get SIGHUP when the main process ends and they will continue to execute
484485
# > until killed or until they check that the main process is dead.
485486
function trap::registerSubshell() {
486-
if ((BASH_SUBSHELL == 0)); then
487-
core::fail "This function should only be called in a subshell."
488-
fi
489-
490-
# in case we are in a subshell called within a bash::catchErrors, we reset errexit and not longer catch errors
491-
unset -v GLOBAL_ERROR_TRAP_TRY_MODE_ENABLED
492-
set -o errexit
493-
494-
# reset the background processes
495-
declare -g -A GLOBAL_BACKGROUND_PROCESSES=() GLOBAL_BACKGROUND_PROCESSES_LOGS=()
496-
497487
trap '_TRAP_SUBSHELL_EXIT_REASON=ERROR trap::onErrorInternal' ERR
498488
trap trap::onSubshellExitInternal EXIT
499489
# Register CTRL+C SIGINT (interrupt) and CTRL+\ QUIT (interrupt with a coredump) event handler

libraries.d/lib-coproc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ function coproc::runInParallel() {
197197
#
198198
# - Run a main command (mainCommand) that is run once at the start of the coproc.
199199
# - Loop until the main thread or the coproc exits. Inside the loop, the coproc can receive messages
200-
# from the main thread. In the loop, more commands are run:
200+
# from the main thread, and more commands are run:
201201
# - A loop command (loopCommand) is run at the beginning of each iteration of the loop.
202202
# - An on message command (onMessageCommand) is run when a message is received from the main thread.
203203
# - Finally, it runs an end command (endCommand) once at the end of the coproc.
@@ -268,7 +268,7 @@ function coproc::run() {
268268
shift 1
269269
eval "local a= ${*@Q}"
270270

271-
if [[ -v "GLOBAL_BACKGROUND_PROCESSES[${coprocVarName}]" ]]; then
271+
if [[ -v "GLOBAL_BACKGROUND_PROCESSES[${coprocVarName}]" ]] && coproc::isRunning "${coprocVarName}"; then
272272
core::fail "The coproc ⌜${coprocVarName}⌝ is already running."
273273
fi
274274

@@ -414,6 +414,8 @@ function coproc::sendMessage() {
414414
#
415415
# - $1: **coproc variable name** _as string_:
416416
# The variable name to use for the coproc.
417+
# - $@: read parameters _as any_:
418+
# The arguments to pass to the read command (e.g. -t for timeout).
417419
#
418420
# Returns:
419421
#
@@ -429,10 +431,11 @@ function coproc::sendMessage() {
429431
# ```
430432
function coproc::receiveMessage() {
431433
local coprocVarName="${1?"The function ⌜${FUNCNAME:-?}⌝ requires more than $# arguments."}"
434+
shift 1
432435

433436
REPLY=""
434437
if eval "local coprocFd=\${${coprocVarName}[0]:-}" && [[ -n ${coprocFd} ]]; then
435-
if { IFS=$'\0' read -rd $'\0' -u "${coprocFd}" REPLY; } 2>/dev/null; then
438+
if { IFS=$'\0' read "$@" -rd $'\0' -u "${coprocFd}" REPLY; } 2>/dev/null; then
436439
return 0
437440
fi
438441
fi

libraries.d/lib-terminal

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ function terminal::testWaitForChar() {
451451
# to be set using `terminal::setRawMode`.
452452
#
453453
#
454-
# - $@: **read parameters** _as any_:
454+
# - $@: read parameters _as any_:
455455
# additional parameters to pass to the read command
456456
#
457457
# Returns:
@@ -535,7 +535,7 @@ function terminal::waitForChar() {
535535
# You must call `terminal::rebindKeymap` and `terminal::setRawMode` before using this function.
536536
# You should use `tui::start` instead of using this function directly.
537537
#
538-
# - $@: **read parameters** _as any_:
538+
# - $@: read parameters _as any_:
539539
# additional parameters to pass to the read command
540540
#
541541
# Returns:

libraries.d/lib-test

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,35 @@ function test::setTerminalInputs() {
538538
TEST_TERMINAL_INPUTS=("${@}")
539539
}
540540

541+
# ## test::setProgramElapsedFunction (private)
542+
#
543+
# Sets properties for the mocked `time::getProgramElapsedMicroseconds` function.
544+
#
545+
# - $1: **current value** _as int_:
546+
# The current elapsed time in microseconds.
547+
# - ${increment} _as int_:
548+
# The increment to add to the elapsed time at each call.
549+
# - ${incrementIncrement} _as int_:
550+
# The increment to add to the increment at each call.
551+
#
552+
# ```bash
553+
# test::setProgramElapsedFunction currentValue=0 increment=1000000 incrementIncrement=1000000
554+
# ```
555+
# shellcheck disable=SC2034
556+
function test::setProgramElapsedFunction() {
557+
local \
558+
currentValue="${1:-0}" \
559+
increment="1000000" \
560+
incrementIncrement="1000000" \
561+
IFS=$' '
562+
shift 1
563+
eval "local a= ${*@Q}"
564+
565+
GLOBAL_FAKE_TIME="${currentValue}"
566+
GLOBAL_FAKE_TIME_INCREMENT="${increment}"
567+
GLOBAL_FAKE_TIME_INCREMENT_INCREMENT="${incrementIncrement}"
568+
}
569+
541570
# ## test::setTestCallStack (private)
542571
#
543572
# This function is used to set the call stack for the tests.

libraries.d/lib-time

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,85 @@ function time::getMicrosecondsToSeconds() {
211211
printf -v REPLY "0.%s" "${REPLY:0:${precision}}"
212212
fi
213213
}
214+
215+
# ## time::getSecondsToMicroseconds
216+
#
217+
# Convert a seconds float to microseconds integer.
218+
# e.g. 1.234567 → 1234567
219+
#
220+
# - $1: **seconds** _as float_:
221+
# the seconds to convert
222+
#
223+
# Returns:
224+
#
225+
# - ${REPLY}: The microseconds (integer number).
226+
#
227+
# ```bash
228+
# time::getSecondsToMicroseconds 1.234567
229+
# echo "${REPLY}"
230+
# ```
231+
function time::getSecondsToMicroseconds() {
232+
local seconds="${1?"The function ⌜${FUNCNAME:-?}⌝ requires more than $# arguments."}"
233+
234+
local integerPart="${seconds%%.*}"
235+
local decimalPart="${seconds#*.}"
236+
if [[ ${integerPart} == "${seconds}" ]]; then
237+
decimalPart="0"
238+
fi
239+
printf -v decimalPart "%-06s" "${decimalPart}"
240+
decimalPart="${decimalPart// /0}"
241+
REPLY="$((integerPart * 1000000 + 10#${decimalPart:0:6}))"
242+
}
243+
244+
# ## time::isTimeElapsed
245+
#
246+
# Check if a given time in microseconds has elapsed since the last call
247+
# to this function.
248+
#
249+
# - $1: **microseconds** _as int_:
250+
# the microseconds to check
251+
# - ${timerName} _as int_:
252+
# A variable name that will be used to store the last time this function was called.
253+
# Defaults to the name of the calling function.
254+
# Can be set to a fixed value if you call this function from different functions
255+
# and want to share the same timer.
256+
# (defaults to "${FUNCNAME[1]}")
257+
#
258+
# Returns:
259+
#
260+
# - 0 if the time has elapsed
261+
# - 1 if the time has not yet elapsed
262+
#
263+
# ```bash
264+
# if time::isTimeElapsed 500000; then
265+
# echo "500ms has elapsed since the last call to this function"
266+
# fi
267+
# ```
268+
function time::isTimeElapsed() {
269+
local \
270+
microseconds="${1?"The function ⌜${FUNCNAME:-?}⌝ requires more than $# arguments."}" \
271+
timerName="${FUNCNAME[1]:-main}" \
272+
IFS=" "
273+
shift 1
274+
eval "local a= ${*@Q}"
275+
276+
timerName="${timerName//[^a-zA-Z0-9_]/_}"
277+
278+
local REPLY
279+
time::getProgramElapsedMicroseconds
280+
281+
if [[ ! -v _TIME_IS_TIME_ELAPSED_${timerName} ]]; then
282+
# initialize the variable to 0 if it does not exist
283+
declare -g "_TIME_IS_TIME_ELAPSED_${timerName}=${REPLY}"
284+
return 1
285+
fi
286+
287+
local -n lastTime="_TIME_IS_TIME_ELAPSED_${timerName}"
288+
289+
if ((REPLY - lastTime >= microseconds)); then
290+
lastTime="${REPLY}"
291+
return 0
292+
fi
293+
294+
return 1
295+
}

0 commit comments

Comments
 (0)