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)
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) # TODO debería rodear solo la ejecución del comando
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 log.debug(_(u'Ejecutando como root: %s'), comando)
357 os.seteuid(0) # Dios! (para chroot)
361 proc = sp.Popen(comando, **options)
363 os.setegid(user_info.gid) # Mortal de nuevo
364 os.seteuid(user_info.uid)
365 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
366 user_info.user, user_info.group, user_info.uid, user_info.gid)
368 if hasattr(e, 'child_traceback'):
369 log.error(_(u'Error en el hijo: %s'), e.child_traceback)
371 proc.wait() #TODO un sleep grande nos caga todo, ver sercom viejo
372 comando_ejecutado.fin = datetime.now() # TODO debería rodear solo la ejecución del comando
373 retorno = self.retorno
374 if retorno != self.RET_ANY:
375 if retorno == self.RET_FAIL:
376 if proc.returncode == 0:
377 if self.rechazar_si_falla:
378 entrega.exito = False
379 comando_ejecutado.exito = False
380 comando_ejecutado.observaciones += _(u'Se esperaba que el '
381 u'programa termine con un error (código de retorno '
382 u'distinto de 0) pero terminó bien (código de retorno '
384 log.debug(_(u'Se esperaba que el programa termine '
385 u'con un error (código de retorno distinto de 0) pero '
386 u'terminó bien (código de retorno 0).\n'))
387 elif retorno != proc.returncode:
388 if self.rechazar_si_falla:
389 entrega.exito = False
390 comando_ejecutado.exito = False
391 if proc.returncode < 0:
392 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
393 u'con un código de retorno %s pero se obtuvo una señal %s '
394 u'(%s).\n') % (retorno, -proc.returncode, -proc.returncode) # TODO poner con texto
395 log.debug(_(u'Se esperaba terminar con un código '
396 u'de retorno %s pero se obtuvo una señal %s (%s).\n'),
397 retorno, -proc.returncode, -proc.returncode)
399 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
400 u'con un código de retorno %s pero se obtuvo %s.\n') \
401 % (retorno, proc.returncode)
402 log.debug(_(u'Se esperaba terminar con un código de retorno '
403 u'%s pero se obtuvo %s.\n'), retorno, proc.returncode)
404 if comando_ejecutado.exito is None:
405 log.debug(_(u'Código de retorno OK'))
408 zip = ZipFile(buffer, 'w')
409 # Guardamos stdout/stderr
410 if self.STDOUTERR in a_guardar:
411 a_guardar.remove(self.STDOUTERR)
412 zip.write('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
415 if self.STDOUT in a_guardar:
416 a_guardar.remove(self.STDOUT)
417 zip.write('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
419 if self.STDERR in a_guardar:
420 a_guardar.remove(self.STDERR)
421 zip.write('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
425 if not os.path.exists(join(path, f)):
426 if self.rechazar_si_falla:
427 entrega.exito = False
428 comando_ejecutado.exito = False
429 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
430 u'"%s" para guardar pero no fue encontrado.\n') % f
431 log.debug(_(u'Se esperaba un archivo "%s" para guardar pero '
432 u'no fue encontrado'), f)
434 zip.write(join(path, f), f)
436 comando_ejecutado.archivos = buffer.getvalue()
437 def diff(new, zip_in, zip_out, name, longname=None, origname='correcto',
438 newname='entregado'):
441 new = file(new, 'r').readlines()
442 orig = zip_in.read(name).split('\n')
443 udiff = ''.join(list(unified_diff(orig, new, fromfile=name+'.'+origname,
444 tofile=name+'.'+newname)))
446 if self.rechazar_si_falla:
447 entrega.exito = False
448 comando_ejecutado.exito = False
449 comando_ejecutado.observaciones += _(u'%s no coincide con lo '
450 u'esperado (archivo "%s.diff").\n') % (longname, name)
451 log.debug(_(u'%s no coincide con lo esperado (archivo "%s.diff")'),
453 htmldiff = HtmlDiff().make_file(orig, new,
454 fromdesc=name+'.'+origname, todesc=name+'.'+newname,
455 context=True, numlines=3)
456 zip_out.writestr(name + '.diff', udiff)
457 zip_out.writestr(name + '.diff.html', htmldiff)
463 zip = ZipFile(buffer, 'w')
464 # Comparamos stdout/stderr
465 if self.STDOUTERR in a_comparar:
466 a_comparar.remove(self.STDOUTERR)
467 diff('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
468 zip_a_comparar, zip, self.STDOUTERR,
469 _(u'La salida estándar y de error combinada'))
471 if self.STDOUT in a_comparar:
472 a_comparar.remove(self.STDOUT)
473 diff('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
474 zip_a_comparar, zip, self.STDOUT, _(u'La salida estándar'))
475 if self.STDERR in a_comparar:
476 a_comparar.remove(self.STDERR)
477 diff('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
478 zip_a_comparar, zip, self.STDERR, _(u'La salida de error'))
481 if not os.path.exists(join(path, f)):
482 if self.rechazar_si_falla:
483 entrega.exito = False
484 comando_ejecutado.exito = False
485 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
486 u'"%s" para comparar pero no fue encontrado') % f
487 log.debug(_(u'Se esperaba un archivo "%s" para comparar pero '
488 u'no fue encontrado'), f)
490 diff(join(path, f), zip_a_comparar, zip, f)
492 comando_ejecutado.diferencias = buffer.getvalue()
493 if comando_ejecutado.exito is None:
494 comando_ejecutado.exito = True
495 elif self.terminar_si_falla:
496 raise ExecutionFailure(self)
498 ComandoFuente.ejecutar = ejecutar_comando_fuente
501 def ejecutar_comando_prueba(self, path, prueba): #{{{
502 # Diferencia con comando fuente: s/entrega/prueba/ y s/build/test/ en path
503 # y setup/clean de test.
504 log.debug(_(u'ComandoPrueba.ejecutar(path=%s, prueba=%s)'), path,
506 caso_de_prueba = prueba.caso_de_prueba
507 comando_ejecutado = prueba.add_comando_ejecutado(self) # TODO debería rodear solo la ejecución del comando
508 basetmp = '/tmp/sercom.tester.prueba' # FIXME TODO /var/run/sercom?
509 #{{{ Código que solo va en ComandoPrueba (setup de directorio)
510 rsync = ('rsync', '--stats', '--itemize-changes', '--human-readable',
511 '--archive', '--acls', '--delete-during', '--force', # TODO config
512 'var/chroot_pepe/home/sercom/build/', path) # FIXME!!!! path
513 log.debug(_(u'Ejecutando como root: %s'), ' '.join(rsync))
514 os.seteuid(0) # Dios! (para chroot)
519 os.setegid(user_info.gid) # Mortal de nuevo
520 os.seteuid(user_info.uid)
521 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
522 user_info.user, user_info.group, user_info.uid, user_info.gid)
523 unzip(caso_de_prueba.archivos_entrada, path, # TODO try/except
524 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)})
526 unzip(self.archivos_entrada, path, # TODO try/except
527 {self.STDIN: '%s.%s.stdin' % (basetmp, comando_ejecutado.id)})
531 preexec_fn=SecureProcess(self, 'var/chroot_pepe', '/home/sercom/test') # FIXME!!!! path
533 if os.path.exists('%s.%s.stdin' % (basetmp, comando_ejecutado.id)):
534 options['stdin'] = file('%s.%s.stdin' % (basetmp, comando_ejecutado.id),
537 options['preexec_fn'].close_stdin = True
538 a_guardar = set(self.archivos_a_guardar)
539 a_guardar |= set(caso_de_prueba.archivos_a_guardar) # FIXME Esto es propio de ComandoPrueba
540 zip_a_comparar = Multizip(self.archivos_a_comparar,
541 caso_de_prueba.archivos_a_comparar) # FIXME Esto es propio de ComandoPrueba
542 a_comparar = set(zip_a_comparar.namelist())
543 a_usar = frozenset(a_guardar | a_comparar)
544 if self.STDOUTERR in a_usar:
545 options['stdout'] = file('%s.%s.stdouterr' % (basetmp,
546 comando_ejecutado.id), 'w')
547 options['stderr'] = sp.STDOUT
549 if self.STDOUT in a_usar:
550 options['stdout'] = file('%s.%s.stdout' % (basetmp,
551 comando_ejecutado.id), 'w')
553 options['preexec_fn'].close_stdout = True
554 if self.STDERR in a_usar:
555 options['stderr'] = file('%s.%s.stderr' % (basetmp,
556 comando_ejecutado.id), 'w')
558 options['preexec_fn'].close_stderr = True
559 comando = self.comando + ' ' + caso_de_prueba.comando # FIXME Esto es propio de ComandoPrueba
560 log.debug(_(u'Ejecutando como root: %s'), comando)
561 os.seteuid(0) # Dios! (para chroot)
565 proc = sp.Popen(comando, **options)
567 os.setegid(user_info.gid) # Mortal de nuevo
568 os.seteuid(user_info.uid)
569 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
570 user_info.user, user_info.group, user_info.uid, user_info.gid)
572 if hasattr(e, 'child_traceback'):
573 log.error(_(u'Error en el hijo: %s'), e.child_traceback)
575 proc.wait() #TODO un sleep grande nos caga todo, ver sercom viejo
576 comando_ejecutado.fin_tareas = datetime.now() # TODO debería rodear solo la ejecución del comando
577 retorno = self.retorno
578 if retorno == self.RET_PRUEBA: # FIXME Esto es propio de ComandoPrueba
579 retorno = caso_de_prueba.retorno # FIXME Esto es propio de ComandoPrueba
580 if retorno != self.RET_ANY:
581 if retorno == self.RET_FAIL:
582 if proc.returncode == 0:
583 if self.rechazar_si_falla:
585 comando_ejecutado.exito = False
586 comando_ejecutado.observaciones += _(u'Se esperaba que el '
587 u'programa termine con un error (código de retorno '
588 u'distinto de 0) pero terminó bien (código de retorno '
590 log.debug(_(u'Se esperaba que el programa termine '
591 u'con un error (código de retorno distinto de 0) pero '
592 u'terminó bien (código de retorno 0).\n'))
593 elif retorno != proc.returncode:
594 if self.rechazar_si_falla:
596 comando_ejecutado.exito = False
597 if proc.returncode < 0:
598 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
599 u'con un código de retorno %s pero se obtuvo una señal %s '
600 u'(%s).\n') % (retorno, -proc.returncode, -proc.returncode) # TODO poner con texto
601 log.debug(_(u'Se esperaba terminar con un código '
602 u'de retorno %s pero se obtuvo una señal %s (%s).\n'),
603 retorno, -proc.returncode, -proc.returncode)
605 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
606 u'con un código de retorno %s pero se obtuvo %s.\n') \
607 % (retorno, proc.returncode)
608 log.debug(_(u'Se esperaba terminar con un código de retorno '
609 u'%s pero se obtuvo %s.\n'), retorno, proc.returncode)
610 if comando_ejecutado.exito is None:
611 log.debug(_(u'Código de retorno OK'))
614 zip = ZipFile(buffer, 'w')
615 # Guardamos stdout/stderr
616 if self.STDOUTERR in a_guardar:
617 a_guardar.remove(self.STDOUTERR)
618 zip.write('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
621 if self.STDOUT in a_guardar:
622 a_guardar.remove(self.STDOUT)
623 zip.write('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
625 if self.STDERR in a_guardar:
626 a_guardar.remove(self.STDERR)
627 zip.write('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
631 if not os.path.exists(join(path, f)):
632 if self.rechazar_si_falla:
634 comando_ejecutado.exito = False
635 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
636 u'"%s" para guardar pero no fue encontrado.\n') % f
637 log.debug(_(u'Se esperaba un archivo "%s" para guardar pero '
638 u'no fue encontrado'), f)
640 zip.write(join(path, f), f)
642 comando_ejecutado.archivos = buffer.getvalue()
643 def diff(new, zip_in, zip_out, name, longname=None, origname='correcto',
644 newname='entregado'):
647 new = file(new, 'r').readlines()
648 orig = zip_in.read(name).split('\n')
649 udiff = ''.join(list(unified_diff(orig, new, fromfile=name+'.'+origname,
650 tofile=name+'.'+newname)))
652 if self.rechazar_si_falla:
654 comando_ejecutado.exito = False
655 comando_ejecutado.observaciones += _(u'%s no coincide con lo '
656 u'esperado (archivo "%s.diff").\n') % (longname, name)
657 log.debug(_(u'%s no coincide con lo esperado (archivo "%s.diff")'),
659 htmldiff = HtmlDiff().make_file(orig, new,
660 fromdesc=name+'.'+origname, todesc=name+'.'+newname,
661 context=True, numlines=3)
662 zip_out.writestr(name + '.diff', udiff)
663 zip_out.writestr(name + '.diff.html', htmldiff)
669 zip = ZipFile(buffer, 'w')
670 # Comparamos stdout/stderr
671 if self.STDOUTERR in a_comparar:
672 a_comparar.remove(self.STDOUTERR)
673 diff('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
674 zip_a_comparar, zip, self.STDOUTERR,
675 _(u'La salida estándar y de error combinada'))
677 if self.STDOUT in a_comparar:
678 a_comparar.remove(self.STDOUT)
679 diff('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
680 zip_a_comparar, zip, self.STDOUT, _(u'La salida estándar'))
681 if self.STDERR in a_comparar:
682 a_comparar.remove(self.STDERR)
683 diff('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
684 zip_a_comparar, zip, self.STDERR, _(u'La salida de error'))
687 if not os.path.exists(join(path, f)):
688 if self.rechazar_si_falla:
690 comando_ejecutado.exito = False
691 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
692 u'"%s" para comparar pero no fue encontrado') % f
693 log.debug(_(u'Se esperaba un archivo "%s" para comparar pero '
694 u'no fue encontrado'), f)
696 diff(join(path, f), zip_a_comparar, zip, f)
698 comando_ejecutado.diferencias = buffer.getvalue()
699 if comando_ejecutado.exito is None:
700 comando_ejecutado.exito = True
701 elif self.terminar_si_falla:
702 raise ExecutionFailure(self)
704 ComandoPrueba.ejecutar = ejecutar_comando_prueba