Skip to content

Commit 375034a

Browse files
committed
Even with unbuffered stdout/stderr, 'smt run' buffers so we can't win.
1 parent 5cf011b commit 375034a

File tree

4 files changed

+84
-56
lines changed

4 files changed

+84
-56
lines changed

femb_python/commands.py

Lines changed: 22 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
#!/usr/bin/env python3
21

32
import os
4-
from subprocess import Popen, PIPE, STDOUT
3+
import sys
4+
from subprocess import Popen, PIPE, STDOUT, TimeoutExpired
55

6-
def dump(oline, eline):
7-
if oline:
8-
print ("Output: %s" % oline)
9-
if eline:
10-
print ("Output: %s" % eline)
6+
def noop(text):
7+
pass
118

12-
13-
def execute(cmdstr, cwd = ".", env = os.environ, out = dump, shell=True):
9+
def execute(cmdstr, outcb=noop, errcb=noop, cwd = ".", env = os.environ, shell=True):
1410
'''
1511
Execute the given command string. Return the exit code.
1612
@@ -19,10 +15,9 @@ def execute(cmdstr, cwd = ".", env = os.environ, out = dump, shell=True):
1915
If "env" is given it is used as an environment dictionary for the
2016
execution, o.w. os.environ will be used.
2117
22-
If "out" is given it will be called once with either a line of
23-
output or a line of error or both. It may be called multiple
24-
times as the process runs. Any trailing newline on a line is
25-
stripped.
18+
If "out" is a callable taking two arguments which may be None or
19+
text. It is called per poll of the running command if any text
20+
was produced on stdout/stderr.
2621
2722
If "shell" is True then the cmdstr is passed to the shell.
2823
@@ -34,48 +29,29 @@ def execute(cmdstr, cwd = ".", env = os.environ, out = dump, shell=True):
3429
if not shell and type(cmdstr) == type(""):
3530
cmdstr = cmdstr.split()
3631

37-
def slurp(oline, eline): # send line to all outs
38-
if oline and oline[-1] == '\n':
39-
oline = oline[:-1]
40-
if eline and eline[-1] == '\n':
41-
eline = eline[:-1]
42-
out(oline, eline)
43-
return
44-
45-
print('Executing: %s' % cmdstr)
4632
try:
47-
proc = Popen(cmdstr, stdout=PIPE, stderr=PIPE, shell=shell, cwd=cwd,
48-
universal_newlines=True, env=env)
33+
proc = Popen(cmdstr, stdout=PIPE, stderr=PIPE,
34+
shell=shell, cwd=cwd, env=env,
35+
bufsize=1, universal_newlines=True) # line buffered
4936
except OSError as err:
5037
print (err)
5138
raise
5239

53-
54-
# While command is polled as running, slurp its output and marshal
55-
# it to out() until command finishes
56-
res = None
5740
while True:
58-
oline = proc.stdout.readline()
59-
eline = proc.stderr.readline()
60-
res = proc.poll()
61-
62-
if oline or eline:
63-
slurp(oline, eline)
64-
65-
if res is None: # still running
41+
try:
42+
o,e = proc.communicate(timeout=1)
43+
outcb(o)
44+
errcb(e)
45+
except TimeoutExpired:
46+
pass
47+
48+
if proc.returncode is None:
6649
continue
67-
68-
for line in proc.stdout.readlines():
69-
slurp(line,None) # drain any remaining
70-
for line in proc.stderr.readlines():
71-
slurp(None,line) # drain any remaining
72-
7350
break
74-
75-
if res == 0:
76-
print('Command: "%s" succeeded' % cmdstr)
51+
52+
if proc.returncode == 0:
7753
return proc.returncode
78-
err = 'Command: "%s" failed with code %d in directory %s' % (cmdstr, res, cwd)
54+
err = 'Command: "%s" failed with code %d in directory %s' % (cmdstr, proc.returncode, cwd)
7955
print (err)
8056
raise RuntimeError( err )
8157

femb_python/runpolicy.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
88
'''
99
import os
10+
import sys
1011
import json
1112
import time
1213
#import subprocess
@@ -113,15 +114,18 @@ def exec(self, cmdstr, cwd=".", shell=True):
113114

114115
stdout = list()
115116
stderr = list()
116-
117-
def out(oline, eline):
118-
if oline:
119-
stdout.append(oline)
120-
print ("Output: %s" % oline)
121-
if eline:
122-
stderr.append(eline)
123-
print ("Error: %s" % eline)
124-
rc = commands.execute(cmdstr, shell=shell, cwd=cwd, out=out)
117+
def output(text):
118+
if not text: return
119+
stdout.append(text)
120+
sys.stdout.write("Output: " + text)
121+
sys.stdout.flush()
122+
def errput(text):
123+
if not text: return
124+
stderr.append(text)
125+
sys.stderr.write("Error: " + text)
126+
127+
rc = commands.execute(cmdstr, output, errput,
128+
shell=shell, cwd=cwd)
125129

126130
stdout = '\n'.join(stdout)
127131
stderr = '\n'.join(stderr)

test/live-spew.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
3+
# This spews stuff expecting to get live output for testing under a
4+
# Sumatra runner.
5+
6+
dofail=${1}
7+
8+
echo "live-spew $@"
9+
10+
for n in {0..3}
11+
do
12+
date
13+
echo "message number $n to stdout"
14+
echo "message number $n to stderr" 1>&2
15+
sleep 1
16+
done
17+
18+
if [ "$dofail" = "fail" ] ; then
19+
echo "live-spew.sh will fail now"
20+
echo "live-spew.sh will fail now" 1>&2
21+
exit 1
22+
fi
23+
24+
exit 0

test/test_runpolicy.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,37 @@ def test_fail():
116116
else:
117117
raise RuntimeError("test of /bin/false failed to fail")
118118

119+
def test_live():
120+
clear()
121+
testdir = os.path.dirname(os.path.realpath(__file__))
122+
prog = os.path.join(testdir, "live-spew.sh")
123+
r = make_runner(SumatraRunner, smtname="test_live",
124+
executable=prog, argstr="",
125+
#stdout="/tmp/out1.log", stderr="/tmp/err1.log"
126+
)
127+
r()
119128

129+
f = make_runner(SumatraRunner, smtname="test_live",
130+
executable=prog, argstr="fail",
131+
stdout="/dev/stdout", stderr="/dev/stderr"
132+
#stdout="/tmp/out2.log", stderr="/tmp/err2.log"
133+
)
134+
try:
135+
f()
136+
except RuntimeError:
137+
print ("Test of failure successfully failed.")
138+
pass
139+
else:
140+
raise RuntimeError("test of failure failed to fail")
141+
142+
120143

121144
if '__main__' == __name__:
145+
122146
test_basic()
123147
test_direct()
124148
test_default_args_only()
125149
test_per_call_args()
126150
test_with_sumatra()
127151
test_fail()
128-
152+
test_live()

0 commit comments

Comments
 (0)