1

From some Googling (I'm no bash expert by any means) I was able to put together a bash script that allows me to run a test suite and output a status bar at the bottom while it runs. It typically takes about 10 hours, and the status bar tells me how many tests passed and how many failed.

It works great sometimes, however occasionally I will run into an infinite loop, which is bad (mmm-kay?). Here's the code I'm using:

#!/bin/bash
WHITE="\033[0m"
GREEN="\033[32m"
RED="\033[31m"

(run_test_suite 2>&1) | tee out.txt |
while IFS=read -r line;
do
    printf "%$(tput cols)s\r" " ";
    printf "%s\n" "$line";

    printf "${WHITE}Passing Tests: ${GREEN}$(grep -c passed out.txt)\t"         2>&1;
    printf "${WHITE}Failed Tests: ${RED}$(   grep -c FAILED out.txt)${WHITE}\r" 2>&1;
done

What happens when I encounter the bug is I'll have an error message repeat infinitely, causing the log file (out.txt) to become some multi-megabyte monstrosity (I think it got into the GB's once). Here's an example error that repeats (with four lines of whitespace between each set):

warning caused by MY::Custom::Perl::Module::TEST_FUNCTION
print() on closed filehandle GEN3663 at /some/CPAN/Perl/Module.pm line 123.

I've tried taking out the 2>&1 redirect, and I've tried changing while IFS=read -r line; to while read -r line;, but I keep getting the infinite loop. What's stranger is this seems to happen most of the time, but there have been times I finish the long test suite without any problems.

EDIT:

The reason I'm writing this is to upgrade from a black & white test suite to a color-coded test suite (hence the ANSI codes). Previously, I would run the test suite using

run_test_suite > out.txt 2>&1 &
watch 'grep -c FAILED out.txt; grep -c passed out.txt; tail -20 out.txt'

Running it this way gets the same warning from Perl, but prints it to the file and moves on, rather than getting stuck in an infinite loop. Using watch, also prints stuff like [32m rather than actually rendering the text as green.

redbmk
  • 4,687
  • 3
  • 25
  • 49
  • What handle is being printed to? – ikegami Feb 20 '13 at 22:35
  • 1
    And what reason do you have to believe that there is a bug in `tee`? What indication do you have that the problem is not in the test suite? – tripleee Feb 20 '13 at 22:35
  • 2
    you're currently assigning the value `read` to IFS and attempting to run a command named `-r` with an argument of line. Also expanding values (especially command output) into the first arg of `printf` isn't a good idea. You certainly have a bug in `run_test_suite`, this doesn't have to do with `tee`. – ormaaj Feb 20 '13 at 22:36
  • I realize there are warnings and errors in the test suite, but that warning shouldn't halt the suite altogether. I've edited the question to show the command I used previously, which works fine (i.e. doesn't get in an infinite loop even if it runs into that warning), but doesn't render color correctly. – redbmk Feb 21 '13 at 00:12
  • The way I understand `read` is that the `-r` flag tells it that [Backslash does not act as an excape character](http://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html) and that `line` will store the value that it read. I'm not sure how IFS works, but like I said - changing it to `read -r line` with `IFS=` doesn't fix the problem. – redbmk Feb 21 '13 at 00:19
  • @redbmk: `IFS` is a variable you can assign to. If you add a space between `IFS=` and `read`, the empty string will be assigned to it prior to invoking the `read` command, which is probably what the example you are modifying does. The shell is very sensitive to whitespace. – reinierpost Feb 21 '13 at 11:49

2 Answers2

0

The bug is in your script. That's not an IO error; that's an illegal argument error. That error happens when the variable you provide as a handle isn't a handle at all, or is one that you've closed.

Writing to a broken pipe results in the process being killed by SIGPIPE or in print returning false with $! set to EPIPE.

squiguy
  • 32,370
  • 6
  • 56
  • 63
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • The script `run_test_suite` runs fine by itself (and still gets the filehandle error), but I want to both (A) store the output in a text file, and (B) display it in realtime (in color) with a status line – redbmk Feb 21 '13 at 00:25
  • The question you asked is about some infinite loop. If you have problems storing the output or displaying something in color, you should ask another question. – ikegami Feb 21 '13 at 00:55
  • If you want more help solving your problem, you're going to have to provide some information about it. We can't help you if you insist on looking away from where the problem resides. – ikegami Feb 21 '13 at 00:56
  • I'll have to take a closer look at where that warning is trying to print and get back to you. I was really wondering why it would print normally and continue on when I run it by itself but get stuck in a loop when I wrap it in this script. There must be something here that I can change to fix it without changing the Perl (so that even if I fix the code now and it breaks again in the future this script doesn't get caught in another infinite loop) – redbmk Feb 21 '13 at 04:04
  • Very constructive. Thanks for pointing out that the code has faults; I clearly had no idea. I just wanted to post it on here to show how wonderful it was. – redbmk Feb 21 '13 at 07:08
  • I thought you posted here because you wanted help? You keep looking at the wrong place, so yeah, I needed to point at where the problem is. I'd help you more, but you haven't given us the means despite the fact that I keep bringing it up. – ikegami Feb 21 '13 at 07:15
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/24873/discussion-between-redbmk-and-ikegami) – redbmk Feb 21 '13 at 07:23
  • I looked into the test suite and was able to find that one of the perl modules used a `select($fh)` without reselecting STDOUT, so instead of `print` using STDOUT by default it was trying to print to the closed filehandle. That fixed the bug, but I'd still like to know how to prevent an infinite loop in the bash script in case something like this happens again. – redbmk Mar 12 '13 at 17:41
  • There was no infinite loop in the bash script. – ikegami Mar 12 '13 at 18:09
  • Well, it always finished when run in the background. If I use something like `tail -f` to watch the output then there's no loop, but it would be nice for it all to run in one process. I get what you're saying about it having to do with piping (`|`) a broken pipe, but is there any way around that without writing it all to a file then watching the file in a separate process? – redbmk Mar 12 '13 at 18:17
  • There's always a loop, see the word `while`? And the more data to process, the longer the loop. That doesn't make it an infinite loop. – ikegami Mar 12 '13 at 18:52
  • you're saying that there is more data to process if it runs in the same process than if it is read from a file? – redbmk Mar 12 '13 at 18:59
  • If I output to a file, then run the script through something like `tail -f` to read from said file, then the script finishes in a timely manner. If I pipe it directly to this script it instead never (apparently a relative term) finishes. See the answer I posted, and my comment from about an hour ago. – redbmk Mar 12 '13 at 19:15
  • Aah, ok. So what about something like `run_full_test | tee out.txt | tail -f | while IFS= read line; do .....`? Would that help or does it still have to load everything in memory? – redbmk Mar 12 '13 at 19:28
0

I was able to fix the perl errors and the bash script seems to work well now after a few modifications. However, it seems this would be a safer way to run the test suite in case something like that were to happen in the future:

#!/bin/bash
WHITE="\033[0m"
GREEN="\033[32m"
RED="\033[31m"

run_full_test > out.txt 2>&1 &
tail -f out.txt | while IFS= read line; do
    printf "%$(tput cols)s\r" " ";
    printf "%s\n" "$line";

    printf "${WHITE}Passing Tests: ${GREEN}$(grep -c passed     out.txt)\t"         2>&1;
    printf "${WHITE}Failed Tests: ${RED}$(   grep -c 'FAILED!!' out.txt)${WHITE}\r" 2>&1;
done

There are some downsides to this. Mainly, if I hit Ctrl-C to stop the test, it appears to have stopped, but really run_full_test is still running in the background and I need to remember to kill it manually. Also, when the test is finished tail -f is still running. In other words there are two processes running here and they are not in sync.

Here is the original script, slightly modified, which addresses those problems, but isn't foolproof (i.e. can get stuck in an infinite loop if run_full_test has issues):

#!/bin/bash
WHITE="\033[0m"
GREEN="\033[32m"
RED="\033[31m"

(run_full_test 2>&1) | tee out.txt | while IFS= read line; do
    printf "%$(tput cols)s\r" " ";
    printf "%s\n" "$line";

    printf "${WHITE}Passing Tests: ${GREEN}$(grep -c passed     out.txt)\t"         2>&1;
    printf "${WHITE}Failed Tests: ${RED}$(   grep -c 'FAILED!!' out.txt)${WHITE}\r" 2>&1;
done
redbmk
  • 4,687
  • 3
  • 25
  • 49
  • This is good enough - but if anyone has a better answer that addresses both issues (running in one process and not being fragile to whatever might happen in `run_full_test`) that would be much appreciated – redbmk Mar 12 '13 at 18:58