Skip to content

Instantly share code, notes, and snippets.

@apainintheneck
Created September 4, 2023 17:19
Show Gist options
  • Save apainintheneck/287276794f3c2c3a0d0696674b05e431 to your computer and use it in GitHub Desktop.
Save apainintheneck/287276794f3c2c3a0d0696674b05e431 to your computer and use it in GitHub Desktop.
A command line test runner to test everything but the kitchen sink.
#!/usr/bin/env awk -f
# Run `ksink` to see usage.
################################
# Main
################################
BEGIN {
parse_args()
file = command_file()
if(usage)
usage_command()
else if(edit)
edit_command(file)
else if(path)
path_command(file)
build_command_list(file)
if(list)
list_command(file)
# Run each command in order and record the exit code.
for(idx = 1; idx <= length(command_list); idx++) {
if(idx > 1 && !quiet) printf("\n")
print(bold(idx ") " command_list[idx]))
exit_codes[idx] = run_command(command_list[idx])
if(fail_fast && exit_codes[idx] != 0) {
print("\n" red("Error: ") command_list[idx])
exit(exit_codes[idx])
}
}
# Print out summary of command results.
print("")
print(bold(underline("Summary")))
for(idx = 1; idx <= length(command_list); idx++) {
line = sprintf("[%3d] %s", exit_codes[idx], command_list[idx])
if(exit_codes[idx] == 0)
print(green(line))
else
print(red(line))
}
}
################################
# Functions
################################
function run_command(command) {
if(quiet)
return silent_system(command)
else
return system(command)
}
function parse_args( idx) {
if(ARGC == 1) {
usage = 1
return
}
for(idx = 1; idx < ARGC; idx++) {
if(ARGV[idx] == "run" || ARGV[idx] == "r") {
# Nothing needs to be set here. The usage command is called by default
# if no options are passed in and the run command sidesteps that.
} else if(ARGV[idx] == "fail-fast" || ARGV[idx] == "ff") {
fail_fast = 1
} else if(ARGV[idx] == "branch" || ARGV[idx] == "b") {
if(!in_git_repo())
error_and_exit("Invalid `branch` option because " ENVIRON["PWD"] " is not a git repo", 1)
branch = 1
} else if(ARGV[idx] == "quiet" || ARGV[idx] == "q") {
quiet = 1
} else if(ARGV[idx] == "edit" || ARGV[idx] == "e") {
# Note: This has precedence over all other commands.
edit = 1
} else if(ARGV[idx] == "path" || ARGV[idx] == "p") {
path = 1
} else {
error_and_exit("Unknown option `" ARGV[idx] "`", 1)
}
}
# Clear ARGV after parsing its contents.
split("", ARGV)
}
function build_command_list(file, idx) {
if(!is_file(file))
error_and_exit("Missing file `" file "`", 1)
else if(!is_readable(file))
error_and_exit("Unable to read file `" file "`", 1)
while((getline line < file) > 0) {
line = strip(line)
if(length(line) == 0) # skip blank lines
continue
else if(substr(line, 1, 1) == "#") # skip comments
continue
command_list[++idx] = line
}
close(file)
if(length(command_list) == 0)
error_and_exit("No commands found in file `" file "`", 1)
}
function command_file( file) {
if(branch)
file = top_level_git_directory() "/." current_git_branch() ".ksink"
else if(in_git_repo())
file = top_level_git_directory() "/.ksink"
else
file = ENVIRON["PWD"] "/.ksink"
return file
}
#
# Commands
#
function path_command(file_path) {
print file_path
exit(0)
}
function edit_command(command_file, command) {
# Initialize a new command file if one doesn't already exist.
if(!is_file(command_file)) {
print "# A command list for the `ksink` utility." > command_file
print "#" > command_file
print "# All non-blank lines not prefixed by the hash symbol (#)" > command_file
print "# are considered individual commands." > command_file
print "# Each command can only span one line." > command_file
print "echo 'Hello World!'" > command_file
close(command_file)
}
command = editor() " " command_file
exit(system(command))
}
function usage_command() {
print("Usage: ksink [options]")
print("")
print("A command line test runner. Run everything in the command list")
print("in order to test everything but the kitchen sink.")
print("")
print("Options:")
print(" r) run Run entire command list.")
print(" ff) fail-fast Exit after the first failure.")
print(" q) quiet Suppress command output.")
print(" b) branch Use branch specific command list.")
print(" e) edit Edit command list (with `$EDITOR` or `vi`).")
print(" p) path Path to the nearest config file.")
exit(0)
}
function list_command(command_file, idx) {
print(title("Command List: " command_file))
for(idx = 1; idx <= length(command_list); idx++)
printf("%d. %s\n", idx, command_list[idx])
exit(0)
}
#
# Git
#
function current_git_branch( command, branch_name, exit_code) {
return popen("git branch --show-current")
}
function top_level_git_directory( command, directory, exit_code) {
return popen("git rev-parse --show-toplevel")
}
function in_git_repo( command) {
return silent_system("git rev-parse --is-inside-work-tree") == 0
}
#
# Formatting
#
function bold(string) {
return "\033[1m" string "\033[0m"
}
function underline(string) {
return "\033[4m" string "\033[0m"
}
function green(string) {
return "\033[32m" string "\033[0m"
}
function red(string) {
return "\033[31m" string "\033[0m"
}
#
# Utils
#
function silent_system(command) {
command = command " 1> /dev/null 2> /dev/null"
return system(command)
}
function popen(command, result, exit_code) {
status = (command | getline result)
close(command)
if(status <= 0)
error_and_exit("Unable to run `" command "`", exit_code)
return result
}
function is_file(file) {
return test_file("-f", file)
}
function is_readable(file) {
return test_file("-r", file)
}
function test_file(test, file, command) {
command = "[ " test " " file " ]"
return silent_system(command) == 0
}
function editor() {
if(ENVIRON["EDITOR"])
return ENVIRON["EDITOR"]
else
return "vi"
}
function error_and_exit(message, exit_code) {
print(red("Error: ") message)
exit(exit_code)
}
function strip(line) {
sub(/^[ \t]+/, "", line)
sub(/[ \t]+$/, "", line)
return line
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment