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(path=%s, entrega=%s)'), path,
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).split('\n')
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.html', 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 zip_a_comparar = Multizip(caso_de_prueba.archivos_a_comparar, # FIXME Esto es propio de ComandoPrueba
543 self.archivos_a_comparar) # FIXME Esto es propio de ComandoPrueba
544 a_comparar = set(zip_a_comparar.namelist())
545 a_usar = frozenset(a_guardar | a_comparar)
546 if self.STDOUTERR in a_usar:
547 options['stdout'] = file('%s.%s.stdouterr' % (basetmp,
548 comando_ejecutado.id), 'w')
549 options['stderr'] = sp.STDOUT
551 if self.STDOUT in a_usar:
552 options['stdout'] = file('%s.%s.stdout' % (basetmp,
553 comando_ejecutado.id), 'w')
555 options['preexec_fn'].close_stdout = True
556 if self.STDERR in a_usar:
557 options['stderr'] = file('%s.%s.stderr' % (basetmp,
558 comando_ejecutado.id), 'w')
560 options['preexec_fn'].close_stderr = True
561 comando = self.comando + ' ' + caso_de_prueba.comando # FIXME Esto es propio de ComandoPrueba
562 comando_ejecutado.inicio = datetime.now()
563 log.debug(_(u'Ejecutando como root: %s'), comando)
564 os.seteuid(0) # Dios! (para chroot)
568 proc = sp.Popen(comando, **options)
570 os.setegid(user_info.gid) # Mortal de nuevo
571 os.seteuid(user_info.uid)
572 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
573 user_info.user, user_info.group, user_info.uid, user_info.gid)
575 if hasattr(e, 'child_traceback'):
576 log.error(_(u'Error en el hijo: %s'), e.child_traceback)
578 proc.wait() #TODO un sleep grande nos caga todo, ver sercom viejo
579 comando_ejecutado.fin = datetime.now()
580 retorno = self.retorno
581 if retorno == self.RET_PRUEBA: # FIXME Esto es propio de ComandoPrueba
582 retorno = caso_de_prueba.retorno # FIXME Esto es propio de ComandoPrueba
583 if retorno != self.RET_ANY:
584 if retorno == self.RET_FAIL:
585 if proc.returncode == 0:
586 if self.rechazar_si_falla:
588 comando_ejecutado.exito = False
589 comando_ejecutado.observaciones += _(u'Se esperaba que el '
590 u'programa termine con un error (código de retorno '
591 u'distinto de 0) pero terminó bien (código de retorno '
593 log.debug(_(u'Se esperaba que el programa termine '
594 u'con un error (código de retorno distinto de 0) pero '
595 u'terminó bien (código de retorno 0).\n'))
596 elif retorno != proc.returncode:
597 if self.rechazar_si_falla:
599 comando_ejecutado.exito = False
600 if proc.returncode < 0:
601 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
602 u'con un código de retorno %s pero se obtuvo una señal %s '
603 u'(%s).\n') % (retorno, -proc.returncode, -proc.returncode) # TODO poner con texto
604 log.debug(_(u'Se esperaba terminar con un código '
605 u'de retorno %s pero se obtuvo una señal %s (%s).\n'),
606 retorno, -proc.returncode, -proc.returncode)
608 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
609 u'con un código de retorno %s pero se obtuvo %s.\n') \
610 % (retorno, proc.returncode)
611 log.debug(_(u'Se esperaba terminar con un código de retorno '
612 u'%s pero se obtuvo %s.\n'), retorno, proc.returncode)
613 if comando_ejecutado.exito is None:
614 log.debug(_(u'Código de retorno OK'))
617 zip = ZipFile(buffer, 'w')
618 # Guardamos stdout/stderr
619 if self.STDOUTERR in a_guardar:
620 a_guardar.remove(self.STDOUTERR)
621 zip.write('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
624 if self.STDOUT in a_guardar:
625 a_guardar.remove(self.STDOUT)
626 zip.write('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
628 if self.STDERR in a_guardar:
629 a_guardar.remove(self.STDERR)
630 zip.write('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
634 if not os.path.exists(join(path, f)):
635 if self.rechazar_si_falla:
637 comando_ejecutado.exito = False
638 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
639 u'"%s" para guardar pero no fue encontrado.\n') % f
640 log.debug(_(u'Se esperaba un archivo "%s" para guardar pero '
641 u'no fue encontrado'), f)
643 zip.write(join(path, f), f)
645 comando_ejecutado.archivos = buffer.getvalue()
646 def diff(new, zip_in, zip_out, name, longname=None, origname='correcto',
647 newname='entregado'):
650 new = file(new, 'r').readlines()
651 orig = zip_in.read(name).split('\n')
652 udiff = ''.join(list(unified_diff(orig, new, fromfile=name+'.'+origname,
653 tofile=name+'.'+newname)))
655 if self.rechazar_si_falla:
657 comando_ejecutado.exito = False
658 comando_ejecutado.observaciones += _(u'%s no coincide con lo '
659 u'esperado (archivo "%s.diff").\n') % (longname, name)
660 log.debug(_(u'%s no coincide con lo esperado (archivo "%s.diff")'),
662 htmldiff = HtmlDiff().make_file(orig, new,
663 fromdesc=name+'.'+origname, todesc=name+'.'+newname,
664 context=True, numlines=3)
665 zip_out.writestr(name + '.diff', udiff)
666 zip_out.writestr(name + '.diff.html', htmldiff)
672 zip = ZipFile(buffer, 'w')
673 # Comparamos stdout/stderr
674 if self.STDOUTERR in a_comparar:
675 a_comparar.remove(self.STDOUTERR)
676 diff('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
677 zip_a_comparar, zip, self.STDOUTERR,
678 _(u'La salida estándar y de error combinada'))
680 if self.STDOUT in a_comparar:
681 a_comparar.remove(self.STDOUT)
682 diff('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
683 zip_a_comparar, zip, self.STDOUT, _(u'La salida estándar'))
684 if self.STDERR in a_comparar:
685 a_comparar.remove(self.STDERR)
686 diff('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
687 zip_a_comparar, zip, self.STDERR, _(u'La salida de error'))
690 if not os.path.exists(join(path, f)):
691 if self.rechazar_si_falla:
693 comando_ejecutado.exito = False
694 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
695 u'"%s" para comparar pero no fue encontrado') % f
696 log.debug(_(u'Se esperaba un archivo "%s" para comparar pero '
697 u'no fue encontrado'), f)
699 diff(join(path, f), zip_a_comparar, zip, f)
701 comando_ejecutado.diferencias = buffer.getvalue()
702 if comando_ejecutado.exito is None:
703 comando_ejecutado.exito = True
704 elif self.terminar_si_falla:
705 raise ExecutionFailure(self)
707 ComandoPrueba.ejecutar = ejecutar_comando_prueba