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', # TODO config
251 join(self.orig_chroot, ''), self.chroot)
252 log.debug(_(u'Ejecutando como root: %s'), ' '.join(rsync))
253 os.seteuid(0) # Dios! (para chroot)
258 os.setegid(user_info.gid) # Mortal de nuevo
259 os.seteuid(user_info.uid)
260 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
261 user_info.user, user_info.group, user_info.uid, user_info.gid)
262 unzip(entrega.archivos, self.build_path)
264 def clean_chroot(self, entrega):
265 log.debug(_(u'Tester.clean_chroot(entrega=%s)'), entrega.shortrepr())
266 pass # Se limpia con el próximo rsync
269 def ejecutar_tareas_fuente(self, entrega): #{{{ y tareas_prueba
270 log.debug(_(u'Tester.ejecutar_tareas_fuente(entrega=%s)'),
272 tareas = [t for t in entrega.instancia.ejercicio.enunciado.tareas
273 if isinstance(t, TareaFuente)]
275 tarea.ejecutar(self.build_path, entrega)
277 def ejecutar_tareas_prueba(self, entrega):
278 log.debug(_(u'Tester.ejecutar_tareas_prueba(entrega=%s)'),
280 for caso in entrega.instancia.ejercicio.enunciado.casos_de_prueba:
281 caso.ejecutar(self.test_path, entrega)
286 def ejecutar_caso_de_prueba(self, path, entrega): #{{{
287 log.debug(_(u'CasoDePrueba.ejecutar(path=%s, entrega=%s)'), path,
289 tareas = [t for t in entrega.instancia.ejercicio.enunciado.tareas
290 if isinstance(t, TareaPrueba)]
291 prueba = entrega.add_prueba(self, inicio=datetime.now())
295 tarea.ejecutar(path, prueba)
296 except ExecutionFailure, e:
299 prueba.fin = datetime.now()
300 if prueba.exito is None:
302 if not prueba.exito and self.rechazar_si_falla:
303 entrega.exito = False
304 if not prueba.exito and self.terminar_si_falla:
305 raise ExecutionFailure(prueba)
306 CasoDePrueba.ejecutar = ejecutar_caso_de_prueba
309 def ejecutar_tarea(self, path, ejecucion): #{{{
310 log.debug(_(u'Tarea.ejecutar(path=%s, ejecucion=%s)'), path,
311 ejecucion.shortrepr())
312 for cmd in self.comandos:
313 cmd.ejecutar(path, ejecucion)
314 Tarea.ejecutar = ejecutar_tarea
317 # TODO generalizar ejecutar_comando_xxxx!!!
319 def ejecutar_comando_fuente(self, path, entrega): #{{{
320 log.debug(_(u'ComandoFuente.ejecutar(path=%s, entrega=%s)'), path,
322 comando_ejecutado = entrega.add_comando_ejecutado(self)
323 basetmp = '/tmp/sercom.tester.fuente' # FIXME TODO /var/run/sercom?
324 unzip(self.archivos_entrada, path, # TODO try/except
325 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)})
329 preexec_fn=SecureProcess(self, 'var/chroot_pepe', '/home/sercom/build') #FIXME!!! path
331 if os.path.exists('%s.%s.stdin' % (basetmp, comando_ejecutado.id)):
332 options['stdin'] = file('%s.%s.stdin' % (basetmp, comando_ejecutado.id),
335 options['preexec_fn'].close_stdin = True
336 a_guardar = set(self.archivos_a_guardar)
337 zip_a_comparar = Multizip(self.archivos_a_comparar)
338 a_comparar = set(zip_a_comparar.namelist())
339 a_usar = frozenset(a_guardar | a_comparar)
340 if self.STDOUTERR in a_usar:
341 options['stdout'] = file('%s.%s.stdouterr' % (basetmp,
342 comando_ejecutado.id), 'w')
343 options['stderr'] = sp.STDOUT
345 if self.STDOUT in a_usar:
346 options['stdout'] = file('%s.%s.stdout' % (basetmp,
347 comando_ejecutado.id), 'w')
349 options['preexec_fn'].close_stdout = True
350 if self.STDERR in a_usar:
351 options['stderr'] = file('%s.%s.stderr' % (basetmp,
352 comando_ejecutado.id), 'w')
354 options['preexec_fn'].close_stderr = True
355 comando = self.comando # FIXME Acá tiene que diferenciarse de ComandoPrueba
356 comando_ejecutado.inicio = datetime.now()
357 log.debug(_(u'Ejecutando como root: %s'), comando)
358 os.seteuid(0) # Dios! (para chroot)
362 proc = sp.Popen(comando, **options)
364 os.setegid(user_info.gid) # Mortal de nuevo
365 os.seteuid(user_info.uid)
366 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
367 user_info.user, user_info.group, user_info.uid, user_info.gid)
369 if hasattr(e, 'child_traceback'):
370 log.error(_(u'Error en el hijo: %s'), e.child_traceback)
372 proc.wait() #TODO un sleep grande nos caga todo, ver sercom viejo
373 comando_ejecutado.fin = datetime.now()
374 retorno = self.retorno
375 if retorno != self.RET_ANY:
376 if retorno == self.RET_FAIL:
377 if proc.returncode == 0:
378 if self.rechazar_si_falla:
379 entrega.exito = False
380 comando_ejecutado.exito = False
381 comando_ejecutado.observaciones += _(u'Se esperaba que el '
382 u'programa termine con un error (código de retorno '
383 u'distinto de 0) pero terminó bien (código de retorno '
385 log.debug(_(u'Se esperaba que el programa termine '
386 u'con un error (código de retorno distinto de 0) pero '
387 u'terminó bien (código de retorno 0).\n'))
388 elif retorno != proc.returncode:
389 if self.rechazar_si_falla:
390 entrega.exito = False
391 comando_ejecutado.exito = False
392 if proc.returncode < 0:
393 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
394 u'con un código de retorno %s pero se obtuvo una señal %s '
395 u'(%s).\n') % (retorno, -proc.returncode, -proc.returncode) # TODO poner con texto
396 log.debug(_(u'Se esperaba terminar con un código '
397 u'de retorno %s pero se obtuvo una señal %s (%s).\n'),
398 retorno, -proc.returncode, -proc.returncode)
400 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
401 u'con un código de retorno %s pero se obtuvo %s.\n') \
402 % (retorno, proc.returncode)
403 log.debug(_(u'Se esperaba terminar con un código de retorno '
404 u'%s pero se obtuvo %s.\n'), retorno, proc.returncode)
405 if comando_ejecutado.exito is None:
406 log.debug(_(u'Código de retorno OK'))
409 zip = ZipFile(buffer, 'w')
410 # Guardamos stdout/stderr
411 if self.STDOUTERR in a_guardar:
412 a_guardar.remove(self.STDOUTERR)
413 zip.write('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
416 if self.STDOUT in a_guardar:
417 a_guardar.remove(self.STDOUT)
418 zip.write('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
420 if self.STDERR in a_guardar:
421 a_guardar.remove(self.STDERR)
422 zip.write('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
426 if not os.path.exists(join(path, f)):
427 if self.rechazar_si_falla:
428 entrega.exito = False
429 comando_ejecutado.exito = False
430 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
431 u'"%s" para guardar pero no fue encontrado.\n') % f
432 log.debug(_(u'Se esperaba un archivo "%s" para guardar pero '
433 u'no fue encontrado'), f)
435 zip.write(join(path, f), f)
437 comando_ejecutado.archivos = buffer.getvalue()
438 def diff(new, zip_in, zip_out, name, longname=None, origname='correcto',
439 newname='entregado'):
442 new = file(new, 'r').readlines()
443 orig = zip_in.read(name).split('\n')
444 udiff = ''.join(list(unified_diff(orig, new, fromfile=name+'.'+origname,
445 tofile=name+'.'+newname)))
447 if self.rechazar_si_falla:
448 entrega.exito = False
449 comando_ejecutado.exito = False
450 comando_ejecutado.observaciones += _(u'%s no coincide con lo '
451 u'esperado (archivo "%s.diff").\n') % (longname, name)
452 log.debug(_(u'%s no coincide con lo esperado (archivo "%s.diff")'),
454 htmldiff = HtmlDiff().make_file(orig, new,
455 fromdesc=name+'.'+origname, todesc=name+'.'+newname,
456 context=True, numlines=3)
457 zip_out.writestr(name + '.diff', udiff)
458 zip_out.writestr(name + '.diff.html', htmldiff)
464 zip = ZipFile(buffer, 'w')
465 # Comparamos stdout/stderr
466 if self.STDOUTERR in a_comparar:
467 a_comparar.remove(self.STDOUTERR)
468 diff('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
469 zip_a_comparar, zip, self.STDOUTERR,
470 _(u'La salida estándar y de error combinada'))
472 if self.STDOUT in a_comparar:
473 a_comparar.remove(self.STDOUT)
474 diff('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
475 zip_a_comparar, zip, self.STDOUT, _(u'La salida estándar'))
476 if self.STDERR in a_comparar:
477 a_comparar.remove(self.STDERR)
478 diff('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
479 zip_a_comparar, zip, self.STDERR, _(u'La salida de error'))
482 if not os.path.exists(join(path, f)):
483 if self.rechazar_si_falla:
484 entrega.exito = False
485 comando_ejecutado.exito = False
486 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
487 u'"%s" para comparar pero no fue encontrado') % f
488 log.debug(_(u'Se esperaba un archivo "%s" para comparar pero '
489 u'no fue encontrado'), f)
491 diff(join(path, f), zip_a_comparar, zip, f)
493 comando_ejecutado.diferencias = buffer.getvalue()
494 if comando_ejecutado.exito is None:
495 comando_ejecutado.exito = True
496 elif self.terminar_si_falla:
497 raise ExecutionFailure(self)
499 ComandoFuente.ejecutar = ejecutar_comando_fuente
502 def ejecutar_comando_prueba(self, path, prueba): #{{{
503 # Diferencia con comando fuente: s/entrega/prueba/ y s/build/test/ en path
504 # y setup/clean de test.
505 log.debug(_(u'ComandoPrueba.ejecutar(path=%s, prueba=%s)'), path,
507 caso_de_prueba = prueba.caso_de_prueba
508 comando_ejecutado = prueba.add_comando_ejecutado(self)
509 basetmp = '/tmp/sercom.tester.prueba' # FIXME TODO /var/run/sercom?
510 #{{{ Código que solo va en ComandoPrueba (setup de directorio)
511 rsync = ('rsync', '--stats', '--itemize-changes', '--human-readable',
512 '--archive', '--acls', '--delete-during', '--force', # TODO config
513 'var/chroot_pepe/home/sercom/build/', path) # FIXME!!!! path
514 log.debug(_(u'Ejecutando como root: %s'), ' '.join(rsync))
515 os.seteuid(0) # Dios! (para chroot)
520 os.setegid(user_info.gid) # Mortal de nuevo
521 os.seteuid(user_info.uid)
522 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
523 user_info.user, user_info.group, user_info.uid, user_info.gid)
525 unzip(self.archivos_entrada, path, # TODO try/except
526 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)})
527 unzip(caso_de_prueba.archivos_entrada, path, # TODO try/except # FIXME Esto es propio de ComandoPrueba
528 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)}) # FIXME Esto es propio de ComandoPrueba
532 preexec_fn=SecureProcess(self, 'var/chroot_pepe', '/home/sercom/test') # FIXME!!!! path
534 if os.path.exists('%s.%s.stdin' % (basetmp, comando_ejecutado.id)):
535 options['stdin'] = file('%s.%s.stdin' % (basetmp, comando_ejecutado.id),
538 options['preexec_fn'].close_stdin = True
539 a_guardar = set(self.archivos_a_guardar)
540 a_guardar |= set(caso_de_prueba.archivos_a_guardar) # FIXME Esto es propio de ComandoPrueba
541 zip_a_comparar = Multizip(caso_de_prueba.archivos_a_comparar, # FIXME Esto es propio de ComandoPrueba
542 self.archivos_a_comparar) # FIXME Esto es propio de ComandoPrueba
543 a_comparar = set(zip_a_comparar.namelist())
544 a_usar = frozenset(a_guardar | a_comparar)
545 if self.STDOUTERR in a_usar:
546 options['stdout'] = file('%s.%s.stdouterr' % (basetmp,
547 comando_ejecutado.id), 'w')
548 options['stderr'] = sp.STDOUT
550 if self.STDOUT in a_usar:
551 options['stdout'] = file('%s.%s.stdout' % (basetmp,
552 comando_ejecutado.id), 'w')
554 options['preexec_fn'].close_stdout = True
555 if self.STDERR in a_usar:
556 options['stderr'] = file('%s.%s.stderr' % (basetmp,
557 comando_ejecutado.id), 'w')
559 options['preexec_fn'].close_stderr = True
560 comando = self.comando + ' ' + caso_de_prueba.comando # FIXME Esto es propio de ComandoPrueba
561 comando_ejecutado.inicio = datetime.now()
562 log.debug(_(u'Ejecutando como root: %s'), comando)
563 os.seteuid(0) # Dios! (para chroot)
567 proc = sp.Popen(comando, **options)
569 os.setegid(user_info.gid) # Mortal de nuevo
570 os.seteuid(user_info.uid)
571 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
572 user_info.user, user_info.group, user_info.uid, user_info.gid)
574 if hasattr(e, 'child_traceback'):
575 log.error(_(u'Error en el hijo: %s'), e.child_traceback)
577 proc.wait() #TODO un sleep grande nos caga todo, ver sercom viejo
578 comando_ejecutado.fin = datetime.now()
579 retorno = self.retorno
580 if retorno == self.RET_PRUEBA: # FIXME Esto es propio de ComandoPrueba
581 retorno = caso_de_prueba.retorno # FIXME Esto es propio de ComandoPrueba
582 if retorno != self.RET_ANY:
583 if retorno == self.RET_FAIL:
584 if proc.returncode == 0:
585 if self.rechazar_si_falla:
587 comando_ejecutado.exito = False
588 comando_ejecutado.observaciones += _(u'Se esperaba que el '
589 u'programa termine con un error (código de retorno '
590 u'distinto de 0) pero terminó bien (código de retorno '
592 log.debug(_(u'Se esperaba que el programa termine '
593 u'con un error (código de retorno distinto de 0) pero '
594 u'terminó bien (código de retorno 0).\n'))
595 elif retorno != proc.returncode:
596 if self.rechazar_si_falla:
598 comando_ejecutado.exito = False
599 if proc.returncode < 0:
600 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
601 u'con un código de retorno %s pero se obtuvo una señal %s '
602 u'(%s).\n') % (retorno, -proc.returncode, -proc.returncode) # TODO poner con texto
603 log.debug(_(u'Se esperaba terminar con un código '
604 u'de retorno %s pero se obtuvo una señal %s (%s).\n'),
605 retorno, -proc.returncode, -proc.returncode)
607 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
608 u'con un código de retorno %s pero se obtuvo %s.\n') \
609 % (retorno, proc.returncode)
610 log.debug(_(u'Se esperaba terminar con un código de retorno '
611 u'%s pero se obtuvo %s.\n'), retorno, proc.returncode)
612 if comando_ejecutado.exito is None:
613 log.debug(_(u'Código de retorno OK'))
616 zip = ZipFile(buffer, 'w')
617 # Guardamos stdout/stderr
618 if self.STDOUTERR in a_guardar:
619 a_guardar.remove(self.STDOUTERR)
620 zip.write('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
623 if self.STDOUT in a_guardar:
624 a_guardar.remove(self.STDOUT)
625 zip.write('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
627 if self.STDERR in a_guardar:
628 a_guardar.remove(self.STDERR)
629 zip.write('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
633 if not os.path.exists(join(path, f)):
634 if self.rechazar_si_falla:
636 comando_ejecutado.exito = False
637 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
638 u'"%s" para guardar pero no fue encontrado.\n') % f
639 log.debug(_(u'Se esperaba un archivo "%s" para guardar pero '
640 u'no fue encontrado'), f)
642 zip.write(join(path, f), f)
644 comando_ejecutado.archivos = buffer.getvalue()
645 def diff(new, zip_in, zip_out, name, longname=None, origname='correcto',
646 newname='entregado'):
649 new = file(new, 'r').readlines()
650 orig = zip_in.read(name).split('\n')
651 udiff = ''.join(list(unified_diff(orig, new, fromfile=name+'.'+origname,
652 tofile=name+'.'+newname)))
654 if self.rechazar_si_falla:
656 comando_ejecutado.exito = False
657 comando_ejecutado.observaciones += _(u'%s no coincide con lo '
658 u'esperado (archivo "%s.diff").\n') % (longname, name)
659 log.debug(_(u'%s no coincide con lo esperado (archivo "%s.diff")'),
661 htmldiff = HtmlDiff().make_file(orig, new,
662 fromdesc=name+'.'+origname, todesc=name+'.'+newname,
663 context=True, numlines=3)
664 zip_out.writestr(name + '.diff', udiff)
665 zip_out.writestr(name + '.diff.html', htmldiff)
671 zip = ZipFile(buffer, 'w')
672 # Comparamos stdout/stderr
673 if self.STDOUTERR in a_comparar:
674 a_comparar.remove(self.STDOUTERR)
675 diff('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
676 zip_a_comparar, zip, self.STDOUTERR,
677 _(u'La salida estándar y de error combinada'))
679 if self.STDOUT in a_comparar:
680 a_comparar.remove(self.STDOUT)
681 diff('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
682 zip_a_comparar, zip, self.STDOUT, _(u'La salida estándar'))
683 if self.STDERR in a_comparar:
684 a_comparar.remove(self.STDERR)
685 diff('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
686 zip_a_comparar, zip, self.STDERR, _(u'La salida de error'))
689 if not os.path.exists(join(path, f)):
690 if self.rechazar_si_falla:
692 comando_ejecutado.exito = False
693 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
694 u'"%s" para comparar pero no fue encontrado') % f
695 log.debug(_(u'Se esperaba un archivo "%s" para comparar pero '
696 u'no fue encontrado'), f)
698 diff(join(path, f), zip_a_comparar, zip, f)
700 comando_ejecutado.diferencias = buffer.getvalue()
701 if comando_ejecutado.exito is None:
702 comando_ejecutado.exito = True
703 elif self.terminar_si_falla:
704 raise ExecutionFailure(self)
706 ComandoPrueba.ejecutar = ejecutar_comando_prueba