hgbook

view en/examples/run-example @ 136:7b5894fffc37

Don't falsely signal success to make.
author Bryan O'Sullivan <bos@serpentine.com>
date Mon Mar 05 20:26:23 2007 -0800 (2007-03-05)
parents c9aad709bd3a
children 9d7dffe74b2c
line source
1 #!/usr/bin/env python
2 #
3 # This program takes something that resembles a shell script and runs
4 # it, spitting input (commands from the script) and output into text
5 # files, for use in examples.
7 import cStringIO
8 import errno
9 import getopt
10 import os
11 import pty
12 import re
13 import select
14 import shutil
15 import signal
16 import stat
17 import sys
18 import tempfile
19 import time
21 tex_subs = {
22 '\\': '\\textbackslash{}',
23 '{': '\\{',
24 '}': '\\}',
25 }
27 def gensubs(s):
28 start = 0
29 for i, c in enumerate(s):
30 sub = tex_subs.get(c)
31 if sub:
32 yield s[start:i]
33 start = i + 1
34 yield sub
35 yield s[start:]
37 def tex_escape(s):
38 return ''.join(gensubs(s))
40 class example:
41 shell = '/usr/bin/env bash'
42 ps1 = '__run_example_ps1__ '
43 ps2 = '__run_example_ps2__ '
44 pi_re = re.compile(r'#\$\s*(name):\s*(.*)$')
46 timeout = 5
48 def __init__(self, name, verbose):
49 self.name = name
50 self.verbose = verbose
51 self.poll = select.poll()
53 def parse(self):
54 '''yield each hunk of input from the file.'''
55 fp = open(self.name)
56 cfp = cStringIO.StringIO()
57 for line in fp:
58 cfp.write(line)
59 if not line.rstrip().endswith('\\'):
60 yield cfp.getvalue()
61 cfp.seek(0)
62 cfp.truncate()
64 def status(self, s):
65 sys.stdout.write(s)
66 if not s.endswith('\n'):
67 sys.stdout.flush()
69 def send(self, s):
70 if self.verbose:
71 print >> sys.stderr, '>', self.debugrepr(s)
72 while s:
73 count = os.write(self.cfd, s)
74 s = s[count:]
76 def debugrepr(self, s):
77 rs = repr(s)
78 limit = 60
79 if len(rs) > limit:
80 return ('%s%s ... [%d bytes]' % (rs[:limit], rs[0], len(s)))
81 else:
82 return rs
84 timeout = 5
86 def read(self):
87 events = self.poll.poll(self.timeout * 1000)
88 if not events:
89 print >> sys.stderr, '[timed out after %d seconds]' % self.timeout
90 os.kill(self.pid, signal.SIGHUP)
91 return ''
92 return os.read(self.cfd, 1024)
94 def receive(self):
95 out = cStringIO.StringIO()
96 while True:
97 try:
98 if self.verbose:
99 sys.stderr.write('< ')
100 s = self.read()
101 except OSError, err:
102 if err.errno == errno.EIO:
103 return '', ''
104 raise
105 if self.verbose:
106 print >> sys.stderr, self.debugrepr(s)
107 out.write(s)
108 s = out.getvalue()
109 if s.endswith(self.ps1):
110 return self.ps1, s.replace('\r\n', '\n')[:-len(self.ps1)]
111 if s.endswith(self.ps2):
112 return self.ps2, s.replace('\r\n', '\n')[:-len(self.ps2)]
114 def sendreceive(self, s):
115 self.send(s)
116 ps, r = self.receive()
117 if r.startswith(s):
118 r = r[len(s):]
119 return ps, r
121 def run(self):
122 ofp = None
123 basename = os.path.basename(self.name)
124 self.status('running %s ' % basename)
125 tmpdir = tempfile.mkdtemp(prefix=basename)
127 # remove the marker file that we tell make to use to see if
128 # this run succeeded
129 try:
130 os.unlink(self.name + '.run')
131 except OSError, err:
132 if err.errno != errno.ENOENT:
133 raise
135 rcfile = os.path.join(tmpdir, '.hgrc')
136 rcfp = open(rcfile, 'w')
137 print >> rcfp, '[ui]'
138 print >> rcfp, "username = Bryan O'Sullivan <bos@serpentine.com>"
140 rcfile = os.path.join(tmpdir, '.bashrc')
141 rcfp = open(rcfile, 'w')
142 print >> rcfp, 'PS1="%s"' % self.ps1
143 print >> rcfp, 'PS2="%s"' % self.ps2
144 print >> rcfp, 'unset HISTFILE'
145 print >> rcfp, 'export EXAMPLE_DIR="%s"' % os.getcwd()
146 print >> rcfp, 'export HGMERGE=merge'
147 print >> rcfp, 'export LANG=C'
148 print >> rcfp, 'export LC_ALL=C'
149 print >> rcfp, 'export TZ=GMT'
150 print >> rcfp, 'export HGRC="%s/.hgrc"' % tmpdir
151 print >> rcfp, 'export HGRCPATH=$HGRC'
152 print >> rcfp, 'cd %s' % tmpdir
153 rcfp.close()
154 sys.stdout.flush()
155 sys.stderr.flush()
156 self.pid, self.cfd = pty.fork()
157 if self.pid == 0:
158 cmdline = ['/usr/bin/env', 'bash', '--noediting', '--noprofile',
159 '--norc']
160 try:
161 os.execv(cmdline[0], cmdline)
162 except OSError, err:
163 print >> sys.stderr, '%s: %s' % (cmdline[0], err.strerror)
164 sys.stderr.flush()
165 os._exit(0)
166 self.poll.register(self.cfd, select.POLLIN | select.POLLERR |
167 select.POLLHUP)
169 prompts = {
170 '': '',
171 self.ps1: '$',
172 self.ps2: '>',
173 }
175 try:
176 try:
177 # eat first prompt string from shell
178 self.read()
179 # setup env and prompt
180 ps, output = self.sendreceive('source %s\n' % rcfile)
181 for hunk in self.parse():
182 # is this line a processing instruction?
183 m = self.pi_re.match(hunk)
184 if m:
185 pi, rest = m.groups()
186 if pi == 'name':
187 self.status('.')
188 out = rest
189 assert os.sep not in out
190 if out:
191 ofp = open('%s.%s.out' % (self.name, out), 'w')
192 else:
193 ofp = None
194 elif hunk.strip():
195 # it's something we should execute
196 newps, output = self.sendreceive(hunk)
197 if not ofp:
198 continue
199 # first, print the command we ran
200 if not hunk.startswith('#'):
201 nl = hunk.endswith('\n')
202 hunk = ('%s \\textbf{%s}' %
203 (prompts[ps],
204 tex_escape(hunk.rstrip('\n'))))
205 if nl: hunk += '\n'
206 ofp.write(hunk)
207 # then its output
208 ofp.write(tex_escape(output))
209 ps = newps
210 self.status('\n')
211 except:
212 print >> sys.stderr, '(killed)'
213 os.kill(self.pid, signal.SIGKILL)
214 pid, rc = os.wait()
215 raise
216 else:
217 try:
218 ps, output = self.sendreceive('exit\n')
219 if ofp:
220 ofp.write(output)
221 os.close(self.cfd)
222 except IOError:
223 pass
224 os.kill(self.pid, signal.SIGTERM)
225 pid, rc = os.wait()
226 if rc:
227 if os.WIFEXITED(rc):
228 print >> sys.stderr, '(exit %s)' % os.WEXITSTATUS(rc)
229 elif os.WIFSIGNALED(rc):
230 print >> sys.stderr, '(signal %s)' % os.WTERMSIG(rc)
231 else:
232 open(self.name + '.run', 'w')
233 return rc
234 finally:
235 shutil.rmtree(tmpdir)
237 def main(path='.'):
238 opts, args = getopt.getopt(sys.argv[1:], 'v', ['verbose'])
239 verbose = False
240 for o, a in opts:
241 if o in ('-v', '--verbose'):
242 verbose = True
243 errs = 0
244 if args:
245 for a in args:
246 try:
247 st = os.lstat(a)
248 except OSError, err:
249 print >> sys.stderr, '%s: %s' % (a, err.strerror)
250 errs += 1
251 continue
252 if stat.S_ISREG(st.st_mode) and st.st_mode & 0111:
253 if example(a, verbose).run():
254 errs += 1
255 else:
256 print >> sys.stderr, '%s: not a file, or not executable' % a
257 errs += 1
258 return errs
259 for name in os.listdir(path):
260 if name == 'run-example' or name.startswith('.'): continue
261 if name.endswith('.out') or name.endswith('~'): continue
262 if name.endswith('.run'): continue
263 pathname = os.path.join(path, name)
264 st = os.lstat(pathname)
265 if stat.S_ISREG(st.st_mode) and st.st_mode & 0111:
266 if example(pathname, verbose).run():
267 errs += 1
268 print >> open(os.path.join(path, '.run'), 'w'), time.asctime()
269 return errs
271 if __name__ == '__main__':
272 sys.exit(main())