1 # vim: set et sw=4 sts=4 encoding=utf-8 foldmethod=marker:
3 from sercom.model import Entrega, CasoDePrueba, Tarea, TareaFuente, TareaPrueba
4 from sercom.model import ComandoFuente, ComandoPrueba
5 from difflib import unified_diff, HtmlDiff
6 from zipfile import ZipFile, BadZipfile
7 from cStringIO import StringIO
8 from shutil import rmtree
9 from datetime import datetime
10 from os.path import join
11 from turbogears import config
12 import subprocess as sp
13 import resource as rsrc
14 import os, sys, pwd, grp
17 log = logging.getLogger('sercom.tester')
19 error_interno = _(u'\n**Error interno al preparar la entrega.**')
21 class UserInfo(object): #{{{
22 def __init__(self, user):
24 info = pwd.getpwnam(user)
26 info = pwd.get(int(user))
33 self.group = grp.getgrgid(self.gid)[0]
36 user_info = UserInfo(config.get('sercom.tester.user', 65534))
38 def check_call(*popenargs, **kwargs): #{{{ XXX Python 2.5 forward-compatibility
39 """Run command with arguments. Wait for command to complete. If
40 the exit code was zero then return, otherwise raise
41 CalledProcessError. The CalledProcessError object will have the
42 return code in the returncode attribute.
43 ret = call(*popenargs, **kwargs)
45 The arguments are the same as for the Popen constructor. Example:
47 check_call(["ls", "-l"])
49 retcode = sp.call(*popenargs, **kwargs)
50 cmd = kwargs.get("args")
54 raise sp.CalledProcessError(retcode, cmd)
56 sp.check_call = check_call
61 class CalledProcessError(Exception): #{{{ XXX Python 2.5 forward-compatibility
62 """This exception is raised when a process run by check_call() returns
63 a non-zero exit status. The exit status will be stored in the
64 returncode attribute."""
65 def __init__(self, returncode, cmd):
66 self.returncode = returncode
69 return ("Command '%s' returned non-zero exit status %d"
70 % (self.cmd, self.returncode))
71 sp.CalledProcessError = CalledProcessError
74 class Error(StandardError): pass
76 class ExecutionFailure(Error, RuntimeError): #{{{
77 def __init__(self, comando, tarea=None, caso_de_prueba=None):
78 self.comando = comando
80 self.caso_de_prueba = caso_de_prueba
85 def unzip(bytes, default_dst='.', specific_dst=dict()): # {{{
86 u"""Descomprime un buffer de datos en formato ZIP.
87 Los archivos se descomprimen en default_dst a menos que exista una entrada
88 en specific_dst cuya clave sea el nombre de archivo a descomprimir, en
89 cuyo caso, se descomprime usando como destino el valor de dicha clave.
91 log.debug(_(u'Intentando descomprimir'))
94 zfile = ZipFile(StringIO(bytes), 'r')
95 for f in zfile.namelist():
96 dst = join(specific_dst.get(f, default_dst), f)
97 if f.endswith(os.sep):
98 log.debug(_(u'Creando directorio "%s" en "%s"'), f, dst)
101 log.debug(_(u'Descomprimiendo archivo "%s" en "%s"'), f, dst)
102 file(dst, 'w').write(zfile.read(f))
106 class Multizip(object): #{{{
107 def __init__(self, *zip_streams):
108 self.zips = [ZipFile(StringIO(z), 'r') for z in zip_streams
112 self.names |= set(z.namelist())
113 def read(self, name):
124 class SecureProcess(object): #{{{
126 max_tiempo_cpu = 120,
129 max_cant_archivos = 5,
130 max_cant_procesos = 0,
131 max_locks_memoria = 0,
133 uid = config.get('sercom.tester.chroot.user', 65534)
135 # XXX probar! make de un solo archivo lleva nproc=100 y nofile=15
136 def __init__(self, comando, chroot, cwd, close_stdin=False,
137 close_stdout=False, close_stderr=False):
138 self.comando = comando
141 self.close_stdin = close_stdin
142 self.close_stdout = close_stdout
143 self.close_stderr = close_stderr
144 log.debug(_(u'Proceso segurizado: chroot=%s, cwd=%s, user=%s, cpu=%s, '
145 u'as=%sMiB, fsize=%sMiB, nofile=%s, nproc=%s, memlock=%s'),
146 self.chroot, self.cwd, self.uid, self.max_tiempo_cpu,
147 self.max_memoria, self.max_tam_archivo, self.max_cant_archivos,
148 self.max_cant_procesos, self.max_locks_memoria)
149 def __getattr__(self, name):
150 if getattr(self.comando, name) is not None:
151 return getattr(self.comando, name)
152 return config.get('sercom.tester.limits.' + name, self.default[name])
154 x2 = lambda x: (x, x)
157 if self.close_stdout:
159 if self.close_stderr:
161 os.chroot(self.chroot)
163 uinfo = UserInfo(self.uid)
165 os.setuid(uinfo.uid) # Somos mortales irreversiblemente
166 rsrc.setrlimit(rsrc.RLIMIT_CPU, x2(self.max_tiempo_cpu))
167 rsrc.setrlimit(rsrc.RLIMIT_AS, x2(self.max_memoria*self.MB))
168 rsrc.setrlimit(rsrc.RLIMIT_FSIZE, x2(self.max_tam_archivo*self.MB)) # XXX calcular en base a archivos esperados?
169 rsrc.setrlimit(rsrc.RLIMIT_NOFILE, x2(self.max_cant_archivos)) #XXX Obtener de archivos esperados?
170 rsrc.setrlimit(rsrc.RLIMIT_NPROC, x2(self.max_cant_procesos))
171 rsrc.setrlimit(rsrc.RLIMIT_MEMLOCK, x2(self.max_locks_memoria))
172 rsrc.setrlimit(rsrc.RLIMIT_CORE, x2(0))
173 # Tratamos de forzar un sync para que entre al sleep del padre FIXME
178 class Tester(object): #{{{
180 def __init__(self, name, path, home, queue): #{{{ y properties
185 # Ahora somos mortales (oid mortales)
186 os.setegid(user_info.gid)
187 os.seteuid(user_info.uid)
188 log.debug(_(u'usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
189 user_info.user, user_info.group, user_info.uid, user_info.gid)
192 def build_path(self):
193 return join(self.chroot, self.home, 'build')
197 return join(self.chroot, self.home, 'test')
201 return join(self.path, 'chroot_' + self.name)
204 def orig_chroot(self):
205 return join(self.path, 'chroot')
209 entrega_id = self.queue.get() # blocking
210 while entrega_id is not None:
211 entrega = Entrega.get(entrega_id)
212 log.debug(_(u'Nueva entrega para probar en tester %s: %s'),
215 log.debug(_(u'Fin de pruebas de: %s'), entrega)
216 entrega_id = self.queue.get() # blocking
219 def test(self, entrega): #{{{
220 log.debug(_(u'Tester.test(entrega=%s)'), entrega)
221 entrega.inicio = datetime.now()
224 self.setup_chroot(entrega)
225 self.ejecutar_tareas_fuente(entrega)
226 self.ejecutar_tareas_prueba(entrega)
227 self.clean_chroot(entrega)
228 except ExecutionFailure, e:
231 if isinstance(e, SystemExit): raise
232 entrega.observaciones += error_interno
233 log.exception(_('Hubo una excepcion inesperada')) # FIXME encoding
235 entrega.observaciones += error_interno
236 log.exception(_('Hubo una excepcion inesperada desconocida')) # FIXME encoding
238 entrega.fin = datetime.now()
239 if entrega.exito is None:
242 log.info(_(u'Entrega correcta: %s'), entrega)
244 log.info(_(u'Entrega incorrecta: %s'), entrega)
247 def setup_chroot(self, entrega): #{{{ y clean_chroot()
248 log.debug(_(u'Tester.setup_chroot(entrega=%s)'), entrega.shortrepr())
249 rsync = ('rsync', '--stats', '--itemize-changes', '--human-readable',
250 '--archive', '--acls', '--delete-during', '--force',
251 '--exclude', '/proc', # TODO config
252 join(self.orig_chroot, ''), self.chroot)
253 log.debug(_(u'Ejecutando como root: %s'), ' '.join(rsync))
254 os.seteuid(0) # Dios! (para chroot)
259 os.setegid(user_info.gid) # Mortal de nuevo
260 os.seteuid(user_info.uid)
261 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
262 user_info.user, user_info.group, user_info.uid, user_info.gid)
263 unzip(entrega.archivos, self.build_path)
265 def clean_chroot(self, entrega):
266 log.debug(_(u'Tester.clean_chroot(entrega=%s)'), entrega.shortrepr())
267 pass # Se limpia con el próximo rsync
270 def ejecutar_tareas_fuente(self, entrega): #{{{ y tareas_prueba
271 log.debug(_(u'Tester.ejecutar_tareas_fuente(entrega=%s)'),
273 tareas = [t for t in entrega.instancia.ejercicio.enunciado.tareas
274 if isinstance(t, TareaFuente)]
276 tarea.ejecutar(self.build_path, entrega)
278 def ejecutar_tareas_prueba(self, entrega):
279 log.debug(_(u'Tester.ejecutar_tareas_prueba(entrega=%s)'),
281 for caso in entrega.instancia.ejercicio.enunciado.casos_de_prueba:
282 caso.ejecutar(self.test_path, entrega)
287 def ejecutar_caso_de_prueba(self, path, entrega): #{{{
288 log.debug(_(u'CasoDePrueba.ejecutar(caso=%s, path=%s, entrega=%s)'), self,
289 path, entrega.shortrepr())
290 tareas = [t for t in entrega.instancia.ejercicio.enunciado.tareas
291 if isinstance(t, TareaPrueba)]
292 prueba = entrega.add_prueba(self, inicio=datetime.now())
296 tarea.ejecutar(path, prueba)
297 except ExecutionFailure, e:
300 prueba.fin = datetime.now()
301 if prueba.exito is None:
303 if not prueba.exito and self.rechazar_si_falla:
304 entrega.exito = False
305 if not prueba.exito and self.terminar_si_falla:
306 raise ExecutionFailure(prueba)
307 CasoDePrueba.ejecutar = ejecutar_caso_de_prueba
310 def ejecutar_tarea(self, path, ejecucion): #{{{
311 log.debug(_(u'Tarea.ejecutar(path=%s, ejecucion=%s)'), path,
312 ejecucion.shortrepr())
313 for cmd in self.comandos:
314 cmd.ejecutar(path, ejecucion)
315 Tarea.ejecutar = ejecutar_tarea
318 # TODO generalizar ejecutar_comando_xxxx!!!
320 def ejecutar_comando_fuente(self, path, entrega): #{{{
321 log.debug(_(u'ComandoFuente.ejecutar(path=%s, entrega=%s)'), path,
323 comando_ejecutado = entrega.add_comando_ejecutado(self)
324 basetmp = '/tmp/sercom.tester.fuente' # FIXME TODO /var/run/sercom?
325 unzip(self.archivos_entrada, path, # TODO try/except
326 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)})
330 preexec_fn=SecureProcess(self, 'var/chroot_pepe', '/home/sercom/build') #FIXME!!! path
332 if os.path.exists('%s.%s.stdin' % (basetmp, comando_ejecutado.id)):
333 options['stdin'] = file('%s.%s.stdin' % (basetmp, comando_ejecutado.id),
336 options['preexec_fn'].close_stdin = True
337 a_guardar = set(self.archivos_a_guardar)
338 zip_a_comparar = Multizip(self.archivos_a_comparar)
339 a_comparar = set(zip_a_comparar.namelist())
340 a_usar = frozenset(a_guardar | a_comparar)
341 if self.STDOUTERR in a_usar:
342 options['stdout'] = file('%s.%s.stdouterr' % (basetmp,
343 comando_ejecutado.id), 'w')
344 options['stderr'] = sp.STDOUT
346 if self.STDOUT in a_usar:
347 options['stdout'] = file('%s.%s.stdout' % (basetmp,
348 comando_ejecutado.id), 'w')
350 options['preexec_fn'].close_stdout = True
351 if self.STDERR in a_usar:
352 options['stderr'] = file('%s.%s.stderr' % (basetmp,
353 comando_ejecutado.id), 'w')
355 options['preexec_fn'].close_stderr = True
356 comando = self.comando # FIXME Acá tiene que diferenciarse de ComandoPrueba
357 comando_ejecutado.inicio = datetime.now()
358 log.debug(_(u'Ejecutando como root: %s'), comando)
359 os.seteuid(0) # Dios! (para chroot)
363 proc = sp.Popen(comando, **options)
365 os.setegid(user_info.gid) # Mortal de nuevo
366 os.seteuid(user_info.uid)
367 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
368 user_info.user, user_info.group, user_info.uid, user_info.gid)
370 if hasattr(e, 'child_traceback'):
371 log.error(_(u'Error en el hijo: %s'), e.child_traceback)
373 proc.wait() #TODO un sleep grande nos caga todo, ver sercom viejo
374 comando_ejecutado.fin = datetime.now()
375 retorno = self.retorno
376 if retorno != self.RET_ANY:
377 if retorno == self.RET_FAIL:
378 if proc.returncode == 0:
379 if self.rechazar_si_falla:
380 entrega.exito = False
381 comando_ejecutado.exito = False
382 comando_ejecutado.observaciones += _(u'Se esperaba que el '
383 u'programa termine con un error (código de retorno '
384 u'distinto de 0) pero terminó bien (código de retorno '
386 log.debug(_(u'Se esperaba que el programa termine '
387 u'con un error (código de retorno distinto de 0) pero '
388 u'terminó bien (código de retorno 0).\n'))
389 elif retorno != proc.returncode:
390 if self.rechazar_si_falla:
391 entrega.exito = False
392 comando_ejecutado.exito = False
393 if proc.returncode < 0:
394 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
395 u'con un código de retorno %s pero se obtuvo una señal %s '
396 u'(%s).\n') % (retorno, -proc.returncode, -proc.returncode) # TODO poner con texto
397 log.debug(_(u'Se esperaba terminar con un código '
398 u'de retorno %s pero se obtuvo una señal %s (%s).\n'),
399 retorno, -proc.returncode, -proc.returncode)
401 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
402 u'con un código de retorno %s pero se obtuvo %s.\n') \
403 % (retorno, proc.returncode)
404 log.debug(_(u'Se esperaba terminar con un código de retorno '
405 u'%s pero se obtuvo %s.\n'), retorno, proc.returncode)
406 if comando_ejecutado.exito is None:
407 log.debug(_(u'Código de retorno OK'))
410 zip = ZipFile(buffer, 'w')
411 # Guardamos stdout/stderr
412 if self.STDOUTERR in a_guardar:
413 a_guardar.remove(self.STDOUTERR)
414 zip.write('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
417 if self.STDOUT in a_guardar:
418 a_guardar.remove(self.STDOUT)
419 zip.write('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
421 if self.STDERR in a_guardar:
422 a_guardar.remove(self.STDERR)
423 zip.write('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
427 if not os.path.exists(join(path, f)):
428 if self.rechazar_si_falla:
429 entrega.exito = False
430 comando_ejecutado.exito = False
431 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
432 u'"%s" para guardar pero no fue encontrado.\n') % f
433 log.debug(_(u'Se esperaba un archivo "%s" para guardar pero '
434 u'no fue encontrado'), f)
436 zip.write(join(path, f), f)
438 comando_ejecutado.archivos = buffer.getvalue()
439 def diff(new, zip_in, zip_out, name, longname=None, origname='correcto',
440 newname='entregado'):
443 new = file(new, 'r').readlines()
444 orig = zip_in.read(name).splitlines(True)
445 udiff = ''.join(list(unified_diff(orig, new, fromfile=name+'.'+origname,
446 tofile=name+'.'+newname)))
448 if self.rechazar_si_falla:
449 entrega.exito = False
450 comando_ejecutado.exito = False
451 comando_ejecutado.observaciones += _(u'%s no coincide con lo '
452 u'esperado (archivo "%s.diff").\n') % (longname, name)
453 log.debug(_(u'%s no coincide con lo esperado (archivo "%s.diff")'),
455 htmldiff = HtmlDiff().make_file(orig, new,
456 fromdesc=name+'.'+origname, todesc=name+'.'+newname,
457 context=True, numlines=3)
458 zip_out.writestr(name + '.diff', udiff)
459 zip_out.writestr(name + '.diff', htmldiff)
465 zip = ZipFile(buffer, 'w')
466 # Comparamos stdout/stderr
467 if self.STDOUTERR in a_comparar:
468 a_comparar.remove(self.STDOUTERR)
469 diff('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
470 zip_a_comparar, zip, self.STDOUTERR,
471 _(u'La salida estándar y de error combinada'))
473 if self.STDOUT in a_comparar:
474 a_comparar.remove(self.STDOUT)
475 diff('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
476 zip_a_comparar, zip, self.STDOUT, _(u'La salida estándar'))
477 if self.STDERR in a_comparar:
478 a_comparar.remove(self.STDERR)
479 diff('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
480 zip_a_comparar, zip, self.STDERR, _(u'La salida de error'))
483 if not os.path.exists(join(path, f)):
484 if self.rechazar_si_falla:
485 entrega.exito = False
486 comando_ejecutado.exito = False
487 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
488 u'"%s" para comparar pero no fue encontrado') % f
489 log.debug(_(u'Se esperaba un archivo "%s" para comparar pero '
490 u'no fue encontrado'), f)
492 diff(join(path, f), zip_a_comparar, zip, f)
494 comando_ejecutado.diferencias = buffer.getvalue()
495 if comando_ejecutado.exito is None:
496 comando_ejecutado.exito = True
497 elif self.terminar_si_falla:
498 raise ExecutionFailure(self)
500 ComandoFuente.ejecutar = ejecutar_comando_fuente
503 def ejecutar_comando_prueba(self, path, prueba): #{{{
504 # Diferencia con comando fuente: s/entrega/prueba/ y s/build/test/ en path
505 # y setup/clean de test.
506 log.debug(_(u'ComandoPrueba.ejecutar(path=%s, prueba=%s)'), path,
508 caso_de_prueba = prueba.caso_de_prueba
509 comando_ejecutado = prueba.add_comando_ejecutado(self)
510 basetmp = '/tmp/sercom.tester.prueba' # FIXME TODO /var/run/sercom?
511 #{{{ Código que solo va en ComandoPrueba (setup de directorio)
512 rsync = ('rsync', '--stats', '--itemize-changes', '--human-readable',
513 '--archive', '--acls', '--delete-during', '--force', # TODO config
514 'var/chroot_pepe/home/sercom/build/', path) # FIXME!!!! path
515 log.debug(_(u'Ejecutando como root: %s'), ' '.join(rsync))
516 os.seteuid(0) # Dios! (para chroot)
521 os.setegid(user_info.gid) # Mortal de nuevo
522 os.seteuid(user_info.uid)
523 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
524 user_info.user, user_info.group, user_info.uid, user_info.gid)
526 unzip(self.archivos_entrada, path, # TODO try/except
527 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)})
528 unzip(caso_de_prueba.archivos_entrada, path, # TODO try/except # FIXME Esto es propio de ComandoPrueba
529 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)}) # FIXME Esto es propio de ComandoPrueba
533 preexec_fn=SecureProcess(self, 'var/chroot_pepe', '/home/sercom/test') # FIXME!!!! path
535 if os.path.exists('%s.%s.stdin' % (basetmp, comando_ejecutado.id)):
536 options['stdin'] = file('%s.%s.stdin' % (basetmp, comando_ejecutado.id),
539 options['preexec_fn'].close_stdin = True
540 a_guardar = set(self.archivos_a_guardar)
541 a_guardar |= set(caso_de_prueba.archivos_a_guardar) # FIXME Esto es propio de ComandoPrueba
542 log.debug('archivos a guardar: %s', a_guardar)
543 zip_a_comparar = Multizip(caso_de_prueba.archivos_a_comparar, # FIXME Esto es propio de ComandoPrueba
544 self.archivos_a_comparar) # FIXME Esto es propio de ComandoPrueba
545 a_comparar = set(zip_a_comparar.namelist())
546 log.debug('archivos a comparar: %s', a_comparar)
547 a_usar = frozenset(a_guardar | a_comparar)
548 log.debug('archivos a usar: %s', a_usar)
549 if self.STDOUTERR in a_usar:
550 options['stdout'] = file('%s.%s.stdouterr' % (basetmp,
551 comando_ejecutado.id), 'w')
552 options['stderr'] = sp.STDOUT
554 if self.STDOUT in a_usar:
555 log.debug('capurando salida en: %s.%s.stdout', basetmp, comando_ejecutado.id)
556 options['stdout'] = file('%s.%s.stdout' % (basetmp,
557 comando_ejecutado.id), 'w')
559 options['preexec_fn'].close_stdout = True
560 if self.STDERR in a_usar:
561 options['stderr'] = file('%s.%s.stderr' % (basetmp,
562 comando_ejecutado.id), 'w')
564 options['preexec_fn'].close_stderr = True
565 comando = self.comando + ' ' + caso_de_prueba.comando # FIXME Esto es propio de ComandoPrueba
566 comando_ejecutado.inicio = datetime.now()
567 log.debug(_(u'Ejecutando como root: %s'), comando)
568 os.seteuid(0) # Dios! (para chroot)
572 proc = sp.Popen(comando, **options)
574 os.setegid(user_info.gid) # Mortal de nuevo
575 os.seteuid(user_info.uid)
576 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
577 user_info.user, user_info.group, user_info.uid, user_info.gid)
579 if hasattr(e, 'child_traceback'):
580 log.error(_(u'Error en el hijo: %s'), e.child_traceback)
582 proc.wait() #TODO un sleep grande nos caga todo, ver sercom viejo
583 comando_ejecutado.fin = datetime.now()
584 retorno = self.retorno
585 if retorno == self.RET_PRUEBA: # FIXME Esto es propio de ComandoPrueba
586 retorno = caso_de_prueba.retorno # FIXME Esto es propio de ComandoPrueba
587 if retorno != self.RET_ANY:
588 if retorno == self.RET_FAIL:
589 if proc.returncode == 0:
590 if self.rechazar_si_falla:
592 comando_ejecutado.exito = False
593 comando_ejecutado.observaciones += _(u'Se esperaba que el '
594 u'programa termine con un error (código de retorno '
595 u'distinto de 0) pero terminó bien (código de retorno '
597 log.debug(_(u'Se esperaba que el programa termine '
598 u'con un error (código de retorno distinto de 0) pero '
599 u'terminó bien (código de retorno 0).\n'))
600 elif retorno != proc.returncode:
601 if self.rechazar_si_falla:
603 comando_ejecutado.exito = False
604 if proc.returncode < 0:
605 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
606 u'con un código de retorno %s pero se obtuvo una señal %s '
607 u'(%s).\n') % (retorno, -proc.returncode, -proc.returncode) # TODO poner con texto
608 log.debug(_(u'Se esperaba terminar con un código '
609 u'de retorno %s pero se obtuvo una señal %s (%s).\n'),
610 retorno, -proc.returncode, -proc.returncode)
612 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
613 u'con un código de retorno %s pero se obtuvo %s.\n') \
614 % (retorno, proc.returncode)
615 log.debug(_(u'Se esperaba terminar con un código de retorno '
616 u'%s pero se obtuvo %s.\n'), retorno, proc.returncode)
617 if comando_ejecutado.exito is None:
618 log.debug(_(u'Código de retorno OK'))
621 zip = ZipFile(buffer, 'w')
622 # Guardamos stdout/stderr
623 if self.STDOUTERR in a_guardar:
624 a_guardar.remove(self.STDOUTERR)
625 zip.write('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
628 if self.STDOUT in a_guardar:
629 a_guardar.remove(self.STDOUT)
630 zip.write('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
632 if self.STDERR in a_guardar:
633 a_guardar.remove(self.STDERR)
634 zip.write('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
638 if not os.path.exists(join(path, f)):
639 if self.rechazar_si_falla:
641 comando_ejecutado.exito = False
642 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
643 u'"%s" para guardar pero no fue encontrado.\n') % f
644 log.debug(_(u'Se esperaba un archivo "%s" para guardar pero '
645 u'no fue encontrado'), f)
647 zip.write(join(path, f), f)
649 comando_ejecutado.archivos = buffer.getvalue()
650 def diff(new, zip_in, zip_out, name, longname=None, origname='correcto',
651 newname='entregado'):
654 new = file(new, 'r').readlines()
655 orig = zip_in.read(name).splitlines(True)
656 udiff = ''.join(list(unified_diff(orig, new, fromfile=name+'.'+origname,
657 tofile=name+'.'+newname)))
659 if self.rechazar_si_falla:
661 comando_ejecutado.exito = False
662 comando_ejecutado.observaciones += _(u'%s no coincide con lo '
663 u'esperado (archivo "%s.diff").\n') % (longname, name)
664 log.debug(_(u'%s no coincide con lo esperado (archivo "%s.diff")'),
666 htmldiff = HtmlDiff().make_file(orig, new,
667 fromdesc=name+'.'+origname, todesc=name+'.'+newname,
668 context=True, numlines=3)
669 zip_out.writestr(name + '.diff', udiff)
670 zip_out.writestr(name + '.html', htmldiff)
676 zip = ZipFile(buffer, 'w')
677 # Comparamos stdout/stderr
678 if self.STDOUTERR in a_comparar:
679 a_comparar.remove(self.STDOUTERR)
680 diff('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
681 zip_a_comparar, zip, self.STDOUTERR,
682 _(u'La salida estándar y de error combinada'))
684 if self.STDOUT in a_comparar:
685 log.debug('comparando salida con: %s.%s.stdout', basetmp, comando_ejecutado.id)
686 a_comparar.remove(self.STDOUT)
687 diff('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
688 zip_a_comparar, zip, self.STDOUT, _(u'La salida estándar'))
689 if self.STDERR in a_comparar:
690 a_comparar.remove(self.STDERR)
691 diff('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
692 zip_a_comparar, zip, self.STDERR, _(u'La salida de error'))
695 if not os.path.exists(join(path, f)):
696 if self.rechazar_si_falla:
698 comando_ejecutado.exito = False
699 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
700 u'"%s" para comparar pero no fue encontrado') % f
701 log.debug(_(u'Se esperaba un archivo "%s" para comparar pero '
702 u'no fue encontrado'), f)
704 diff(join(path, f), zip_a_comparar, zip, f)
706 comando_ejecutado.diferencias = buffer.getvalue()
707 if comando_ejecutado.exito is None:
708 comando_ejecutado.exito = True
709 elif self.terminar_si_falla:
710 raise ExecutionFailure(self)
712 ComandoPrueba.ejecutar = ejecutar_comando_prueba