Add a byte to hex function with unit test

This commit is contained in:
Wesley Moore 2025-02-10 12:03:11 +10:00
parent ff3e779f22
commit c059620b09
No known key found for this signature in database
6 changed files with 356 additions and 2 deletions

View file

@ -3,14 +3,26 @@ AS=riscv64-unknown-elf-as
ASFLAGS=-g -mabi=ilp32e -march=rv32ec
CFLAGS=$(ASFLAGS)
LD=riscv64-unknown-elf-ld
export JQ?=jaq
export RV32EMU?=$(HOME)/Source/github.com/sysprog21/rv32emu/build/rv32emu
all: calc.elf
check: tests
@tests/unittest
hello.elf: hello.o
$(LD) -m elf32lriscv $^ -o $@
calc.elf: calc.o
calc.elf: hex.o calc.o
$(LD) -m elf32lriscv -T link.ld $^ -o $@
tests: tests/btohex.elf
tests/btohex.elf: hex.o tests/btohex.o
$(LD) -m elf32lriscv -T link.ld $^ -o $@
%.o : %.s
$(AS) $(ASFLAGS) $< -o $@
.PHONY: check tests

13
calc.s
View file

@ -20,7 +20,7 @@ regnames:
.section .bss
buf: .skip 20 # room for 20 byte string
# x10: ABCDEFG\n is 23 chars
# x10: ABCDEFGH\n is 14 chars
.section .data
buflen: .byte 0 # length of buf string
@ -160,3 +160,14 @@ memcpy_loop:
j memcpy_loop
memcpy_done:
ret
# Write hex representation into buffer
# arguments:
# a0: value to format
# a1: address of buffer to write to
# temporaries used:
# t0
# return:
# none
tohex:

29
hex.s Normal file
View file

@ -0,0 +1,29 @@
.global btohex
.section .rodata
hexchars:
.ascii "0123456789ABCDEF"
.text
# Convert byte to ASCII hex
# arguments:
# a0: value to format
# temporaries used:
# t0, t1
# return:
# a0: value in ASCII hex
btohex:
andi t0, a0, 0xF # Mask off lower nybble
la a1, hexchars # load address of hexchars
add a2, a1, t0 # offset to nybble
lbu t0, 0(a2) # load hex char at offset
srli t1, a0, 4 # shift upper nybble down
andi t1, t1, 0xF # mask off upper nybble
add a2, a1, t1 # offset to nybble
lbu t1, 0(a2) # load hex char at offset
mv a0, t1 # copy upper char to a0
slli a0, a0, 8 # shuft upper char up
or a0, a0, t0 # OR the lower char into a0
ret

20
tests/btohex.s Normal file
View file

@ -0,0 +1,20 @@
# Test for btohex
.org 0
# Provide program starting address to linker
.global _start
.extern btohex
/* newlib system calls */
.set SYSEXIT, 93
.text
_start:
li a0, 0xA5
jal btohex
mv a1, a0
li t0, SYSEXIT # "exit" syscall
la a0, 0 # Use 0 return code
ecall # invoke syscall to terminate the program

10
tests/test_btohex.sh Normal file
View file

@ -0,0 +1,10 @@
#!/bin/sh
test_btohex() {
# FIXME: Remove grep when this bug is fixed:
# https://github.com/sysprog21/rv32emu/issues/561
result=$("${RV32EMU}" -d - -q tests/btohex | grep -v '^\d' | "${JQ}" .x11)
test $? -eq 0 && test "${result}" -eq 16693 # 16693 is A5 in ASCII
}

272
tests/unittest Executable file
View file

@ -0,0 +1,272 @@
#!/bin/sh
#
# unittest - unit tests framework for shell scripts.
#
# https://github.com/macie/unittest.sh
#
# Copyright (c) 2014-2023 Maciej Żok <maciek.zok@gmail.com>
# MIT License (http://opensource.org/licenses/MIT)
#
# MESSAGES
#
unittest__print_result() {
# Write test status message to stdout
# $1 - test location
# $2 - test status
# prefix: utt9t_
utt9t_color_default=''
utt9t_color_location=''
utt9t_color_status=''
if { [ -n "${CLICOLOR_FORCE}" ] && [ "${CLICOLOR_FORCE}" -ne 0 ]; } || { [ -t 1 ] && [ -z "${NO_COLOR}" ]; }; then # stdout is interactive terminal
utt9t_color_default='\033[0m'
case $2 in
PASS) # location: default; status: green
utt9t_color_location='\033[0m'
utt9t_color_status='\033[32m'
;;
FAIL) # location: red; status: white on red
utt9t_color_location='\033[31m'
utt9t_color_status='\033[97;41m'
;;
SKIP) # location: gray; status: gray
utt9t_color_location='\033[37m'
utt9t_color_status='\033[37m'
;;
esac
fi
printf "${utt9t_color_location}%s\t${utt9t_color_status}%s${utt9t_color_default}\n" "$1" "$2"
unset -v utt9t_color_default utt9t_color_location utt9t_color_status
return 0
}
unittest__print_debug() {
# $1 - category
# $2-... - paragraphs
# prefix: utt13o_
utt13o_color=''
utt13o_color_default=''
if { [ -n "${CLICOLOR_FORCE}" ] && [ "${CLICOLOR_FORCE}" -ne 0 ]; } || { [ -t 2 ] && [ -z "${NO_COLOR}" ]; }; then # stderr is interactive terminal
utt13o_color='\033[34m' # blue
utt13o_color_default='\033[0m'
utt13o_color_quote='\033[37m' # gray
fi
printf "\n${utt13o_color}-- %s${utt13o_color_default}\n\n" "$1" >&2
shift 1
for utt13o_paragraph in "$@"; do
# shellcheck disable=SC2059
case ${utt13o_paragraph} in
' '*) printf "${utt13o_color_quote}" >&2 ;;
*) ;;
esac
printf "%s${utt13o_color_default}\n\n" "${utt13o_paragraph}" >&2
done
unset -v utt13o_paragraph utt13o_color utt13o_color_default
return 0
}
#
# ASSERTIONS
#
# shellcheck disable=SC2317
test() {
# same arguments as command test(1)
UT4e1_test_error_msg=$(/bin/test "$@" 2>&1)
case $? in
0) ;;
1)
unittest__print_debug "FAILED TEST [${UNITTEST_CURRENT}]" \
'I expected:' \
" test$(printf " '%s'" "$@")" \
'to be true, but the result was false.'
return 1
;;
*)
unittest__print_debug "INVALID ASSERTION [${UNITTEST_CURRENT}]" \
'I tried to check' \
" test$(printf " '%s'" "$@")" \
'but I got error with message:' \
" ${UT4e1_test_error_msg}" \
'Did you use proper operator?' \
"Hint: Some operators requires specific type of values. Read 'man test' to learn more."
return 1
;;
esac
return 0
}
#
# FUNCTIONS
#
##
# Find files with tests (test_*.sh).
# SYNOPSIS:
# unittest__test_files [directory...]
# OPERANDS:
# directory - A pathname of directory to search in. If no directory is
# given, it will look for 'tests' directory inside current one. If
# a directory is '-', it will use stdin.
# STDIN:
# (optional) List of directories to search in. Used when call with '-' argument.
# STDOUT:
# List of 'test_*.sh' file paths.
# STDERR:
# (optional) Debug/error message.
# EXIT STATUS:
# 0 - Successfully traversed all directories.
# >0 - An error occurred.
# EXAMPLES:
# unittest__test_files ./unit_tests ./integration_tests
# ls ../ | unittest__test_files -
# CAVEATS:
# Calling it inside pipeline without '-' will disregards standard input and
# use defaults instead.
unittest__test_files() {
{
if [ "$1" = '-' ]; then
cat -
else
printf '%s\n' "$@"
fi
} |
while read -r unittest_dir; do
if ! find "${unittest_dir:-./}" -path "*${unittest_dir:-tests/}*" -name 'test_*.sh' 2>/dev/null; then
unittest__print_debug 'TESTS NOT FOUND' \
"I was looking for 'test_*.sh' files inside '${unittest_dir:-tests/}' directory using:" \
" $ find \"${unittest_dir:-./}\" -path \"*${unittest_dir:-tests/}*\" -name 'test_*.sh' -print" \
'but instead of files I got an error with message:' \
" $(find "${unittest_dir:-./}" -path "*${unittest_dir:-tests/}*" -name 'test_*.sh' -print 2>&1)"
unset -v unittest_dir
return 1
fi
unset -v unittest_dir
done
return $?
}
##
# Run tests from given files.
# STDIN: List of files.
# STDOUT: Test name with status.
# STDERR: (optional) Debug/error message.
# EXIT STATUS:
# 0 - All tests passed.
# >0 - Some tests failed.
##
unittest__run() {
# prefix: utt8r_
UNITTEST_STATUS=0
while read -r utt8r_testfile; do
(
utt8r_beforeAll=$(sed -n 's/^[ \t]*\(beforeAll\)[ \t]*(.*/\1/p' "${utt8r_testfile}")
utt8r_afterAll=$(sed -n 's/^[ \t]*\(afterAll\)[ \t]*(.*/\1/p' "${utt8r_testfile}")
utt8r_beforeEach=$(sed -n -e 's/^[ \t]*\(beforeEach\)[ \t]*(.*/\1/p' -e 's/^[ \t]*\(setUp\)[ \t]*(.*/\1/p' "${utt8r_testfile}")
utt8r_afterEach=$(sed -n -e 's/^[ \t]*\(afterEach\)[ \t]*(.*/\1/p' -e 's/^[ \t]*\(tearDown\)[ \t]*(.*/\1/p' "${utt8r_testfile}")
utt8r_tests=$(sed -n 's/^[ \t]*\(x\{0,1\}test_[^(=]*\)(.*/\1/p' "${utt8r_testfile}")
# shellcheck source=/dev/null
. "${utt8r_testfile}"
${utt8r_beforeAll}
for _current_testcase in ${utt8r_tests}; do
UNITTEST_CURRENT="${utt8r_testfile#./}:${_current_testcase}"
case ${_current_testcase} in
x*)
unittest__print_result "${UNITTEST_CURRENT}" 'SKIP'
;;
*)
${utt8r_beforeEach}
# test result is status of last command in test
if ${_current_testcase}; then
unittest__print_result "${UNITTEST_CURRENT}" 'PASS'
else
# last command in test failed
UNITTEST_STATUS=1
unittest__print_result "${UNITTEST_CURRENT}" 'FAIL'
fi
${utt8r_afterEach}
;;
esac
done
${utt8r_afterAll}
unset -v UNITTEST_CURRENT
exit ${UNITTEST_STATUS}
)
UNITTEST_STATUS=$?
done
return ${UNITTEST_STATUS}
}
#
# MAIN ROUTINE
#
{
# Color output by default: on supported terminals when NO_COLOR is not set.
# Supported terminals are recognized based on TERM variable. When TERM is
# not set (for example inside CI environment) we assume that terminal is dumb.
if [ -z "${NO_COLOR}" ] && [ "$(TERM=${TERM:-dumb} tput colors)" -lt 8 ]; then
NO_COLOR='YES'
fi
case $# in
0) # discovery mode
unittest__test_files | unittest__run
exit $?
;;
1)
case $1 in
-h|--help)
cat >&2 <<-'EOF'
unittest - unit tests framework for shell scripts.
Usage:
unittest [options] [test_directory | test_file]
Options:
-h, --help Show this help and exit.
-v, --version Show version number and exit.
Without any arguments it will run all tests from 'tests' directory.
EOF
exit 0
;;
-v|--version)
printf 'unittest 23.11\n' >&2
exit 0
;;
*) # specified directory/file
if [ -d "$1" ]; then
unittest__test_files "$1" | unittest__run
exit $?
elif [ -f "$1" ]; then
printf '%s\n' "$1" | unittest__run
exit $?
fi
;;
esac
esac
unittest__print_debug 'INVALID USAGE' \
"I cannot understand '$*' option. Did you want to use option or did you misspell file/directory?" \
'Hint: Find valid usage with:' \
' $ unittest -h'
exit 64 # EX_USAGE
}