Mercurial > hgbook
comparison en/examples/run-example @ 776:019040fbf5f5
merged to upstream: phase 1
author | Yoshiki Yazawa <yaz@honeyplanet.jp> |
---|---|
date | Tue, 21 Apr 2009 00:36:40 +0900 |
parents | 73aa295a40da 3b640272a966 |
children |
comparison
equal
deleted
inserted
replaced
389:5981a0f7540a | 776:019040fbf5f5 |
---|---|
5 # files, for use in examples. | 5 # files, for use in examples. |
6 | 6 |
7 import cStringIO | 7 import cStringIO |
8 import errno | 8 import errno |
9 import getopt | 9 import getopt |
10 import glob | |
10 import os | 11 import os |
11 import pty | 12 import pty |
12 import re | 13 import re |
13 import select | 14 import select |
14 import shutil | 15 import shutil |
16 import stat | 17 import stat |
17 import sys | 18 import sys |
18 import tempfile | 19 import tempfile |
19 import time | 20 import time |
20 | 21 |
21 tex_subs = { | 22 xml_subs = { |
22 '\\': '\\textbackslash{}', | 23 '<': '<', |
23 '{': '\\{', | 24 '>': '>', |
24 '}': '\\}', | 25 '&': '&', |
25 } | 26 } |
26 | 27 |
27 def gensubs(s): | 28 def gensubs(s): |
28 start = 0 | 29 start = 0 |
29 for i, c in enumerate(s): | 30 for i, c in enumerate(s): |
30 sub = tex_subs.get(c) | 31 sub = xml_subs.get(c) |
31 if sub: | 32 if sub: |
32 yield s[start:i] | 33 yield s[start:i] |
33 start = i + 1 | 34 start = i + 1 |
34 yield sub | 35 yield sub |
35 yield s[start:] | 36 yield s[start:] |
36 | 37 |
37 def tex_escape(s): | 38 def xml_escape(s): |
38 return ''.join(gensubs(s)) | 39 return ''.join(gensubs(s)) |
39 | 40 |
40 def maybe_unlink(name): | 41 def maybe_unlink(name): |
41 try: | 42 try: |
42 os.unlink(name) | 43 os.unlink(name) |
51 name = os.path.join(p, program) | 52 name = os.path.join(p, program) |
52 if os.access(name, os.X_OK): | 53 if os.access(name, os.X_OK): |
53 return p | 54 return p |
54 return None | 55 return None |
55 | 56 |
57 def result_name(name): | |
58 return os.path.normpath(os.path.join('results', name.replace(os.sep, '-'))) | |
59 | |
60 def wopen(name): | |
61 path = os.path.dirname(name) | |
62 if path: | |
63 try: | |
64 os.makedirs(path) | |
65 except OSError, err: | |
66 if err.errno != errno.EEXIST: | |
67 raise | |
68 return open(name, 'w') | |
69 | |
56 class example: | 70 class example: |
71 entities = dict.fromkeys(l.rstrip() for l in open('auto-snippets.xml')) | |
72 | |
73 def __init__(self, name, verbose, keep_change): | |
74 self.name = os.path.normpath(name) | |
75 self.verbose = verbose | |
76 self.keep_change = keep_change | |
77 | |
78 def status(self, s): | |
79 sys.stdout.write(s) | |
80 if not s.endswith('\n'): | |
81 sys.stdout.flush() | |
82 | |
83 def rename_output(self, base, ignore=[]): | |
84 mangle_re = re.compile('(?:' + '|'.join(ignore) + ')') | |
85 def mangle(s): | |
86 return mangle_re.sub('', s) | |
87 def matchfp(fp1, fp2): | |
88 while True: | |
89 s1 = mangle(fp1.readline()) | |
90 s2 = mangle(fp2.readline()) | |
91 if cmp(s1, s2): | |
92 break | |
93 if not s1: | |
94 return True | |
95 return False | |
96 | |
97 oldname = result_name(base + '.out') | |
98 tmpname = result_name(base + '.tmp') | |
99 errname = result_name(base + '.err') | |
100 errfp = open(errname, 'w+') | |
101 for line in open(tmpname): | |
102 errfp.write(mangle_re.sub('', line)) | |
103 os.rename(tmpname, result_name(base + '.lxo')) | |
104 errfp.seek(0) | |
105 try: | |
106 oldfp = open(oldname) | |
107 except IOError, err: | |
108 if err.errno != errno.ENOENT: | |
109 raise | |
110 os.rename(errname, oldname) | |
111 return False | |
112 if matchfp(oldfp, errfp): | |
113 os.unlink(errname) | |
114 return False | |
115 else: | |
116 print >> sys.stderr, '\nOutput of %s has changed!' % base | |
117 if self.keep_change: | |
118 os.rename(errname, oldname) | |
119 return False | |
120 else: | |
121 os.system('diff -u %s %s 1>&2' % (oldname, errname)) | |
122 return True | |
123 | |
124 class static_example(example): | |
125 def run(self): | |
126 self.status('running %s\n' % self.name) | |
127 s = open(self.name).read().rstrip() | |
128 s = s.replace('&', '&').replace('<', '<').replace('>', '>') | |
129 ofp = wopen(result_name(self.name + '.tmp')) | |
130 ofp.write('<!-- BEGIN %s -->\n' % self.name) | |
131 ofp.write('<programlisting>') | |
132 ofp.write(s) | |
133 ofp.write('</programlisting>\n') | |
134 ofp.write('<!-- END %s -->\n' % self.name) | |
135 ofp.close() | |
136 self.rename_output(self.name) | |
137 norm = self.name.replace(os.sep, '-') | |
138 example.entities[ | |
139 '<!ENTITY %s SYSTEM "results/%s.lxo">' % (norm, norm)] = 1 | |
140 | |
141 | |
142 class shell_example(example): | |
57 shell = '/usr/bin/env bash' | 143 shell = '/usr/bin/env bash' |
58 ps1 = '__run_example_ps1__ ' | 144 ps1 = '__run_example_ps1__ ' |
59 ps2 = '__run_example_ps2__ ' | 145 ps2 = '__run_example_ps2__ ' |
60 pi_re = re.compile(r'#\$\s*(name|ignore):\s*(.*)$') | 146 pi_re = re.compile(r'#\$\s*(name|ignore):\s*(.*)$') |
61 | 147 |
62 timeout = 10 | 148 timeout = 10 |
63 | 149 |
64 def __init__(self, name, verbose): | 150 def __init__(self, name, verbose, keep_change): |
65 self.name = name | 151 example.__init__(self, name, verbose, keep_change) |
66 self.verbose = verbose | |
67 self.poll = select.poll() | 152 self.poll = select.poll() |
68 | 153 |
69 def parse(self): | 154 def parse(self): |
70 '''yield each hunk of input from the file.''' | 155 '''yield each hunk of input from the file.''' |
71 fp = open(self.name) | 156 fp = open(self.name) |
74 cfp.write(line) | 159 cfp.write(line) |
75 if not line.rstrip().endswith('\\'): | 160 if not line.rstrip().endswith('\\'): |
76 yield cfp.getvalue() | 161 yield cfp.getvalue() |
77 cfp.seek(0) | 162 cfp.seek(0) |
78 cfp.truncate() | 163 cfp.truncate() |
79 | |
80 def status(self, s): | |
81 sys.stdout.write(s) | |
82 if not s.endswith('\n'): | |
83 sys.stdout.flush() | |
84 | 164 |
85 def send(self, s): | 165 def send(self, s): |
86 if self.verbose: | 166 if self.verbose: |
87 print >> sys.stderr, '>', self.debugrepr(s) | 167 print >> sys.stderr, '>', self.debugrepr(s) |
88 while s: | 168 while s: |
144 # remove the marker file that we tell make to use to see if | 224 # remove the marker file that we tell make to use to see if |
145 # this run succeeded | 225 # this run succeeded |
146 maybe_unlink(self.name + '.run') | 226 maybe_unlink(self.name + '.run') |
147 | 227 |
148 rcfile = os.path.join(tmpdir, '.hgrc') | 228 rcfile = os.path.join(tmpdir, '.hgrc') |
149 rcfp = open(rcfile, 'w') | 229 rcfp = wopen(rcfile) |
150 print >> rcfp, '[ui]' | 230 print >> rcfp, '[ui]' |
151 print >> rcfp, "username = Bryan O'Sullivan <bos@serpentine.com>" | 231 print >> rcfp, "username = Bryan O'Sullivan <bos@serpentine.com>" |
152 | 232 |
153 rcfile = os.path.join(tmpdir, '.bashrc') | 233 rcfile = os.path.join(tmpdir, '.bashrc') |
154 rcfp = open(rcfile, 'w') | 234 rcfp = wopen(rcfile) |
155 print >> rcfp, 'PS1="%s"' % self.ps1 | 235 print >> rcfp, 'PS1="%s"' % self.ps1 |
156 print >> rcfp, 'PS2="%s"' % self.ps2 | 236 print >> rcfp, 'PS2="%s"' % self.ps2 |
157 print >> rcfp, 'unset HISTFILE' | 237 print >> rcfp, 'unset HISTFILE' |
158 path = ['/usr/bin', '/bin'] | 238 path = ['/usr/bin', '/bin'] |
159 hg = find_path_to('hg') | 239 hg = find_path_to('hg') |
228 'name %r' % | 308 'name %r' % |
229 (self.name, out)) | 309 (self.name, out)) |
230 return 1 | 310 return 1 |
231 assert os.sep not in out | 311 assert os.sep not in out |
232 if ofp is not None: | 312 if ofp is not None: |
313 ofp.write('</screen>\n') | |
314 ofp.write('<!-- END %s -->\n' % ofp_basename) | |
233 ofp.close() | 315 ofp.close() |
234 err |= self.rename_output(ofp_basename, ignore) | 316 err |= self.rename_output(ofp_basename, ignore) |
235 if out: | 317 if out: |
236 ofp_basename = '%s.%s' % (self.name, out) | 318 ofp_basename = '%s.%s' % (self.name, out) |
319 norm = os.path.normpath(ofp_basename) | |
320 norm = norm.replace(os.sep, '-') | |
321 example.entities[ | |
322 '<!ENTITY interaction.%s ' | |
323 'SYSTEM "results/%s.lxo">' | |
324 % (norm, norm)] = 1 | |
237 read_hint = ofp_basename + ' ' | 325 read_hint = ofp_basename + ' ' |
238 ofp = open(ofp_basename + '.tmp', 'w') | 326 ofp = wopen(result_name(ofp_basename + '.tmp')) |
327 ofp.write('<!-- BEGIN %s -->\n' % ofp_basename) | |
328 ofp.write('<screen>') | |
239 else: | 329 else: |
240 ofp = None | 330 ofp = None |
241 elif pi == 'ignore': | 331 elif pi == 'ignore': |
242 ignore.append(rest) | 332 ignore.append(rest) |
243 elif hunk.strip(): | 333 elif hunk.strip(): |
246 if not ofp: | 336 if not ofp: |
247 continue | 337 continue |
248 # first, print the command we ran | 338 # first, print the command we ran |
249 if not hunk.startswith('#'): | 339 if not hunk.startswith('#'): |
250 nl = hunk.endswith('\n') | 340 nl = hunk.endswith('\n') |
251 hunk = ('%s \\textbf{%s}' % | 341 hunk = ('<prompt>%s</prompt> ' |
342 '<userinput>%s</userinput>' % | |
252 (prompts[ps], | 343 (prompts[ps], |
253 tex_escape(hunk.rstrip('\n')))) | 344 xml_escape(hunk.rstrip('\n')))) |
254 if nl: hunk += '\n' | 345 if nl: hunk += '\n' |
255 ofp.write(hunk) | 346 ofp.write(hunk) |
256 # then its output | 347 # then its output |
257 ofp.write(tex_escape(output)) | 348 ofp.write(xml_escape(output)) |
258 ps = newps | 349 ps = newps |
259 self.status('\n') | 350 self.status('\n') |
260 except: | 351 except: |
261 print >> sys.stderr, '(killed)' | 352 print >> sys.stderr, '(killed)' |
262 os.kill(self.pid, signal.SIGKILL) | 353 os.kill(self.pid, signal.SIGKILL) |
263 pid, rc = os.wait() | 354 pid, rc = os.wait() |
265 else: | 356 else: |
266 try: | 357 try: |
267 ps, output = self.sendreceive('exit\n', read_hint) | 358 ps, output = self.sendreceive('exit\n', read_hint) |
268 if ofp is not None: | 359 if ofp is not None: |
269 ofp.write(output) | 360 ofp.write(output) |
361 ofp.write('</screen>\n') | |
362 ofp.write('<!-- END %s -->\n' % ofp_basename) | |
270 ofp.close() | 363 ofp.close() |
271 err |= self.rename_output(ofp_basename, ignore) | 364 err |= self.rename_output(ofp_basename, ignore) |
272 os.close(self.cfd) | 365 os.close(self.cfd) |
273 except IOError: | 366 except IOError: |
274 pass | 367 pass |
279 if os.WIFEXITED(rc): | 372 if os.WIFEXITED(rc): |
280 print >> sys.stderr, '(exit %s)' % os.WEXITSTATUS(rc) | 373 print >> sys.stderr, '(exit %s)' % os.WEXITSTATUS(rc) |
281 elif os.WIFSIGNALED(rc): | 374 elif os.WIFSIGNALED(rc): |
282 print >> sys.stderr, '(signal %s)' % os.WTERMSIG(rc) | 375 print >> sys.stderr, '(signal %s)' % os.WTERMSIG(rc) |
283 else: | 376 else: |
284 open(self.name + '.run', 'w') | 377 wopen(result_name(self.name + '.run')) |
285 # return err | 378 return err |
286 return 0 | |
287 finally: | 379 finally: |
288 shutil.rmtree(tmpdir) | 380 shutil.rmtree(tmpdir) |
289 | |
290 def rename_output(self, base, ignore): | |
291 mangle_re = re.compile('(?:' + '|'.join(ignore) + ')') | |
292 def mangle(s): | |
293 return mangle_re.sub('', s) | |
294 def matchfp(fp1, fp2): | |
295 while True: | |
296 s1 = mangle(fp1.readline()) | |
297 s2 = mangle(fp2.readline()) | |
298 if cmp(s1, s2): | |
299 break | |
300 if not s1: | |
301 return True | |
302 return False | |
303 | |
304 oldname = base + '.out' | |
305 tmpname = base + '.tmp' | |
306 errname = base + '.err' | |
307 errfp = open(errname, 'w+') | |
308 for line in open(tmpname): | |
309 errfp.write(mangle_re.sub('', line)) | |
310 os.rename(tmpname, base + '.lxo') | |
311 errfp.seek(0) | |
312 try: | |
313 oldfp = open(oldname) | |
314 except IOError, err: | |
315 if err.errno != errno.ENOENT: | |
316 raise | |
317 os.rename(errname, oldname) | |
318 return False | |
319 if matchfp(oldfp, errfp): | |
320 os.unlink(errname) | |
321 return False | |
322 else: | |
323 print >> sys.stderr, '\nOutput of %s has changed!' % base | |
324 os.system('diff -u %s %s 1>&2' % (oldname, errname)) | |
325 return True | |
326 | 381 |
327 def print_help(exit, msg=None): | 382 def print_help(exit, msg=None): |
328 if msg: | 383 if msg: |
329 print >> sys.stderr, 'Error:', msg | 384 print >> sys.stderr, 'Error:', msg |
330 print >> sys.stderr, 'Usage: run-example [options] [test...]' | 385 print >> sys.stderr, 'Usage: run-example [options] [test...]' |
331 print >> sys.stderr, 'Options:' | 386 print >> sys.stderr, 'Options:' |
332 print >> sys.stderr, ' -a --all run all tests in this directory' | 387 print >> sys.stderr, ' -a --all run all examples in this directory' |
333 print >> sys.stderr, ' -h --help print this help message' | 388 print >> sys.stderr, ' -h --help print this help message' |
389 print >> sys.stderr, ' --keep keep new output as desired output' | |
334 print >> sys.stderr, ' -v --verbose display extra debug output' | 390 print >> sys.stderr, ' -v --verbose display extra debug output' |
335 sys.exit(exit) | 391 sys.exit(exit) |
336 | 392 |
337 def main(path='.'): | 393 def main(path='.'): |
394 if os.path.realpath(path).split(os.sep)[-1] != 'examples': | |
395 print >> sys.stderr, 'Not being run from the examples directory!' | |
396 sys.exit(1) | |
397 | |
338 opts, args = getopt.getopt(sys.argv[1:], '?ahv', | 398 opts, args = getopt.getopt(sys.argv[1:], '?ahv', |
339 ['all', 'help', 'verbose']) | 399 ['all', 'help', 'keep', 'verbose']) |
340 verbose = False | 400 verbose = False |
341 run_all = False | 401 run_all = False |
402 keep_change = False | |
403 | |
342 for o, a in opts: | 404 for o, a in opts: |
343 if o in ('-h', '-?', '--help'): | 405 if o in ('-h', '-?', '--help'): |
344 print_help(0) | 406 print_help(0) |
345 if o in ('-a', '--all'): | 407 if o in ('-a', '--all'): |
346 run_all = True | 408 run_all = True |
409 if o in ('--keep',): | |
410 keep_change = True | |
347 if o in ('-v', '--verbose'): | 411 if o in ('-v', '--verbose'): |
348 verbose = True | 412 verbose = True |
349 errs = 0 | 413 errs = 0 |
350 if args: | 414 if args: |
351 for a in args: | 415 for a in args: |
353 st = os.lstat(a) | 417 st = os.lstat(a) |
354 except OSError, err: | 418 except OSError, err: |
355 print >> sys.stderr, '%s: %s' % (a, err.strerror) | 419 print >> sys.stderr, '%s: %s' % (a, err.strerror) |
356 errs += 1 | 420 errs += 1 |
357 continue | 421 continue |
358 if stat.S_ISREG(st.st_mode) and st.st_mode & 0111: | 422 if stat.S_ISREG(st.st_mode): |
359 if example(a, verbose).run(): | 423 if st.st_mode & 0111: |
360 errs += 1 | 424 if shell_example(a, verbose, keep_change).run(): |
425 errs += 1 | |
426 elif a.endswith('.lst'): | |
427 static_example(a, verbose, keep_change).run() | |
361 else: | 428 else: |
362 print >> sys.stderr, '%s: not a file, or not executable' % a | 429 print >> sys.stderr, '%s: not a file, or not executable' % a |
363 errs += 1 | 430 errs += 1 |
364 elif run_all: | 431 elif run_all: |
365 names = os.listdir(path) | 432 names = glob.glob("*") + glob.glob("app*/*") + glob.glob("ch*/*") |
366 names.sort() | 433 names.sort() |
367 for name in names: | 434 for name in names: |
368 if name == 'run-example' or name.startswith('.'): continue | 435 if name == 'run-example' or name.endswith('~'): continue |
369 if name.endswith('.out') or name.endswith('~'): continue | |
370 if name.endswith('.run'): continue | |
371 pathname = os.path.join(path, name) | 436 pathname = os.path.join(path, name) |
372 try: | 437 try: |
373 st = os.lstat(pathname) | 438 st = os.lstat(pathname) |
374 except OSError, err: | 439 except OSError, err: |
375 # could be an output file that was removed while we ran | 440 # could be an output file that was removed while we ran |
376 if err.errno != errno.ENOENT: | 441 if err.errno != errno.ENOENT: |
377 raise | 442 raise |
378 continue | 443 continue |
379 if stat.S_ISREG(st.st_mode) and st.st_mode & 0111: | 444 if stat.S_ISREG(st.st_mode): |
380 if example(pathname, verbose).run(): | 445 if st.st_mode & 0111: |
381 errs += 1 | 446 if shell_example(pathname, verbose, keep_change).run(): |
382 print >> open(os.path.join(path, '.run'), 'w'), time.asctime() | 447 errs += 1 |
448 elif pathname.endswith('.lst'): | |
449 static_example(pathname, verbose, keep_change).run() | |
450 print >> wopen(os.path.join(path, '.run')), time.asctime() | |
383 else: | 451 else: |
384 print_help(1, msg='no test names given, and --all not provided') | 452 print_help(1, msg='no test names given, and --all not provided') |
453 | |
454 fp = wopen('auto-snippets.xml') | |
455 for key in sorted(example.entities.iterkeys()): | |
456 print >> fp, key | |
457 fp.close() | |
385 return errs | 458 return errs |
386 | 459 |
387 if __name__ == '__main__': | 460 if __name__ == '__main__': |
388 try: | 461 try: |
389 sys.exit(main()) | 462 sys.exit(main()) |