Created
September 4, 2023 17:19
-
-
Save apainintheneck/287276794f3c2c3a0d0696674b05e431 to your computer and use it in GitHub Desktop.
A command line test runner to test everything but the kitchen sink.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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