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 log.debug(_(u'Ejecutando como root: %s'), self.comando)
342 os.seteuid(0) # Dios! (para chroot)
346 proc = sp.Popen(self.comando, **options)
348 os.setegid(user_info.gid) # Mortal de nuevo
349 os.seteuid(user_info.uid)
350 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
351 user_info.user, user_info.group, user_info.uid, user_info.gid)
353 if hasattr(e, 'child_traceback'):
354 log.error(_(u'Error en el hijo: %s'), e.child_traceback)
356 proc.wait() #TODO un sleep grande nos caga todo, ver sercom viejo
357 comando_ejecutado.fin = datetime.now() # TODO debería rodear solo la ejecución del comando
358 retorno = self.retorno
359 if retorno != self.RET_ANY:
360 if retorno == self.RET_FAIL:
361 if proc.returncode == 0:
362 if self.rechazar_si_falla:
363 entrega.exito = False
364 comando_ejecutado.exito = False
365 comando_ejecutado.observaciones += _(u'Se esperaba que el '
366 u'programa termine con un error (código de retorno '
367 u'distinto de 0) pero terminó bien (código de retorno '
369 log.debug(_(u'Se esperaba que el programa termine '
370 u'con un error (código de retorno distinto de 0) pero '
371 u'terminó bien (código de retorno 0).\n'))
372 elif retorno != proc.returncode:
373 if self.rechazar_si_falla:
374 entrega.exito = False
375 comando_ejecutado.exito = False
376 if proc.returncode < 0:
377 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
378 u'con un código de retorno %s pero se obtuvo una señal %s '
379 u'(%s).\n') % (retorno, -proc.returncode, -proc.returncode) # TODO poner con texto
380 log.debug(_(u'Se esperaba terminar con un código '
381 u'de retorno %s pero se obtuvo una señal %s (%s).\n'),
382 retorno, -proc.returncode, -proc.returncode)
384 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
385 u'con un código de retorno %s pero se obtuvo %s.\n') \
386 % (retorno, proc.returncode)
387 log.debug(_(u'Se esperaba terminar con un código de retorno '
388 u'%s pero se obtuvo %s.\n'), retorno, proc.returncode)
389 if comando_ejecutado.exito is None:
390 log.debug(_(u'Código de retorno OK'))
393 zip = ZipFile(buffer, 'w')
394 # Guardamos stdout/stderr
395 if self.STDOUTERR in a_guardar:
396 a_guardar.remove(self.STDOUTERR)
397 zip.write('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
400 if self.STDOUT in a_guardar:
401 a_guardar.remove(self.STDOUT)
402 zip.write('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
404 if self.STDERR in a_guardar:
405 a_guardar.remove(self.STDERR)
406 zip.write('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
410 if not os.path.exists(join(path, f)):
411 if self.rechazar_si_falla:
412 entrega.exito = False
413 comando_ejecutado.exito = False
414 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
415 u'"%s" para guardar pero no fue encontrado.\n') % f
416 log.debug(_(u'Se esperaba un archivo "%s" para guardar pero '
417 u'no fue encontrado'), f)
419 zip.write(join(path, f), f)
421 comando_ejecutado.archivos = buffer.getvalue()
422 def diff(new, zip_in, zip_out, name, longname=None, origname='correcto',
423 newname='entregado'):
426 new = file(new, 'r').readlines()
427 orig = zip_in.read(name).split('\n')
428 udiff = ''.join(list(unified_diff(orig, new, fromfile=name+'.'+origname,
429 tofile=name+'.'+newname)))
431 if self.rechazar_si_falla:
432 entrega.exito = False
433 comando_ejecutado.exito = False
434 comando_ejecutado.observaciones += _(u'%s no coincide con lo '
435 u'esperado (archivo "%s.diff").\n') % (longname, name)
436 log.debug(_(u'%s no coincide con lo esperado (archivo "%s.diff")'),
438 htmldiff = HtmlDiff().make_file(orig, new,
439 fromdesc=name+'.'+origname, todesc=name+'.'+newname,
440 context=True, numlines=3)
441 zip_out.writestr(name + '.diff', udiff)
442 zip_out.writestr(name + '.diff.html', htmldiff)
448 zip = ZipFile(buffer, 'w')
449 # Comparamos stdout/stderr
450 if self.STDOUTERR in a_comparar:
451 a_comparar.remove(self.STDOUTERR)
452 diff('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
453 zip_a_comparar, zip, self.STDOUTERR,
454 _(u'La salida estándar y de error combinada'))
456 if self.STDOUT in a_comparar:
457 a_comparar.remove(self.STDOUT)
458 diff('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
459 zip_a_comparar, zip, self.STDOUT, _(u'La salida estándar'))
460 if self.STDERR in a_comparar:
461 a_comparar.remove(self.STDERR)
462 diff('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
463 zip_a_comparar, zip, self.STDERR, _(u'La salida de error'))
466 if not os.path.exists(join(path, f)):
467 if self.rechazar_si_falla:
468 entrega.exito = False
469 comando_ejecutado.exito = False
470 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
471 u'"%s" para comparar pero no fue encontrado') % f
472 log.debug(_(u'Se esperaba un archivo "%s" para comparar pero '
473 u'no fue encontrado'), f)
475 diff(join(path, f), zip_a_comparar, zip, f)
477 comando_ejecutado.diferencias = buffer.getvalue()
478 if comando_ejecutado.exito is None:
479 comando_ejecutado.exito = True
480 elif self.terminar_si_falla:
481 raise ExecutionFailure(self)
483 ComandoFuente.ejecutar = ejecutar_comando_fuente
486 def ejecutar_comando_prueba(self, path, prueba): #{{{
487 # Diferencia con comando fuente: s/entrega/prueba/ y s/build/test/ en path
488 # y setup/clean de test.
489 log.debug(_(u'ComandoPrueba.ejecutar(path=%s, prueba=%s)'), path,
491 comando_ejecutado = prueba.add_comando_ejecutado(self) # TODO debería rodear solo la ejecución del comando
492 basetmp = '/tmp/sercom.tester.prueba' # FIXME TODO /var/run/sercom?
493 #{{{ Código que solo va en ComandoPrueba (setup de directorio)
494 rsync = ('rsync', '--stats', '--itemize-changes', '--human-readable',
495 '--archive', '--acls', '--delete-during', '--force', # TODO config
496 'var/chroot_pepe/home/sercom/build/', path) # FIXME!!!! path
497 log.debug(_(u'Ejecutando como root: %s'), ' '.join(rsync))
498 os.seteuid(0) # Dios! (para chroot)
503 os.setegid(user_info.gid) # Mortal de nuevo
504 os.seteuid(user_info.uid)
505 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
506 user_info.user, user_info.group, user_info.uid, user_info.gid)
507 unzip(prueba.caso_de_prueba.archivos_entrada, path, # TODO try/except
508 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)})
510 unzip(self.archivos_entrada, path, # TODO try/except
511 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)})
515 preexec_fn=SecureProcess(self, 'var/chroot_pepe', '/home/sercom/test') # FIXME!!!! path
517 if os.path.exists('%s.%s.stdin' % (basetmp, comando_ejecutado.id)):
518 options['stdin'] = file('%s.%s.stdin' % (basetmp, comando_ejecutado.id),
521 options['preexec_fn'].close_stdin = True
522 a_guardar = set(self.archivos_a_guardar)
523 if self.archivos_a_comparar:
524 zip_a_comparar = ZipFile(StringIO(self.archivos_a_comparar), 'r')
525 a_comparar = set(zip_a_comparar.namelist())
527 zip_a_comparar = None
528 a_comparar = frozenset()
529 a_usar = frozenset(a_guardar | a_comparar)
530 if self.STDOUTERR in a_usar:
531 options['stdout'] = file('%s.%s.stdouterr' % (basetmp,
532 comando_ejecutado.id), 'w')
533 options['stderr'] = sp.STDOUT
535 if self.STDOUT in a_usar:
536 options['stdout'] = file('%s.%s.stdout' % (basetmp,
537 comando_ejecutado.id), 'w')
539 options['preexec_fn'].close_stdout = True
540 if self.STDERR in a_usar:
541 options['stderr'] = file('%s.%s.stderr' % (basetmp,
542 comando_ejecutado.id), 'w')
544 options['preexec_fn'].close_stderr = True
545 log.debug(_(u'Ejecutando como root: %s'), self.comando)
546 os.seteuid(0) # Dios! (para chroot)
550 proc = sp.Popen(self.comando, **options)
552 os.setegid(user_info.gid) # Mortal de nuevo
553 os.seteuid(user_info.uid)
554 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
555 user_info.user, user_info.group, user_info.uid, user_info.gid)
557 if hasattr(e, 'child_traceback'):
558 log.error(_(u'Error en el hijo: %s'), e.child_traceback)
560 proc.wait() #TODO un sleep grande nos caga todo, ver sercom viejo
561 comando_ejecutado.fin_tareas = datetime.now() # TODO debería rodear solo la ejecución del comando
562 retorno = self.retorno
563 if retorno == self.RET_PRUEBA: # FIXME Esto es propio de ComandoPrueba
564 retorno = prueba.caso_de_prueba.retorno # FIXME Esto es propio de ComandoPrueba
565 if retorno != self.RET_ANY:
566 if retorno == self.RET_FAIL:
567 if proc.returncode == 0:
568 if self.rechazar_si_falla:
570 comando_ejecutado.exito = False
571 comando_ejecutado.observaciones += _(u'Se esperaba que el '
572 u'programa termine con un error (código de retorno '
573 u'distinto de 0) pero terminó bien (código de retorno '
575 log.debug(_(u'Se esperaba que el programa termine '
576 u'con un error (código de retorno distinto de 0) pero '
577 u'terminó bien (código de retorno 0).\n'))
578 elif retorno != proc.returncode:
579 if self.rechazar_si_falla:
581 comando_ejecutado.exito = False
582 if proc.returncode < 0:
583 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
584 u'con un código de retorno %s pero se obtuvo una señal %s '
585 u'(%s).\n') % (retorno, -proc.returncode, -proc.returncode) # TODO poner con texto
586 log.debug(_(u'Se esperaba terminar con un código '
587 u'de retorno %s pero se obtuvo una señal %s (%s).\n'),
588 retorno, -proc.returncode, -proc.returncode)
590 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
591 u'con un código de retorno %s pero se obtuvo %s.\n') \
592 % (retorno, proc.returncode)
593 log.debug(_(u'Se esperaba terminar con un código de retorno '
594 u'%s pero se obtuvo %s.\n'), retorno, proc.returncode)
595 if comando_ejecutado.exito is None:
596 log.debug(_(u'Código de retorno OK'))
599 zip = ZipFile(buffer, 'w')
600 # Guardamos stdout/stderr
601 if self.STDOUTERR in a_guardar:
602 a_guardar.remove(self.STDOUTERR)
603 zip.write('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
606 if self.STDOUT in a_guardar:
607 a_guardar.remove(self.STDOUT)
608 zip.write('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
610 if self.STDERR in a_guardar:
611 a_guardar.remove(self.STDERR)
612 zip.write('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
616 if not os.path.exists(join(path, f)):
617 if self.rechazar_si_falla:
619 comando_ejecutado.exito = False
620 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
621 u'"%s" para guardar pero no fue encontrado.\n') % f
622 log.debug(_(u'Se esperaba un archivo "%s" para guardar pero '
623 u'no fue encontrado'), f)
625 zip.write(join(path, f), f)
627 comando_ejecutado.archivos = buffer.getvalue()
628 def diff(new, zip_in, zip_out, name, longname=None, origname='correcto',
629 newname='entregado'):
632 new = file(new, 'r').readlines()
633 orig = zip_in.read(name).split('\n')
634 udiff = ''.join(list(unified_diff(orig, new, fromfile=name+'.'+origname,
635 tofile=name+'.'+newname)))
637 if self.rechazar_si_falla:
639 comando_ejecutado.exito = False
640 comando_ejecutado.observaciones += _(u'%s no coincide con lo '
641 u'esperado (archivo "%s.diff").\n') % (longname, name)
642 log.debug(_(u'%s no coincide con lo esperado (archivo "%s.diff")'),
644 htmldiff = HtmlDiff().make_file(orig, new,
645 fromdesc=name+'.'+origname, todesc=name+'.'+newname,
646 context=True, numlines=3)
647 zip_out.writestr(name + '.diff', udiff)
648 zip_out.writestr(name + '.diff.html', htmldiff)
654 zip = ZipFile(buffer, 'w')
655 # Comparamos stdout/stderr
656 if self.STDOUTERR in a_comparar:
657 a_comparar.remove(self.STDOUTERR)
658 diff('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
659 zip_a_comparar, zip, self.STDOUTERR,
660 _(u'La salida estándar y de error combinada'))
662 if self.STDOUT in a_comparar:
663 a_comparar.remove(self.STDOUT)
664 diff('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
665 zip_a_comparar, zip, self.STDOUT, _(u'La salida estándar'))
666 if self.STDERR in a_comparar:
667 a_comparar.remove(self.STDERR)
668 diff('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
669 zip_a_comparar, zip, self.STDERR, _(u'La salida de error'))
672 if not os.path.exists(join(path, f)):
673 if self.rechazar_si_falla:
675 comando_ejecutado.exito = False
676 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
677 u'"%s" para comparar pero no fue encontrado') % f
678 log.debug(_(u'Se esperaba un archivo "%s" para comparar pero '
679 u'no fue encontrado'), f)
681 diff(join(path, f), zip_a_comparar, zip, f)
683 comando_ejecutado.diferencias = buffer.getvalue()
684 if comando_ejecutado.exito is None:
685 comando_ejecutado.exito = True
686 elif self.terminar_si_falla:
687 raise ExecutionFailure(self)
689 ComandoPrueba.ejecutar = ejecutar_comando_prueba