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 SecureProcess(object): #{{{
108 max_tiempo_cpu = 120,
111 max_cant_archivos = 5,
112 max_cant_procesos = 0,
113 max_locks_memoria = 0,
115 uid = config.get('sercom.tester.chroot.user', 65534)
117 # XXX probar! make de un solo archivo lleva nproc=100 y nofile=15
118 def __init__(self, comando, chroot, cwd, close_stdin=False,
119 close_stdout=False, close_stderr=False):
120 self.comando = comando
123 self.close_stdin = close_stdin
124 self.close_stdout = close_stdout
125 self.close_stderr = close_stderr
126 log.debug(_(u'Proceso segurizado: chroot=%s, cwd=%s, user=%s, cpu=%s, '
127 u'as=%sMiB, fsize=%sMiB, nofile=%s, nproc=%s, memlock=%s'),
128 self.chroot, self.cwd, self.uid, self.max_tiempo_cpu,
129 self.max_memoria, self.max_tam_archivo, self.max_cant_archivos,
130 self.max_cant_procesos, self.max_locks_memoria)
131 def __getattr__(self, name):
132 if getattr(self.comando, name) is not None:
133 return getattr(self.comando, name)
134 return config.get('sercom.tester.limits.' + name, self.default[name])
136 x2 = lambda x: (x, x)
139 if self.close_stdout:
141 if self.close_stderr:
143 os.chroot(self.chroot)
145 uinfo = UserInfo(self.uid)
147 os.setuid(uinfo.uid) # Somos mortales irreversiblemente
148 rsrc.setrlimit(rsrc.RLIMIT_CPU, x2(self.max_tiempo_cpu))
149 rsrc.setrlimit(rsrc.RLIMIT_AS, x2(self.max_memoria*self.MB))
150 rsrc.setrlimit(rsrc.RLIMIT_FSIZE, x2(self.max_tam_archivo*self.MB)) # XXX calcular en base a archivos esperados?
151 rsrc.setrlimit(rsrc.RLIMIT_NOFILE, x2(self.max_cant_archivos)) #XXX Obtener de archivos esperados?
152 rsrc.setrlimit(rsrc.RLIMIT_NPROC, x2(self.max_cant_procesos))
153 rsrc.setrlimit(rsrc.RLIMIT_MEMLOCK, x2(self.max_locks_memoria))
154 rsrc.setrlimit(rsrc.RLIMIT_CORE, x2(0))
155 # Tratamos de forzar un sync para que entre al sleep del padre FIXME
160 class Tester(object): #{{{
162 def __init__(self, name, path, home, queue): #{{{ y properties
167 # Ahora somos mortales (oid mortales)
168 os.setegid(user_info.gid)
169 os.seteuid(user_info.uid)
170 log.debug(_(u'usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
171 user_info.user, user_info.group, user_info.uid, user_info.gid)
174 def build_path(self):
175 return join(self.chroot, self.home, 'build')
179 return join(self.chroot, self.home, 'test')
183 return join(self.path, 'chroot_' + self.name)
186 def orig_chroot(self):
187 return join(self.path, 'chroot')
191 entrega_id = self.queue.get() # blocking
192 while entrega_id is not None:
193 entrega = Entrega.get(entrega_id)
194 log.debug(_(u'Nueva entrega para probar en tester %s: %s'),
197 log.debug(_(u'Fin de pruebas de: %s'), entrega)
198 entrega_id = self.queue.get() # blocking
201 def test(self, entrega): #{{{
202 log.debug(_(u'Tester.test(entrega=%s)'), entrega)
203 entrega.inicio = datetime.now()
206 self.setup_chroot(entrega)
207 self.ejecutar_tareas_fuente(entrega)
208 self.ejecutar_tareas_prueba(entrega)
209 self.clean_chroot(entrega)
210 except ExecutionFailure, e:
213 if isinstance(e, SystemExit): raise
214 entrega.observaciones += error_interno
215 log.exception(_('Hubo una excepcion inesperada')) # FIXME encoding
217 entrega.observaciones += error_interno
218 log.exception(_('Hubo una excepcion inesperada desconocida')) # FIXME encoding
220 entrega.fin = datetime.now()
221 if entrega.exito is None:
224 log.info(_(u'Entrega correcta: %s'), entrega)
226 log.info(_(u'Entrega incorrecta: %s'), entrega)
229 def setup_chroot(self, entrega): #{{{ y clean_chroot()
230 log.debug(_(u'Tester.setup_chroot(entrega=%s)'), entrega.shortrepr())
231 rsync = ('rsync', '--stats', '--itemize-changes', '--human-readable',
232 '--archive', '--acls', '--delete-during', '--force', # TODO config
233 join(self.orig_chroot, ''), self.chroot)
234 log.debug(_(u'Ejecutando como root: %s'), ' '.join(rsync))
235 os.seteuid(0) # Dios! (para chroot)
240 os.setegid(user_info.gid) # Mortal de nuevo
241 os.seteuid(user_info.uid)
242 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
243 user_info.user, user_info.group, user_info.uid, user_info.gid)
244 unzip(entrega.archivos, self.build_path)
246 def clean_chroot(self, entrega):
247 log.debug(_(u'Tester.clean_chroot(entrega=%s)'), entrega.shortrepr())
248 pass # Se limpia con el próximo rsync
251 def ejecutar_tareas_fuente(self, entrega): #{{{ y tareas_prueba
252 log.debug(_(u'Tester.ejecutar_tareas_fuente(entrega=%s)'),
254 tareas = [t for t in entrega.instancia.ejercicio.enunciado.tareas
255 if isinstance(t, TareaFuente)]
257 tarea.ejecutar(self.build_path, entrega)
259 def ejecutar_tareas_prueba(self, entrega):
260 log.debug(_(u'Tester.ejecutar_tareas_prueba(entrega=%s)'),
262 for caso in entrega.instancia.ejercicio.enunciado.casos_de_prueba:
263 caso.ejecutar(self.test_path, entrega)
268 def ejecutar_caso_de_prueba(self, path, entrega): #{{{
269 log.debug(_(u'CasoDePrueba.ejecutar(path=%s, entrega=%s)'), path,
271 tareas = [t for t in entrega.instancia.ejercicio.enunciado.tareas
272 if isinstance(t, TareaPrueba)]
273 prueba = entrega.add_prueba(self)
277 tarea.ejecutar(path, prueba)
278 except ExecutionFailure, e:
281 prueba.fin = datetime.now()
282 if prueba.exito is None:
284 if not prueba.exito and self.rechazar_si_falla:
285 entrega.exito = False
286 if not prueba.exito and self.terminar_si_falla:
287 raise ExecutionFailure(prueba)
288 CasoDePrueba.ejecutar = ejecutar_caso_de_prueba
291 def ejecutar_tarea(self, path, ejecucion): #{{{
292 log.debug(_(u'Tarea.ejecutar(path=%s, ejecucion=%s)'), path,
293 ejecucion.shortrepr())
294 for cmd in self.comandos:
295 cmd.ejecutar(path, ejecucion)
296 Tarea.ejecutar = ejecutar_tarea
299 # TODO generalizar ejecutar_comando_xxxx!!!
301 def ejecutar_comando_fuente(self, path, entrega): #{{{
302 log.debug(_(u'ComandoFuente.ejecutar(path=%s, entrega=%s)'), path,
304 comando_ejecutado = entrega.add_comando_ejecutado(self) # TODO debería rodear solo la ejecución del comando
305 basetmp = '/tmp/sercom.tester.fuente' # FIXME TODO /var/run/sercom?
306 unzip(self.archivos_entrada, path, # TODO try/except
307 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)})
311 preexec_fn=SecureProcess(self, 'var/chroot_pepe', '/home/sercom/build') #FIXME!!! path
313 if os.path.exists('%s.%s.stdin' % (basetmp, comando_ejecutado.id)):
314 options['stdin'] = file('%s.%s.stdin' % (basetmp, comando_ejecutado.id),
317 options['preexec_fn'].close_stdin = True
318 a_guardar = set(self.archivos_a_guardar)
319 if self.archivos_a_comparar:
320 zip_a_comparar = ZipFile(StringIO(self.archivos_a_comparar), 'r')
321 a_comparar = set(zip_a_comparar.namelist())
323 zip_a_comparar = None
324 a_comparar = frozenset()
325 a_usar = frozenset(a_guardar | a_comparar)
326 if self.STDOUTERR in a_usar:
327 options['stdout'] = file('%s.%s.stdouterr' % (basetmp,
328 comando_ejecutado.id), 'w')
329 options['stderr'] = sp.STDOUT
331 if self.STDOUT in a_usar:
332 options['stdout'] = file('%s.%s.stdout' % (basetmp,
333 comando_ejecutado.id), 'w')
335 options['preexec_fn'].close_stdout = True
336 if self.STDERR in a_usar:
337 options['stderr'] = file('%s.%s.stderr' % (basetmp,
338 comando_ejecutado.id), 'w')
340 options['preexec_fn'].close_stderr = True
341 comando = self.comando # FIXME Acá tiene que diferenciarse de ComandoPrueba
342 log.debug(_(u'Ejecutando como root: %s'), comando)
343 os.seteuid(0) # Dios! (para chroot)
347 proc = sp.Popen(comando, **options)
349 os.setegid(user_info.gid) # Mortal de nuevo
350 os.seteuid(user_info.uid)
351 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
352 user_info.user, user_info.group, user_info.uid, user_info.gid)
354 if hasattr(e, 'child_traceback'):
355 log.error(_(u'Error en el hijo: %s'), e.child_traceback)
357 proc.wait() #TODO un sleep grande nos caga todo, ver sercom viejo
358 comando_ejecutado.fin = datetime.now() # TODO debería rodear solo la ejecución del comando
359 retorno = self.retorno
360 if retorno != self.RET_ANY:
361 if retorno == self.RET_FAIL:
362 if proc.returncode == 0:
363 if self.rechazar_si_falla:
364 entrega.exito = False
365 comando_ejecutado.exito = False
366 comando_ejecutado.observaciones += _(u'Se esperaba que el '
367 u'programa termine con un error (código de retorno '
368 u'distinto de 0) pero terminó bien (código de retorno '
370 log.debug(_(u'Se esperaba que el programa termine '
371 u'con un error (código de retorno distinto de 0) pero '
372 u'terminó bien (código de retorno 0).\n'))
373 elif retorno != proc.returncode:
374 if self.rechazar_si_falla:
375 entrega.exito = False
376 comando_ejecutado.exito = False
377 if proc.returncode < 0:
378 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
379 u'con un código de retorno %s pero se obtuvo una señal %s '
380 u'(%s).\n') % (retorno, -proc.returncode, -proc.returncode) # TODO poner con texto
381 log.debug(_(u'Se esperaba terminar con un código '
382 u'de retorno %s pero se obtuvo una señal %s (%s).\n'),
383 retorno, -proc.returncode, -proc.returncode)
385 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
386 u'con un código de retorno %s pero se obtuvo %s.\n') \
387 % (retorno, proc.returncode)
388 log.debug(_(u'Se esperaba terminar con un código de retorno '
389 u'%s pero se obtuvo %s.\n'), retorno, proc.returncode)
390 if comando_ejecutado.exito is None:
391 log.debug(_(u'Código de retorno OK'))
394 zip = ZipFile(buffer, 'w')
395 # Guardamos stdout/stderr
396 if self.STDOUTERR in a_guardar:
397 a_guardar.remove(self.STDOUTERR)
398 zip.write('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
401 if self.STDOUT in a_guardar:
402 a_guardar.remove(self.STDOUT)
403 zip.write('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
405 if self.STDERR in a_guardar:
406 a_guardar.remove(self.STDERR)
407 zip.write('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
411 if not os.path.exists(join(path, f)):
412 if self.rechazar_si_falla:
413 entrega.exito = False
414 comando_ejecutado.exito = False
415 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
416 u'"%s" para guardar pero no fue encontrado.\n') % f
417 log.debug(_(u'Se esperaba un archivo "%s" para guardar pero '
418 u'no fue encontrado'), f)
420 zip.write(join(path, f), f)
422 comando_ejecutado.archivos = buffer.getvalue()
423 def diff(new, zip_in, zip_out, name, longname=None, origname='correcto',
424 newname='entregado'):
427 new = file(new, 'r').readlines()
428 orig = zip_in.read(name).split('\n')
429 udiff = ''.join(list(unified_diff(orig, new, fromfile=name+'.'+origname,
430 tofile=name+'.'+newname)))
432 if self.rechazar_si_falla:
433 entrega.exito = False
434 comando_ejecutado.exito = False
435 comando_ejecutado.observaciones += _(u'%s no coincide con lo '
436 u'esperado (archivo "%s.diff").\n') % (longname, name)
437 log.debug(_(u'%s no coincide con lo esperado (archivo "%s.diff")'),
439 htmldiff = HtmlDiff().make_file(orig, new,
440 fromdesc=name+'.'+origname, todesc=name+'.'+newname,
441 context=True, numlines=3)
442 zip_out.writestr(name + '.diff', udiff)
443 zip_out.writestr(name + '.diff.html', htmldiff)
449 zip = ZipFile(buffer, 'w')
450 # Comparamos stdout/stderr
451 if self.STDOUTERR in a_comparar:
452 a_comparar.remove(self.STDOUTERR)
453 diff('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
454 zip_a_comparar, zip, self.STDOUTERR,
455 _(u'La salida estándar y de error combinada'))
457 if self.STDOUT in a_comparar:
458 a_comparar.remove(self.STDOUT)
459 diff('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
460 zip_a_comparar, zip, self.STDOUT, _(u'La salida estándar'))
461 if self.STDERR in a_comparar:
462 a_comparar.remove(self.STDERR)
463 diff('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
464 zip_a_comparar, zip, self.STDERR, _(u'La salida de error'))
467 if not os.path.exists(join(path, f)):
468 if self.rechazar_si_falla:
469 entrega.exito = False
470 comando_ejecutado.exito = False
471 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
472 u'"%s" para comparar pero no fue encontrado') % f
473 log.debug(_(u'Se esperaba un archivo "%s" para comparar pero '
474 u'no fue encontrado'), f)
476 diff(join(path, f), zip_a_comparar, zip, f)
478 comando_ejecutado.diferencias = buffer.getvalue()
479 if comando_ejecutado.exito is None:
480 comando_ejecutado.exito = True
481 elif self.terminar_si_falla:
482 raise ExecutionFailure(self)
484 ComandoFuente.ejecutar = ejecutar_comando_fuente
487 def ejecutar_comando_prueba(self, path, prueba): #{{{
488 # Diferencia con comando fuente: s/entrega/prueba/ y s/build/test/ en path
489 # y setup/clean de test.
490 log.debug(_(u'ComandoPrueba.ejecutar(path=%s, prueba=%s)'), path,
492 comando_ejecutado = prueba.add_comando_ejecutado(self) # TODO debería rodear solo la ejecución del comando
493 basetmp = '/tmp/sercom.tester.prueba' # FIXME TODO /var/run/sercom?
494 #{{{ Código que solo va en ComandoPrueba (setup de directorio)
495 rsync = ('rsync', '--stats', '--itemize-changes', '--human-readable',
496 '--archive', '--acls', '--delete-during', '--force', # TODO config
497 'var/chroot_pepe/home/sercom/build/', path) # FIXME!!!! path
498 log.debug(_(u'Ejecutando como root: %s'), ' '.join(rsync))
499 os.seteuid(0) # Dios! (para chroot)
504 os.setegid(user_info.gid) # Mortal de nuevo
505 os.seteuid(user_info.uid)
506 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
507 user_info.user, user_info.group, user_info.uid, user_info.gid)
508 unzip(prueba.caso_de_prueba.archivos_entrada, path, # TODO try/except
509 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)})
511 unzip(self.archivos_entrada, path, # TODO try/except
512 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)})
516 preexec_fn=SecureProcess(self, 'var/chroot_pepe', '/home/sercom/test') # FIXME!!!! path
518 if os.path.exists('%s.%s.stdin' % (basetmp, comando_ejecutado.id)):
519 options['stdin'] = file('%s.%s.stdin' % (basetmp, comando_ejecutado.id),
522 options['preexec_fn'].close_stdin = True
523 a_guardar = set(self.archivos_a_guardar)
524 a_guardar |= set(prueba.caso_de_prueba.archivos_a_guardar) # FIXME Esto es propio de ComandoPrueba
525 if self.archivos_a_comparar:
526 zip_a_comparar = ZipFile(StringIO(self.archivos_a_comparar), 'r')
527 a_comparar = set(zip_a_comparar.namelist())
529 zip_a_comparar = None
530 a_comparar = frozenset()
531 a_usar = frozenset(a_guardar | a_comparar)
532 if self.STDOUTERR in a_usar:
533 options['stdout'] = file('%s.%s.stdouterr' % (basetmp,
534 comando_ejecutado.id), 'w')
535 options['stderr'] = sp.STDOUT
537 if self.STDOUT in a_usar:
538 options['stdout'] = file('%s.%s.stdout' % (basetmp,
539 comando_ejecutado.id), 'w')
541 options['preexec_fn'].close_stdout = True
542 if self.STDERR in a_usar:
543 options['stderr'] = file('%s.%s.stderr' % (basetmp,
544 comando_ejecutado.id), 'w')
546 options['preexec_fn'].close_stderr = True
547 comando = self.comando + ' ' + prueba.caso_de_prueba.comando # FIXME Esto es propio de ComandoPrueba
548 log.debug(_(u'Ejecutando como root: %s'), comando)
549 os.seteuid(0) # Dios! (para chroot)
553 proc = sp.Popen(comando, **options)
555 os.setegid(user_info.gid) # Mortal de nuevo
556 os.seteuid(user_info.uid)
557 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
558 user_info.user, user_info.group, user_info.uid, user_info.gid)
560 if hasattr(e, 'child_traceback'):
561 log.error(_(u'Error en el hijo: %s'), e.child_traceback)
563 proc.wait() #TODO un sleep grande nos caga todo, ver sercom viejo
564 comando_ejecutado.fin_tareas = datetime.now() # TODO debería rodear solo la ejecución del comando
565 retorno = self.retorno
566 if retorno == self.RET_PRUEBA: # FIXME Esto es propio de ComandoPrueba
567 retorno = prueba.caso_de_prueba.retorno # FIXME Esto es propio de ComandoPrueba
568 if retorno != self.RET_ANY:
569 if retorno == self.RET_FAIL:
570 if proc.returncode == 0:
571 if self.rechazar_si_falla:
573 comando_ejecutado.exito = False
574 comando_ejecutado.observaciones += _(u'Se esperaba que el '
575 u'programa termine con un error (código de retorno '
576 u'distinto de 0) pero terminó bien (código de retorno '
578 log.debug(_(u'Se esperaba que el programa termine '
579 u'con un error (código de retorno distinto de 0) pero '
580 u'terminó bien (código de retorno 0).\n'))
581 elif retorno != proc.returncode:
582 if self.rechazar_si_falla:
584 comando_ejecutado.exito = False
585 if proc.returncode < 0:
586 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
587 u'con un código de retorno %s pero se obtuvo una señal %s '
588 u'(%s).\n') % (retorno, -proc.returncode, -proc.returncode) # TODO poner con texto
589 log.debug(_(u'Se esperaba terminar con un código '
590 u'de retorno %s pero se obtuvo una señal %s (%s).\n'),
591 retorno, -proc.returncode, -proc.returncode)
593 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
594 u'con un código de retorno %s pero se obtuvo %s.\n') \
595 % (retorno, proc.returncode)
596 log.debug(_(u'Se esperaba terminar con un código de retorno '
597 u'%s pero se obtuvo %s.\n'), retorno, proc.returncode)
598 if comando_ejecutado.exito is None:
599 log.debug(_(u'Código de retorno OK'))
602 zip = ZipFile(buffer, 'w')
603 # Guardamos stdout/stderr
604 if self.STDOUTERR in a_guardar:
605 a_guardar.remove(self.STDOUTERR)
606 zip.write('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
609 if self.STDOUT in a_guardar:
610 a_guardar.remove(self.STDOUT)
611 zip.write('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
613 if self.STDERR in a_guardar:
614 a_guardar.remove(self.STDERR)
615 zip.write('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
619 if not os.path.exists(join(path, f)):
620 if self.rechazar_si_falla:
622 comando_ejecutado.exito = False
623 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
624 u'"%s" para guardar pero no fue encontrado.\n') % f
625 log.debug(_(u'Se esperaba un archivo "%s" para guardar pero '
626 u'no fue encontrado'), f)
628 zip.write(join(path, f), f)
630 comando_ejecutado.archivos = buffer.getvalue()
631 def diff(new, zip_in, zip_out, name, longname=None, origname='correcto',
632 newname='entregado'):
635 new = file(new, 'r').readlines()
636 orig = zip_in.read(name).split('\n')
637 udiff = ''.join(list(unified_diff(orig, new, fromfile=name+'.'+origname,
638 tofile=name+'.'+newname)))
640 if self.rechazar_si_falla:
642 comando_ejecutado.exito = False
643 comando_ejecutado.observaciones += _(u'%s no coincide con lo '
644 u'esperado (archivo "%s.diff").\n') % (longname, name)
645 log.debug(_(u'%s no coincide con lo esperado (archivo "%s.diff")'),
647 htmldiff = HtmlDiff().make_file(orig, new,
648 fromdesc=name+'.'+origname, todesc=name+'.'+newname,
649 context=True, numlines=3)
650 zip_out.writestr(name + '.diff', udiff)
651 zip_out.writestr(name + '.diff.html', htmldiff)
657 zip = ZipFile(buffer, 'w')
658 # Comparamos stdout/stderr
659 if self.STDOUTERR in a_comparar:
660 a_comparar.remove(self.STDOUTERR)
661 diff('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
662 zip_a_comparar, zip, self.STDOUTERR,
663 _(u'La salida estándar y de error combinada'))
665 if self.STDOUT in a_comparar:
666 a_comparar.remove(self.STDOUT)
667 diff('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
668 zip_a_comparar, zip, self.STDOUT, _(u'La salida estándar'))
669 if self.STDERR in a_comparar:
670 a_comparar.remove(self.STDERR)
671 diff('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
672 zip_a_comparar, zip, self.STDERR, _(u'La salida de error'))
675 if not os.path.exists(join(path, f)):
676 if self.rechazar_si_falla:
678 comando_ejecutado.exito = False
679 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
680 u'"%s" para comparar pero no fue encontrado') % f
681 log.debug(_(u'Se esperaba un archivo "%s" para comparar pero '
682 u'no fue encontrado'), f)
684 diff(join(path, f), zip_a_comparar, zip, f)
686 comando_ejecutado.diferencias = buffer.getvalue()
687 if comando_ejecutado.exito is None:
688 comando_ejecutado.exito = True
689 elif self.terminar_si_falla:
690 raise ExecutionFailure(self)
692 ComandoPrueba.ejecutar = ejecutar_comando_prueba