#!/usr/bin/env bash set -e # Just stream the TAP output (sans extended syntax) if tput is missing command -v tput >/dev/null || exec grep -v "^begin " global header_pattern := ''[0-9]+\.\.[0-9]+'' env IFS= read -r header if [[ "$header" =~ $header_pattern ]] { global count := $(header:3) global index := '0' global failures := '0' global skipped := '0' global name := ''"" global count_column_width := $( ${#count} * 2 + 2 ) } else { # If the first line isn't a TAP plan, print it and pass the rest through printf "%s\n" $header exec cat } proc update_screen_width { global screen_width := $[tput cols] global count_column_left := $( $screen_width - $count_column_width ) } trap update_screen_width WINCH update_screen_width proc begin { go_to_column 0 printf_with_truncation $( $count_column_left - 1 ) " %s" $name clear_to_end_of_line go_to_column $count_column_left printf "%$(#count)s/$(count)" $index go_to_column 1 } proc pass { go_to_column 0 printf " ✓ %s" $name advance } proc skip { var reason = $1 test -z $reason || reason := "": $reason"" go_to_column 0 printf " - %s (skipped%s)" $name $reason advance } proc fail { go_to_column 0 set_color 1 bold printf " ✗ %s" $name advance } proc log { set_color 1 printf " %s\n" $1 clear_color } proc summary { printf "\n%d test%s" $count $[plural $count] printf ", %d failure%s" $failures $[plural $failures] if test $skipped -gt 0 { printf ", %d skipped" $skipped } printf "\n" } proc printf_with_truncation { var width = $1 shift var string = $[printf @Argv] if test $(#string) -gt $width { printf "%s..." $(string:0:$(( $width - 4 ))) } else { printf "%s" $string } } proc go_to_column { var column = $1 printf "\x1B[%dG" $( $column + 1 ) } proc clear_to_end_of_line { printf "\x1B[K" } proc advance { clear_to_end_of_line echo clear_color } proc set_color { var color = $1 var weight = $2 printf "\x1B[%d;%dm" $( 30 + $color ) $[ test $weight = "bold" && echo 1 || echo 22] } proc clear_color { printf "\x1B[0m" } proc plural { test $1 -eq 1 || echo "s" } global _buffer := ''"" proc buffer { global _buffer := ""$(_buffer)$[@Argv]"" } proc flush { printf "%s" $_buffer global _buffer := ''"" } proc finish { flush printf "\n" } trap finish EXIT while IFS= read -r line { matchstr $line { "begin "* { let index+=1 global name := $(line#* $index ) buffer begin flush } "ok "* { global skip_expr := ""ok $index # skip (\(([^)]*)\))?"" if [[ "$line" =~ $skip_expr ]] { let skipped+=1 buffer skip $(BASH_REMATCH[2]) } else { buffer pass } } "not ok "* { let failures+=1 buffer fail } "# "* { buffer log $(line:2) } } } buffer summary (CommandList children: [ (C {(set)} {(-e)}) (AndOr children: [ (SimpleCommand words: [{(command)} {(-v)} {(tput)}] redirects: [(Redir op_id:Redir_Great fd:-1 arg_word:{(/dev/null)} spids:[17])] ) (C {(exec)} {(grep)} {(-v)} {(DQ ("^begin "))}) ] op_id: Op_DPipe ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:header_pattern) op: Equal rhs: {(SQ <"[0-9]+\\.\\.[0-9]+">)} spids: [33] ) ] spids: [33] ) (SimpleCommand words: [{(read)} {(-r)} {(header)}] more_env: [(env_pair name:IFS val:{(SQ )} spids:[38])] ) (If arms: [ (if_arm cond: [ (Sentence child: (DBracket expr: (BoolBinary op_id: BoolBinary_EqualTilde left: {(DQ ($ VSub_Name "$header"))} right: {($ VSub_Name "$header_pattern")} ) ) terminator: ) ] action: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:count) op: Equal rhs: { (DQ (BracedVarSub token: suffix_op: (Slice begin:(ArithWord w:{(Lit_Digits 3)})) spids: [67 71] ) ) } spids: [65] ) ] spids: [65] ) (Assignment keyword: Assign_None pairs: [(assign_pair lhs:(LhsName name:index) op:Equal rhs:{(0)} spids:[75])] spids: [75] ) (Assignment keyword: Assign_None pairs: [(assign_pair lhs:(LhsName name:failures) op:Equal rhs:{(0)} spids:[79])] spids: [79] ) (Assignment keyword: Assign_None pairs: [(assign_pair lhs:(LhsName name:skipped) op:Equal rhs:{(0)} spids:[83])] spids: [83] ) (Assignment keyword: Assign_None pairs: [(assign_pair lhs:(LhsName name:name) op:Equal rhs:{(DQ )} spids:[87])] spids: [87] ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:count_column_width) op: Equal rhs: { (ArithSubPart anode: (ArithBinary op_id: Arith_Plus left: (ArithBinary op_id: Arith_Star left: (ArithWord w: { (BracedVarSub token: prefix_op: VSub_Pound spids: [95 98] ) } ) right: (ArithWord w:{(Lit_Digits 2)}) ) right: (ArithWord w:{(Lit_Digits 2)}) ) spids: [93 109] ) } spids: [92] ) ] spids: [92] ) ] spids: [-1 62] ) ] else_action: [ (C {(printf)} {(DQ ("%s") (EscapedLiteralPart token:))} {(DQ ($ VSub_Name "$header"))} ) (C {(exec)} {(cat)}) ] spids: [111 134] ) (FuncDef name: update_screen_width body: (BraceGroup children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:screen_width) op: Equal rhs: { (DQ (CommandSubPart command_list: (CommandList children:[(C {(tput)} {(cols)})]) left_token: spids: [146 150] ) ) } spids: [144] ) ] spids: [144] ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:count_column_left) op: Equal rhs: { (ArithSubPart anode: (ArithBinary op_id: Arith_Minus left: (ArithWord w:{($ VSub_Name "$screen_width")}) right: (ArithWord w:{($ VSub_Name "$count_column_width")}) ) spids: [155 164] ) } spids: [154] ) ] spids: [154] ) ] spids: [141] ) spids: [137 140] ) (C {(trap)} {(update_screen_width)} {(WINCH)}) (C {(update_screen_width)}) (FuncDef name: begin body: (BraceGroup children: [ (C {(go_to_column)} {(0)}) (C {(printf_with_truncation)} { (ArithSubPart anode: (ArithBinary op_id: Arith_Minus left: (ArithWord w:{($ VSub_Name "$count_column_left")}) right: (ArithWord w:{(Lit_Digits 1)}) ) spids: [192 201] ) } {(DQ (" %s"))} {(DQ ($ VSub_Name "$name"))} ) (C {(clear_to_end_of_line)}) (C {(go_to_column)} {($ VSub_Name "$count_column_left")}) (C {(printf)} { (DQ ("%") (BracedVarSub token: prefix_op:VSub_Pound spids:[224227]) (s/) (${ VSub_Name count) ) } {(DQ ($ VSub_Name "$index"))} ) (C {(go_to_column)} {(1)}) ] spids: [182] ) spids: [178 181] ) (FuncDef name: pass body: (BraceGroup children: [ (C {(go_to_column)} {(0)}) (C {(printf)} {(DQ (" \u2713 %s"))} {(DQ ($ VSub_Name "$name"))}) (C {(advance)}) ] spids: [250] ) spids: [246 249] ) (FuncDef name: skip body: (BraceGroup children: [ (Assignment keyword: Assign_Local pairs: [ (assign_pair lhs: (LhsName name:reason) op: Equal rhs: {(DQ ($ VSub_Number "$1"))} spids: [283] ) ] spids: [281] ) (AndOr children: [ (C {(Lit_Other "[")} {(-z)} {(DQ ($ VSub_Name "$reason"))} {(Lit_Other "]")}) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:reason) op: Equal rhs: {(DQ (": ") ($ VSub_Name "$reason"))} spids: [301] ) ] spids: [301] ) ] op_id: Op_DPipe ) (C {(go_to_column)} {(0)}) (C {(printf)} {(DQ (" - %s (skipped%s)"))} {(DQ ($ VSub_Name "$name"))} {(DQ ($ VSub_Name "$reason"))} ) (C {(advance)}) ] spids: [278] ) spids: [274 277] ) (FuncDef name: fail body: (BraceGroup children: [ (C {(go_to_column)} {(0)}) (C {(set_color)} {(1)} {(bold)}) (C {(printf)} {(DQ (" \u2717 %s"))} {(DQ ($ VSub_Name "$name"))}) (C {(advance)}) ] spids: [337] ) spids: [333 336] ) (FuncDef name: log body: (BraceGroup children: [ (C {(set_color)} {(1)}) (C {(printf)} {(DQ (" %s") (EscapedLiteralPart token:))} {(DQ ($ VSub_Number "$1"))} ) (C {(clear_color)}) ] spids: [372] ) spids: [368 371] ) (FuncDef name: summary body: (BraceGroup children: [ (C {(printf)} {(DQ (EscapedLiteralPart token:) ("%d test%s"))} {(DQ ($ VSub_Name "$count"))} { (DQ (CommandSubPart command_list: (CommandList children: [(C {(plural)} {(DQ ($ VSub_Name "$count"))})] ) left_token: spids: [416 422] ) ) } ) (C {(printf)} {(DQ (", %d failure%s"))} {(DQ ($ VSub_Name "$failures"))} { (DQ (CommandSubPart command_list: (CommandList children: [(C {(plural)} {(DQ ($ VSub_Name "$failures"))})] ) left_token: spids: [438 444] ) ) } ) (If arms: [ (if_arm cond: [ (Sentence child: (C {(Lit_Other "[")} {(DQ ($ VSub_Name "$skipped"))} {(-gt)} {(0)} {(Lit_Other "]")} ) terminator: ) ] action: [(C {(printf)} {(DQ (", %d skipped"))} {(DQ ($ VSub_Name "$skipped"))})] spids: [-1 464] ) ] spids: [-1 478] ) (C {(printf)} {(DQ (EscapedLiteralPart token:))}) ] spids: [401] ) spids: [397 400] ) (FuncDef name: printf_with_truncation body: (BraceGroup children: [ (Assignment keyword: Assign_Local pairs: [ (assign_pair lhs: (LhsName name:width) op: Equal rhs: {(DQ ($ VSub_Number "$1"))} spids: [500] ) ] spids: [498] ) (C {(shift)}) (Assignment keyword: Assign_Local pairs: [ (assign_pair lhs: (LhsName name:string) op: Equal rhs: { (DQ (CommandSubPart command_list: (CommandList children: [(C {(printf)} {(DQ ($ VSub_At "$@"))})] ) left_token: spids: [513 519] ) ) } spids: [511] ) ] spids: [509] ) (If arms: [ (if_arm cond: [ (Sentence child: (C {(Lit_Other "[")} { (DQ (BracedVarSub token: prefix_op: VSub_Pound spids: [529 532] ) ) } {(-gt)} {(DQ ($ VSub_Name "$width"))} {(Lit_Other "]")} ) terminator: ) ] action: [ (C {(printf)} {(DQ ("%s..."))} { (DQ (BracedVarSub token: suffix_op: (Slice begin: (ArithWord w:{(Lit_Digits 0)}) length: (ArithWord w: { (ArithSubPart anode: (ArithBinary op_id: Arith_Minus left: (ArithWord w:{($ VSub_Name "$width")}) right: (ArithWord w:{(Lit_Digits 4)}) ) spids: [559 568] ) } ) ) spids: [554 569] ) ) } ) ] spids: [-1 544] ) ] else_action: [(C {(printf)} {(DQ ("%s"))} {(DQ ($ VSub_Name "$string"))})] spids: [573 587] ) ] spids: [495] ) spids: [491 494] ) (FuncDef name: go_to_column body: (BraceGroup children: [ (Assignment keyword: Assign_Local pairs: [ (assign_pair lhs: (LhsName name:column) op: Equal rhs: {(DQ ($ VSub_Number "$1"))} spids: [601] ) ] spids: [599] ) (C {(printf)} {(DQ (EscapedLiteralPart token:) ("1B[%dG"))} { (ArithSubPart anode: (ArithBinary op_id: Arith_Plus left: (ArithWord w:{($ VSub_Name "$column")}) right: (ArithWord w:{(Lit_Digits 1)}) ) spids: [614 623] ) } ) ] spids: [596] ) spids: [592 595] ) (FuncDef name: clear_to_end_of_line body: (BraceGroup children: [(C {(printf)} {(DQ (EscapedLiteralPart token:) ("1B[K"))})] spids: [632] ) spids: [628 631] ) (FuncDef name: advance body: (BraceGroup children: [(C {(clear_to_end_of_line)}) (C {(echo)}) (C {(clear_color)})] spids: [649] ) spids: [645 648] ) (FuncDef name: set_color body: (BraceGroup children: [ (Assignment keyword: Assign_Local pairs: [ (assign_pair lhs: (LhsName name:color) op: Equal rhs: {(DQ ($ VSub_Number "$1"))} spids: [672] ) ] spids: [670] ) (Assignment keyword: Assign_Local pairs: [ (assign_pair lhs: (LhsName name:weight) op: Equal rhs: {(DQ ($ VSub_Number "$2"))} spids: [680] ) ] spids: [678] ) (C {(printf)} {(DQ (EscapedLiteralPart token:) ("1B[%d;%dm"))} { (ArithSubPart anode: (ArithBinary op_id: Arith_Plus left: (ArithWord w:{(Lit_Digits 30)}) right: (ArithWord w:{($ VSub_Name "$color")}) ) spids: [693 702] ) } { (DQ (CommandSubPart command_list: (CommandList children: [ (AndOr children: [ (C {(Lit_Other "[")} {(DQ ($ VSub_Name "$weight"))} {(Lit_Other "=")} {(DQ (bold))} {(Lit_Other "]")} ) (AndOr children: [(C {(echo)} {(1)}) (C {(echo)} {(22)})] op_id: Op_DPipe ) ] op_id: Op_DAmp ) ] ) left_token: spids: [705 733] ) ) } ) ] spids: [667] ) spids: [663 666] ) (FuncDef name: clear_color body: (BraceGroup children: [(C {(printf)} {(DQ (EscapedLiteralPart token:) ("1B[0m"))})] spids: [743] ) spids: [739 742] ) (FuncDef name: plural body: (BraceGroup children: [ (AndOr children: [ (C {(Lit_Other "[")} {(DQ ($ VSub_Number "$1"))} {(-eq)} {(1)} {(Lit_Other "]")}) (C {(echo)} {(DQ (s))}) ] op_id: Op_DPipe ) ] spids: [760] ) spids: [756 759] ) (Assignment keyword: Assign_None pairs: [(assign_pair lhs:(LhsName name:_buffer) op:Equal rhs:{(DQ )} spids:[786])] spids: [786] ) (FuncDef name: buffer body: (BraceGroup children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:_buffer) op: Equal rhs: { (DQ (${ VSub_Name _buffer) (CommandSubPart command_list: (CommandList children:[(C {(DQ ($ VSub_At "$@"))})]) left_token: spids: [803 807] ) ) } spids: [798] ) ] spids: [798] ) ] spids: [795] ) spids: [791 794] ) (FuncDef name: flush body: (BraceGroup children: [ (C {(printf)} {(DQ ("%s"))} {(DQ ($ VSub_Name "$_buffer"))}) (Assignment keyword: Assign_None pairs: [(assign_pair lhs:(LhsName name:_buffer) op:Equal rhs:{(DQ )} spids:[831])] spids: [831] ) ] spids: [817] ) spids: [813 816] ) (FuncDef name: finish body: (BraceGroup children: [ (C {(flush)}) (C {(printf)} {(DQ (EscapedLiteralPart token:))}) ] spids: [842] ) spids: [838 841] ) (C {(trap)} {(finish)} {(EXIT)}) (While cond: [ (Sentence child: (SimpleCommand words: [{(read)} {(-r)} {(line)}] more_env: [(env_pair name:IFS val:{(SQ )} spids:[866])] ) terminator: ) ] body: (DoGroup children: [ (Case to_match: {(DQ ($ VSub_Name "$line"))} arms: [ (case_arm pat_list: [{(DQ ("begin ")) (Lit_Other "*")}] action: [ (C {(let)} {(Lit_VarLike "index+=") (1)}) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:name) op: Equal rhs: { (DQ (BracedVarSub token: suffix_op: (StringUnary op_id: VOp1_Pound arg_word: {("* ") ($ VSub_Name "$index") (" ")} ) spids: [903 909] ) ) } spids: [901] ) ] spids: [901] ) (C {(buffer)} {(begin)}) (C {(flush)}) ] spids: [888 892 921 -1] ) (case_arm pat_list: [{(DQ ("ok ")) (Lit_Other "*")}] action: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:skip_expr) op: Equal rhs: { (DQ ("ok ") ($ VSub_Name "$index") (" # skip (") (EscapedLiteralPart token: ) ("([^)]*)") (EscapedLiteralPart token:) (")?") ) } spids: [932] ) ] spids: [932] ) (If arms: [ (if_arm cond: [ (Sentence child: (DBracket expr: (BoolBinary op_id: BoolBinary_EqualTilde left: {(DQ ($ VSub_Name "$line"))} right: {($ VSub_Name "$skip_expr")} ) ) terminator: ) ] action: [ (C {(let)} {(Lit_VarLike "skipped+=") (1)}) (C {(buffer)} {(skip)} { (DQ (BracedVarSub token: bracket_op: (ArrayIndex expr:(ArithWord w:{(Lit_Digits 2)})) spids: [973 978] ) ) } ) ] spids: [-1 959] ) ] else_action: [(C {(buffer)} {(pass)})] spids: [982 990] ) ] spids: [925 929 993 -1] ) (case_arm pat_list: [{(DQ ("not ok ")) (Lit_Other "*")}] action: [(C {(let)} {(Lit_VarLike "failures+=") (1)}) (C {(buffer)} {(fail)})] spids: [997 1001 1015 -1] ) (case_arm pat_list: [{(DQ ("# ")) (Lit_Other "*")}] action: [ (C {(buffer)} {(log)} { (DQ (BracedVarSub token: suffix_op: (Slice begin:(ArithWord w:{(Lit_Digits 2)})) spids: [1031 1035] ) ) } ) ] spids: [1019 1023 1039 -1] ) ] spids: [878 884 1042] ) ] spids: [875 1044] ) ) (C {(buffer)} {(summary)}) ] )