diff --git a/requirements.txt b/requirements.txt index 3e8378a..5826427 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ jinja2 - +jinja2-strcase diff --git a/tests/fhtagn-0.1.0/.github/workflows/run-tests.yml b/tests/fhtagn-0.1.0/.github/workflows/run-tests.yml new file mode 100644 index 0000000..dd16ced --- /dev/null +++ b/tests/fhtagn-0.1.0/.github/workflows/run-tests.yml @@ -0,0 +1,62 @@ +on: + - "push" + +name: "Run tests" + +jobs: + nix: + name: "*nix - all awks" + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ ubuntu-20.04, ubuntu-22.04 ] + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + id: cache-soft + with: + path: soft + key: ${{ matrix.os }}-${{ hashFiles('Makesurefile') }}-soft--all-2 + + - name: "run tests" + run: | + ./makesure + + macos: + name: "macOS" + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ macos-11, macos-12, macos-13 ] + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + id: cache-soft + with: + path: soft + key: ${{ matrix.os }}-soft--macos + + - name: "run tests" + run: | + ./makesure tested_by_default_awk + + win: + name: "Win" + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ windows-2019, windows-2022 ] + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + id: cache-soft + with: + path: soft + key: ${{ matrix.os }}-soft--win + + - name: "run tests" + run: | + & bash -e -c "./makesure tested_by_default_awk" diff --git a/tests/fhtagn-0.1.0/.gitignore b/tests/fhtagn-0.1.0/.gitignore new file mode 100644 index 0000000..d889f19 --- /dev/null +++ b/tests/fhtagn-0.1.0/.gitignore @@ -0,0 +1,3 @@ +/*.iml +/.idea/ +/soft/ \ No newline at end of file diff --git a/tests/fhtagn-0.1.0/LICENSE b/tests/fhtagn-0.1.0/LICENSE new file mode 100644 index 0000000..716c14a --- /dev/null +++ b/tests/fhtagn-0.1.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Volodymyr Gubarkov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tests/fhtagn-0.1.0/Makesurefile b/tests/fhtagn-0.1.0/Makesurefile new file mode 100644 index 0000000..7ba5275 --- /dev/null +++ b/tests/fhtagn-0.1.0/Makesurefile @@ -0,0 +1,188 @@ +# vim: syntax=bash +@options timing + +@define TUSH_REPO='https://github.com/adolfopa/tush' +@define GOAWK_VERSION='1.23.1' +@define GOAWK="goawk$GOAWK_VERSION" + +@goal soft_folder_created @private +@reached_if [[ -d "soft" ]] + mkdir soft + +@goal tush_installed @private +@depends_on soft_folder_created +@reached_if [[ -f "soft/tush/bin/tush-check" ]] + echo + echo "Fetching tush..." + echo + + cd "soft" + + if command -v wget >/dev/null + then + wget $TUSH_REPO/archive/master.tar.gz -O./tush.tar.gz + tar xzvf ./tush.tar.gz + rm ./tush.tar.gz + mv tush-master tush + elif command -v curl >/dev/null + then + curl -L $TUSH_REPO/archive/master.tar.gz -o ./tush.tar.gz + tar xzvf ./tush.tar.gz + rm ./tush.tar.gz + mv tush-master tush + else + git clone --depth 1 $TUSH_REPO.git + rm -r tush/.git + fi + +@goal default +@depends_on tested + +@goal tested +@depends_on tested_by_default_awk +@depends_on tested_by_bwk +@depends_on tested_by_gawk +@depends_on tested_by @args 'tush' 'mawk -f ./fhtagn.awk' +@depends_on tested_by @args 'fhtagn' 'mawk -f ./fhtagn.awk' +@depends_on tested_by_busybox_awk +#@depends_on tested_by_goawk + +@goal tested_by_bwk +@depends_on installed_bwk +@depends_on tested_by @args 'tush' './soft/bwk -f ./fhtagn.awk' +@depends_on tested_by @args 'fhtagn' './soft/bwk -f ./fhtagn.awk' + +@goal tested_by_gawk +@depends_on tested_by @args 'tush' 'gawk -f ./fhtagn.awk' +@depends_on tested_by @args 'fhtagn' 'gawk -f ./fhtagn.awk' + +@goal tested_by_default_awk +@depends_on tested_by @args 'tush' './fhtagn.awk' +@depends_on tested_by @args 'fhtagn' './fhtagn.awk' + +@goal tested_by_busybox_awk +@depends_on installed_busybox +@depends_on tested_by @args 'tush' './soft/busybox awk -f ./fhtagn.awk' +@depends_on tested_by @args 'fhtagn' './soft/busybox awk -f ./fhtagn.awk' + +@goal tested_by_goawk +@depends_on installed_goawk +@depends_on tested_by @args 'tush' './soft/goawk1.23.1 -f ./fhtagn.awk' +@depends_on tested_by @args 'fhtagn' './soft/goawk1.23.1 -f ./fhtagn.awk' + +@goal tested_by @params TOOL FHTAGN +@depends_on tush_installed + f=tests/fhtagn.tush + + echo "Testing with $TOOL ($FHTAGN)..." + export FHTAGN + + calc_temp_files() { + local tmp_count=$(find /tmp -maxdepth 1 -type f -name 'fhtagn.*' | wc -l) + local cnt + (( cnt = tmp_count )) + if [[ -d "/dev/shm" ]] + then + local shm_count=$(find /dev/shm -maxdepth 1 -type f -name 'fhtagn.*' | wc -l) + (( cnt += shm_count )) + fi + echo $cnt + } + + test_file() { + if [[ $TOOL == "tush" ]] + then + export PATH="$PATH:$MYDIR/soft/tush/bin" + DIFF="diff --strip-trailing-cr" tush-check "$f" + else + local before_count=$(calc_temp_files) + ./fhtagn.awk "$f" + local after_count=$(calc_temp_files) + if (( before_count != after_count )) + then + echo >&2 "!!! temp file not deleted !!!" + exit 1 + fi + fi + } + + if test_file + then + echo "TESTS PASSED : $f" + else + echo >&2 "!!! TESTS FAILED !!! : $f" + exit 1 + fi + +@goal installed_bwk @private +@reached_if [[ -f soft/bwk ]] +@depends_on soft_folder_created + echo + echo "Fetching BWK..." + echo + + cd "soft" + + wget https://github.com/onetrueawk/awk/archive/refs/heads/master.tar.gz -Obwk.tar.gz + tar xzvf bwk.tar.gz + rm bwk.tar.gz + + echo + echo "Compile BWK..." + echo + + cd "awk-master" + + make + + mv a.out ../bwk + + cd .. + ./bwk --version + rm -r awk-master + +@goal installed_busybox @private +@reached_if [[ -x ./soft/busybox ]] + wget 'https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox' -O ./soft/busybox + chmod +x ./soft/busybox + echo "Installed: $(./soft/busybox | head -n 1)" + +@goal installed_goawk @private +@reached_if [[ -f soft/$GOAWK ]] +@depends_on soft_folder_created + echo + echo "Fetching GoAWK $GOAWK_VERSION ..." + echo + + cd "soft" + + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + os="linux" + elif [[ "$OSTYPE" == "darwin"* ]]; then + os="darwin" + else + >&2 echo "Unknown OS" + exit 1 + fi + + F=goawk_v${GOAWK_VERSION}_${os}_amd64.tar.gz + wget "https://github.com/benhoyt/goawk/releases/download/v$GOAWK_VERSION/$F" + tar xzvf "$F" goawk + rm "$F" + + mv goawk $GOAWK + "./$GOAWK" --version + +@goal update_readme +@doc 'updates the README.md with the current output of the tool' + awk ' + !Off + Examples { + if (/^\$/) { Off=1; system(substr($0,2)) } + else if (/^```$/ && Off) { Off=0; print } + } + /## Usage/ { Examples=1 } + ' README.md > README.md.1 + mv README.md.1 README.md + + diff --git a/tests/fhtagn-0.1.0/README.md b/tests/fhtagn-0.1.0/README.md new file mode 100644 index 0000000..5e8ebcb --- /dev/null +++ b/tests/fhtagn-0.1.0/README.md @@ -0,0 +1,86 @@ +# fhtagn + +[![Run tests](https://github.com/xonixx/fhtagn/actions/workflows/run-tests.yml/badge.svg)](https://github.com/xonixx/fhtagn/actions/workflows/run-tests.yml) + +`fhtagn.awk` is a tiny CLI tool for literate testing for command-line programs. + +File `tests.tush`: +``` +$ command --that --should --execute correctly +| expected stdout output + +$ command --that --will --cause error +@ expected stderr output +? expected-exit-code +``` + +Run the tests: +```shell +./fhtagn.awk tests.tush +``` + +In fact this is a re-implementation of [darius/tush](https://github.com/darius/tush), [adolfopa/tush](https://github.com/adolfopa/tush). +But simpler (single tiny AWK script) and faster, because: + +- it uses `/dev/shm` where available instead of `/tmp` +- it compares the expected result with the actual in the code and only calls `diff` to show the difference if they don't match +- it doesn't create a sandbox folder for each test +- it doesn't use `mktemp` but rather generates random name in the code + +## Usage + +``` +./fhtagn.awk f1.tush [ f2.tush [ f3.tush [...] ] ] +``` +This will stop on the first error found. + +Example: +``` +$ ./fhtagn.awk tests/1.tush tests/2.tush tests/3.tush +tests/2.tush:10: $ echo "hello world"; echo "error msg" >&2; exit 7 +--- expected ++++ actual +@@ -1,4 +1,4 @@ + | hello world +-@ error msg 444 +-? 8 ++@ error msg ++? 7 + +``` + +### Fail at the end + +Set `ALL=1` environment variable. + +This runs all tests, collects all errors, and shows the stats at the end. +``` +ALL=1 ./fhtagn.awk f1.tush [ f2.tush [ f3.tush [...] ] ] +``` + +Useful for running in CI. + +Example: +``` +$ ALL=1 ./fhtagn.awk tests/1.tush tests/2.tush tests/3.tush +tests/2.tush:10: $ echo "hello world"; echo "error msg" >&2; exit 7 +--- expected ++++ actual +@@ -1,4 +1,4 @@ + | hello world +-@ error msg 444 +-? 8 ++@ error msg ++? 7 + +tests/3.tush:4: $ echo bbb +--- expected ++++ actual +@@ -1,2 +1,2 @@ +-| BBB ++| bbb + +result=FAIL, failure=2, success=4, total=6, files=3 +``` + + diff --git a/tests/fhtagn-0.1.0/fhtagn.awk b/tests/fhtagn-0.1.0/fhtagn.awk new file mode 100755 index 0000000..dd83f02 --- /dev/null +++ b/tests/fhtagn-0.1.0/fhtagn.awk @@ -0,0 +1,81 @@ +#!/usr/bin/awk -f +BEGIN { + Prog = "fhtagn" + initTmpRnd() + if (!(Diff = ENVIRON["DIFF"])) Diff = "diff" + All = ENVIRON["ALL"] + srand() + Success = Failed = 0 + fhtagn() +} +END { if (ToDel) system("rm -f" ToDel) } +function initTmpRnd( c) { + c = "[ -d /dev/shm ] && echo /dev/shm || echo /tmp ; echo $$" + c | getline Tmp + c | getline Rnd # additional source of "random" + close(c) +} +function fhtagn( i,file,err,l,code,nr,line,random,exitCode,stdOutF,stdErrF,testStarted,expected,hasMoreCode) { + for (i = 1; i < ARGC; i++) { + file = ARGV[i] + for (nr = 1; (err = getline l < file) > 0; nr++) { + if (l ~ /^\$/) { + if (testStarted) # finish previous + checkTestResult(file,code,line,expected,stdOutF,stdErrF,exitCode,random) + testStarted = 1 + expected = "" + # execute line starting '$', producing out & err & exit_code + ToDel = ToDel " " (stdOutF = tmpFile(random = rndS(), "out")) " " (stdErrF = tmpFile(random, "err")) + code = substr(l,2) + line = nr + if (!(hasMoreCode = l ~ /\\$/)) + exitCode = run(code,stdOutF,stdErrF) + } else if (hasMoreCode) { + code = code "\n" l + if (!(hasMoreCode = l ~ /\\$/)) + exitCode = run(code,stdOutF,stdErrF) + } else if (l ~ /^[|@?]/) { + # parse result block (|@?) + expected = expected l "\n" + } else if (testStarted) { + testStarted = 0 + checkTestResult(file,code,line,expected,stdOutF,stdErrF,exitCode,random) + } + } + if (err) die("error reading file: " file) + close(file) + if (testStarted) { + testStarted = 0 + checkTestResult(file,code,line,expected,stdOutF,stdErrF,exitCode,random) + } + } + if (All) printf "result=%s, failure=%d, success=%d, total=%d, files=%d\n", Failed ? "FAIL" : "SUCCESS", Failed, Success, Failed + Success, i - 1 +} +function die(err) { print err > "/dev/stderr"; exit 2 } +function checkTestResult(file, code, line, expected, stdOutF, stdErrF, exitCode, random, actual,expectF,d) { + actual = prefixFile("|",stdOutF) prefixFile("@",stdErrF) + if (exitCode != 0) actual = actual "? " exitCode "\n" + if (expected != actual) { + Failed++ + # printf "FAIL:\nexpected:\n#%s#\nactual:\n#%s#\n", expected, actual + # use diff to show the difference + print expected > (expectF = tmpFile(random, "exp")) + close(expectF) + print file ":" line ": $" code + print actual | (d = Diff " -u --label expected --label actual " expectF " -; rm " expectF) + close(d) + if (!All) exit 1 + } else Success++ +} +function prefixFile(prefix, fname, l,res,err) { + while ((err = getline l < fname) > 0) + res = res prefix " " l "\n" + if (err) die("error reading file: " fname) + close(fname) + return res +} +function run(code,stdOutF,stdErrF) { + return system("(" code ") 1>" stdOutF " 2>" stdErrF) # can it be that {} are better than ()? +} +function rndS() { return int(2147483647 * rand()) "." Rnd } +function tmpFile(random, ext) { return sprintf("%s/%s.%s.%s", Tmp, Prog, random, ext) } diff --git a/tests/fhtagn-0.1.0/gen_speed_test.awk b/tests/fhtagn-0.1.0/gen_speed_test.awk new file mode 100755 index 0000000..6502eaa --- /dev/null +++ b/tests/fhtagn-0.1.0/gen_speed_test.awk @@ -0,0 +1,12 @@ +#!/usr/bin/awk -f +BEGIN { + for (i=0; i&2; exit %d\n", i, i, i, (exitCode = i % 128) + printf "| aaa%d\n", i + printf "| bbb%d\n", i + printf "@ ccc%d\n", i + if (exitCode) printf "? %d\n", exitCode + print + } +} \ No newline at end of file diff --git a/tests/fhtagn-0.1.0/makesure b/tests/fhtagn-0.1.0/makesure new file mode 100755 index 0000000..5d19851 --- /dev/null +++ b/tests/fhtagn-0.1.0/makesure @@ -0,0 +1,627 @@ +#!/bin/sh +if command -v gawk >/dev/null;then makesure_awk='gawk -ltime -v Gawk=1';makesure_pre='';else makesure_awk=awk;makesure_pre='function gettimeofday(){}';fi +exec $makesure_awk -v "Version=0.9.20" -v "Prog=$0" "$makesure_pre"' +BEGIN { + Shell="bash" + SupportedShells["bash"] + SupportedShells["sh"] + SupportedOptions["tracing"] + SupportedOptions["silent"] + SupportedOptions["timing"] + DefinesCode="" + GlobCnt=0 + GlobGoalName="" + Mode="prelude" + srand() + prepareArgs() + MyDirScript="MYDIR=" quoteArg(getMyDir(ARGV[1]))";export MYDIR;cd \"$MYDIR\"" + Error="" + makesure()} +function makesure(i){ + while(getline>0){ + Lines[NR]=$0 + if($1~/^@/&&"@define"!=$1&&"@reached_if"!=$1)reparseCli() + if("@options"==$1)handleOptions() + else if("@define"==$1)handleDefine() + else if("@shell"==$1)handleShell() + else if("@goal"==$1){if("@glob"==$2||"@glob"==$3)handleGoalGlob();else handleGoal()} + else if("@doc"==$1)handleDoc() + else if("@depends_on"==$1)handleDependsOn() + else if("@reached_if"==$1)handleReachedIf() + else if("@lib"==$1)handleLib() + else if("@use_lib"==$1)handleUseLib() + else if($1~/^@/)addError("Unknown directive: " $1) + else handleCodeLine($0) + for(i=1;i<10;i++)$i=""} + doWork() + realExit(0)} +function prepareArgs(i,arg){ + for(i=2;i2)addError("nothing allowed after goal name")} +function validateParamName(p){ + if(p !~ /^[A-Z_][A-Z0-9_]*$/)addError("@param name should match /^[A-Z_][A-Z0-9_]*$/: '\''" p "'\''") + return p} +function registerGoal(priv,goalName){ + if(""==goalName||"@params"==goalName) + addError("Goal must have a name") + else if(goalName in GoalsByName) + addError("Goal " quote2(goalName,1)" is already defined") + else { + arrPush(GoalNames,goalName) + GoalsByName[goalName]=priv + return 1}} +function globGoal(i){return (GlobGoalName?GlobGoalName "@":"")GlobFiles[i]} +function calcGlob(goalName,pattern,script,file){ + GlobCnt=0 + GlobGoalName=goalName + delete GlobFiles + script=MyDirScript ";for f in " pattern ";do test -e \"$f\"&&echo \"$f\"||:;done" + if("sh"!=Shell) + script=Shell " -c " quoteArg(script) + while((script|getline file)>0){ + GlobCnt++ + arrPush(GlobFiles,file)} + closeErr(script) + quicksort(GlobFiles,0,arrLen(GlobFiles)-1)} +function parsePriv(){ + if("@private"!=$NF)return 0 + $NF="" + NF-- + return 1} +function handleGoalGlob(goalName,globAllGoal,globSingle,priv,i,pattern,nfMax){ + started("goal_glob") + priv=parsePriv() + if("@glob"==(goalName=$2)){ + goalName="";pattern=$(nfMax=3) + }else + pattern=$(nfMax=4) + if(NF>nfMax) + addError("nothing allowed after glob pattern") + else if(pattern=="") + addError("absent glob pattern") + else { + calcGlob(goalName,pattern) + globAllGoal=goalName?goalName:pattern + globSingle=GlobCnt==1&&globAllGoal==globGoal(0) + for(i=0;ignMaxLen&&gnLen<=30) + gnMaxLen=gnLen} + for(i=0;i in GoalNames;i++){ + goalName=GoalNames[i] + if(list&&GoalsByName[goalName]) + continue + printf " " + if(goalName in Doc) + printf "%-" gnMaxLen "s : %s\n",quote2(goalName),Doc[goalName] + else + print quote2(goalName)} + }else { + if(timingOn()) + t0=currentTimeMillis() + topologicalSort(1,ArgGoals,resolvedGoals,reachedGoals) + preludeCode=getPreludeCode() + for(i=0;i in GoalNames;i++){ + body=trim(Code[goalName=GoalNames[i]]) + emptyGoals[goalName]=""==body + goalBody[0]="" + addLine(goalBody,preludeCode) + addLine(goalBody,CodePre[goalName]) + if(goalName in GoalToLib) + addLine(goalBody,Lib[GoalToLib[goalName]]) + addLine(goalBody,body) + goalBodies[goalName]=goalBody[0]} + if("-d" in Args||"--resolved" in Args){ + printf "Resolved goals to reach for" + for(i=0;i in ArgGoals;i++) + printf " %s",quote2(ArgGoals[i],1) + print ":" + for(i=0;i in resolvedGoals;i++) + if(!reachedGoals[goalName=resolvedGoals[i]]&&!emptyGoals[goalName]) + print " " quote2(goalName) + }else { + for(i=0;i in resolvedGoals;i++){ + goalName=resolvedGoals[i] + goalTimed=timingOn()&&!reachedGoals[goalName]&&!emptyGoals[goalName] + if(goalTimed) + t1=t2?t2:currentTimeMillis() + if(!("silent" in Options)) + print " goal " quote2(goalName,1)" " (reachedGoals[goalName]?"[already satisfied].":emptyGoals[goalName]?"[empty].":"...") + exitCode=(reachedGoals[goalName]||emptyGoals[goalName])?0:shellExec(goalBodies[goalName],goalName) + if(exitCode!=0) + print " goal " quote2(goalName,1)" failed" + if(goalTimed){ + t2=currentTimeMillis() + print " goal " quote2(goalName,1)" took " renderDuration(t2 - t1)} + if(exitCode!=0) + break} + if(timingOn()) + print " total time " renderDuration((t2?t2:currentTimeMillis())- t0) + if(exitCode!=0) + realExit(exitCode)}}} +function topologicalSort(includeReachedIf,requestedGoals,result,reachedGoals,i,j,goalName,loop,depCnt){ + topologicalSortReset() + for(i=0;i in GoalNames;i++){ + depCnt=DependenciesCnt[goalName=GoalNames[i]] + for(j=0;j " loop[2])} +function isCodeAllowed(){return "goal"==Mode||"goal_glob"==Mode||"lib"==Mode} +function isPrelude(){return "prelude"==Mode} +function checkPreludeOnly(){if(!isPrelude())addError("Only use " $1 " in prelude")} +function checkGoalOnly(){if("goal"!=Mode&&"goal_glob"!=Mode)addError("Only use " $1 " in @goal")} +function currentGoalName(){return arrLast(GoalNames)} +function currentLibName(){return arrLast(LibNames)} +function realExit(code){ + exit code} +function addError(err,n){if(!n)n=NR;Error=addL(Error,err ":\n" ARGV[1] ":" n ": " Lines[n])} +function addErrorDedup(err,n){if((err,n)in AddedErrors)return;AddedErrors[err,n];addError(err,n)} +function die(msg,out){ + out="cat 1>&2" + print msg|out + close(out) + realExit(1)} +function checkConditionReached(goalName,conditionStr,script){ + script=getPreludeCode() + if(goalName in GoalToLib) + script=script "\n" Lib[GoalToLib[goalName]] + script=script "\n" conditionStr + return shellExec(script,goalName "@reached_if")==0} +function shellExec(script,comment,res){ + if("tracing" in Options){ + script=": " quoteArg(comment)"\n" script + script=Shell " -x -e -c " quoteArg(script) + }else + script=Shell " -e -c " quoteArg(script) + script="trap '\''exit 7'\'' INT;" script + res=system(script) + return res} +function getMyDir(makesurefilePath){ + return executeGetLine("cd \"$(dirname " quoteArg(makesurefilePath)")\";pwd")} +function handleCodeLine(line){ + if(!isCodeAllowed()&&line !~ /^[ \t]*#/&&trim(line)!=""){ + if(!ShellInPreludeErrorShown++) + addError("Shell code is not allowed outside goals/libs") + }else + addCodeLine(line)} +function addCodeLine(line,goalName,name,i){ + if("lib"==Mode){ + name=currentLibName() + Lib[name]=addL(Lib[name],line) + }else if("goal_glob"==Mode){ + for(i=0;i0){ + arrDel(GoalNames,goalName) + delete GoalsByName[goalName]}} +function instantiate(goal,args,newArgs,i,j,depArg,depArgType,dep,goalNameInstantiated,argsCnt,gi,gii,argsCode){ + goalNameInstantiated=instantiateGoalName(goal,args) + if(goalNameInstantiated!=goal){ + if(!(goalNameInstantiated in GoalsByName)) + arrPush(GoalNames,goalNameInstantiated) + copyKey(goal,goalNameInstantiated,GoalsByName) + copyKey(goal,goalNameInstantiated,DependenciesCnt) + copyKey(goal,goalNameInstantiated,CodePre) + copyKey(goal,goalNameInstantiated,Code) + copyKey(goal,goalNameInstantiated,Doc) + copyKey(goal,goalNameInstantiated,ReachedIf) + copyKey(goal,goalNameInstantiated,GoalToLib) + for(i in args) + argsCode=addL(argsCode,i "=" quoteArg(args[i])) + CodePre[goalNameInstantiated]=addL(CodePre[goalNameInstantiated],argsCode) + if(goalNameInstantiated in ReachedIf) + ReachedIf[goalNameInstantiated]=argsCode "\n" ReachedIf[goalNameInstantiated]} + for(i=0;i " newVer + }else print "you have latest version " Version " installed"} + rm(tmp) + if(err)die(err)} +function renderDuration(deltaMillis,\ + deltaSec,deltaMin,deltaHr,deltaDay,dayS,hrS,minS,secS,secSI,res){ + deltaSec=deltaMillis/1000 + deltaMin=0 + deltaHr=0 + deltaDay=0 + if(deltaSec>=60){ + deltaMin=int(deltaSec/60) + deltaSec=deltaSec - deltaMin*60} + if(deltaMin>=60){ + deltaHr=int(deltaMin/60) + deltaMin=deltaMin - deltaHr*60} + if(deltaHr>=24){ + deltaDay=int(deltaHr/24) + deltaHr=deltaHr - deltaDay*24} + dayS=deltaDay>0?deltaDay " d":"" + hrS=deltaHr>0?deltaHr " h":"" + minS=deltaMin>0?deltaMin " m":"" + secS=deltaSec>0?deltaSec " s":"" + secSI=deltaSec>0?int(deltaSec)" s":"" + if(dayS!="") + res=dayS " " (hrS==""?"0 h":hrS) + else if(deltaHr>0) + res=hrS " " (minS==""?"0 m":minS) + else if(deltaMin>0) + res=minS " " (secSI==""?"0 s":secSI) + else + res=deltaSec>0?secS:"0 s" + return res} +function executeGetLine(script,res){ + script|getline res + closeErr(script) + return res} +function closeErr(script){if(close(script)!=0)die("Error executing: " script)} +function dl(url,dest,verbose){ + verbose="VERBOSE" in ENVIRON + if(commandExists("wget")){ + if(!ok("wget " (verbose?"":"-q")" " quoteArg(url)" -O" quoteArg(dest))) + return "error with wget" + }else if(commandExists("curl")){ + if(!ok("curl " (verbose?"":"-s")" " quoteArg(url)" -o " quoteArg(dest))) + return "error with curl" + }else return "wget/curl not found"} +function natOrder(s1,s2,i1,i2,c1,c2,n1,n2){ + if(_digit(c1=substr(s1,i1,1))&&_digit(c2=substr(s2,i2,1))){ + n1=+c1;while(_digit(c1=substr(s1,++i1,1))){n1=n1*10+c1} + n2=+c2;while(_digit(c2=substr(s2,++i2,1))){n2=n2*10+c2} + return n1==n2?natOrder(s1,s2,i1,i2):_cmp(n1,n2)} + while((c1=substr(s1,i1,1))==(c2=substr(s2,i2,1))&&c1!=""&&!_digit(c1)){ + i1++;i2++} + return _digit(c1)&&_digit(c2)?natOrder(s1,s2,i1,i2):_cmp(c1,c2)} +function _cmp(v1,v2){return v1>v2?1:v1="0"&&c<="9"} +function quicksort(data,left,right,i,last){ + if(left>=right) + return + quicksortSwap(data,left,int((left+right)/2)) + last=left + for(i=left+1;i<=right;i++) + if(natOrder(data[i],data[left],1,1)<0) + quicksortSwap(data,++last,i) + quicksortSwap(data,left,last) + quicksort(data,left,last - 1) + quicksort(data,last+1,right)} +function quicksortSwap(data,i,j,temp){ + temp=data[i] + data[i]=data[j] + data[j]=temp} +function parseCli(line,res,pos,c,last,is_doll,c1){ + for(pos=1;;){ + while((c=substr(line,pos,1))==" "||c=="\t")pos++ + if((c=substr(line,pos,1))=="#"||c=="") + return + else { + if((is_doll=c=="$")&&substr(line,pos+1,1)=="'\''"||c=="'\''"){ + if(is_doll) + pos++ + res[last=res[-7]++]="" + res[last,"quote"]=is_doll?"$":"'\''" + while((c=substr(line,++pos,1))!="'\''"){ + if(c=="") + return "unterminated argument" + else if(is_doll&&c=="\\"&&((c1=substr(line,pos+1,1))=="'\''"||c1==c)){ + c=c1;pos++} + res[last]=res[last] c} + if((c=substr(line,++pos,1))!=""&&c!=" "&&c!="\t") + return "joined arguments" + }else { + res[last=res[-7]++]=c + while((c=substr(line,++pos,1))!=""&&c!=" "&&c!="\t"){ + if(c=="'\''") + return "joined arguments" + res[last]=res[last] c}}}}} +function reparseCli(res,i,err){ + err=parseCli($0,res) + if(err){ + addError("Syntax error: " err) + die(Error) + }else { + $0="" + for(i=NF=0;i in res;i++){ + $(++NF)=res[i] + Quotes[NF]=res[i,"quote"]}}} +function quote2(s,force){ + if(index(s,"'\''")){ + gsub(/\\/,"\\\\",s) + gsub(/'\''/,"\\'\''",s) + return "$'\''" s "'\''" + }else + return force||s~/[^a-zA-Z0-9.,@_\/=+-]/?"'\''" s "'\''":s} +function addLine(target,line){target[0]=addL(target[0],line)} +function addL(s,l){return s?s "\n" l:l} +function arrPush(arr,elt){arr[arr[-7]++]=elt} +function arrLen(arr){return +arr[-7]} +function arrDel(arr,v,l,i,e,resArr){ + l=arrLen(arr) + for(i=0;i0?arr[l-1]:""} +function commandExists(cmd){return ok("command -v " cmd " >/dev/null")} +function ok(cmd){return system(cmd)==0} +function isFile(path){return ok("test -f " quoteArg(path))} +function rm(f){system("rm " quoteArg(f))} +function quoteArg(a){gsub("'\''","'\''\\'\'''\''",a);return "'\''" a "'\''"} +function trim(s){sub(/^[ \t\r\n]+/,"",s);sub(/[ \t\r\n]+$/,"",s);return s} +function copyKey(keySrc,keyDst,arr){if(keySrc in arr)arr[keyDst]=arr[keySrc]}' Makesurefile "$@" diff --git a/tests/fhtagn-0.1.0/tests/1.tush b/tests/fhtagn-0.1.0/tests/1.tush new file mode 100644 index 0000000..cfbbbd4 --- /dev/null +++ b/tests/fhtagn-0.1.0/tests/1.tush @@ -0,0 +1,15 @@ + +success +======= + +$ echo OK +| OK + + +error +===== + +$ echo "hello world"; echo "error msg" >&2; exit 7 +| hello world +@ error msg +? 7 diff --git a/tests/fhtagn-0.1.0/tests/2.tush b/tests/fhtagn-0.1.0/tests/2.tush new file mode 100644 index 0000000..77cd11b --- /dev/null +++ b/tests/fhtagn-0.1.0/tests/2.tush @@ -0,0 +1,15 @@ + +success +======= + +$ echo OK +| OK + + +error +===== + +$ echo "hello world"; echo "error msg" >&2; exit 7 +| hello world +@ error msg 444 +? 8 diff --git a/tests/fhtagn-0.1.0/tests/3.tush b/tests/fhtagn-0.1.0/tests/3.tush new file mode 100644 index 0000000..ee4ba2d --- /dev/null +++ b/tests/fhtagn-0.1.0/tests/3.tush @@ -0,0 +1,8 @@ + +testing the 2nd test goes immediately after the 1st +=================================================== + +$ echo aaa +| aaa +$ echo bbb +| BBB \ No newline at end of file diff --git a/tests/fhtagn-0.1.0/tests/4-multiline.tush b/tests/fhtagn-0.1.0/tests/4-multiline.tush new file mode 100644 index 0000000..2dc095c --- /dev/null +++ b/tests/fhtagn-0.1.0/tests/4-multiline.tush @@ -0,0 +1,28 @@ + +OK 1 +==== + +$ echo 111; \ + echo 222 +| 111 +| 222 + +OK 2 +==== + +$ echo 111; \ + echo 222; \ + echo 333 >&2 ; \ + exit 7 +| 111 +| 222 +@ 333 +? 7 + +Test err +======== + +$ echo 111; \ + echo 222 +| 111 +| 777 diff --git a/tests/fhtagn-0.1.0/tests/fhtagn.tush b/tests/fhtagn-0.1.0/tests/fhtagn.tush new file mode 100644 index 0000000..d541be4 --- /dev/null +++ b/tests/fhtagn-0.1.0/tests/fhtagn.tush @@ -0,0 +1,71 @@ + +$ cd "$MYDIR"; $FHTAGN -- absent +@ error reading file: absent +? 2 + +$ cd "$MYDIR"; $FHTAGN -- tests/1.tush + +$ cd "$MYDIR"; ./tests/trimBlank.sh $FHTAGN tests/2.tush +| tests/2.tush:12: $ echo "hello world"; echo "error msg" >&2; exit 7 +| --- expected +| +++ actual +| @@ -1,4 +1,4 @@ +| | hello world +| -@ error msg 444 +| -? 8 +| +@ error msg +| +? 7 +| ---blank--- +? 1 + +$ cd "$MYDIR"; ./tests/trimBlank.sh $FHTAGN tests/3.tush +| tests/3.tush:7: $ echo bbb +| --- expected +| +++ actual +| @@ -1,2 +1,2 @@ +| -| BBB +| +| bbb +| ---blank--- +? 1 + +$ cd "$MYDIR"; ./tests/trimBlank.sh $FHTAGN tests/1.tush tests/3.tush +| tests/3.tush:7: $ echo bbb +| --- expected +| +++ actual +| @@ -1,2 +1,2 @@ +| -| BBB +| +| bbb +| ---blank--- +? 1 + +$ cd "$MYDIR"; ALL=1 ./tests/trimBlank.sh $FHTAGN tests/1.tush tests/2.tush tests/3.tush +| tests/2.tush:12: $ echo "hello world"; echo "error msg" >&2; exit 7 +| --- expected +| +++ actual +| @@ -1,4 +1,4 @@ +| | hello world +| -@ error msg 444 +| -? 8 +| +@ error msg +| +? 7 +| ---blank--- +| tests/3.tush:7: $ echo bbb +| --- expected +| +++ actual +| @@ -1,2 +1,2 @@ +| -| BBB +| +| bbb +| ---blank--- +| result=FAIL, failure=2, success=4, total=6, files=3 + +$ cd "$MYDIR"; ./tests/trimBlank.sh $FHTAGN tests/4-multiline.tush +| tests/4-multiline.tush:25: $ echo 111; \ +| echo 222 +| --- expected +| +++ actual +| @@ -1,3 +1,3 @@ +| | 111 +| -| 777 +| +| 222 +| ---blank--- +? 1 diff --git a/tests/fhtagn-0.1.0/tests/trimBlank.sh b/tests/fhtagn-0.1.0/tests/trimBlank.sh new file mode 100755 index 0000000..e9f4708 --- /dev/null +++ b/tests/fhtagn-0.1.0/tests/trimBlank.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# trims blank lines but preserves exit code + +o=$(mktemp) +e=$(mktemp) + +( "$@" ) 1>"$o" 2>"$e" + +err=$? + +awkScript='{ print NF ? $0 : "---blank---" }' + +awk "$awkScript" "$o" +awk "$awkScript" "$e" >&2 + +rm "$o" "$e" + +exit $err \ No newline at end of file diff --git a/tests/sanity.tush b/tests/sanity.tush new file mode 100644 index 0000000..90e1c77 --- /dev/null +++ b/tests/sanity.tush @@ -0,0 +1,30 @@ +Try every command +================= + +Remove previously created files if present +$ rm -rf example file.txt + +$ ../bin/vte +@ usage: vte [-h] {generate,preprocess,list,quickstart} ... +@ vte: error: the following arguments are required: subcmd +? 2 + +Expecting no output yet +$ ../bin/vte list + +Quickstart does not generate the output directory +$ mkdir example + +$ echo "Example" | ../bin/vte quickstart -o example +| Verification Template Engine Quickstart +| Template directory: example +| Template Description []? + + +$ VTE_TEMPLATE_PATH=. ../bin/vte list +| example - Example + +$ touch file.txt +$ VTE_TEMPLATE_PATH=. ../bin/vte preprocess file.txt + +$ VTE_TEMPLATE_PATH=. ../bin/vte generate example hello