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 if self.archivos_a_comparar:
525 zip_a_comparar = ZipFile(StringIO(self.archivos_a_comparar), 'r')
526 a_comparar = set(zip_a_comparar.namelist())
528 zip_a_comparar = None
529 a_comparar = frozenset()
530 a_usar = frozenset(a_guardar | a_comparar)
531 if self.STDOUTERR in a_usar:
532 options['stdout'] = file('%s.%s.stdouterr' % (basetmp,
533 comando_ejecutado.id), 'w')
534 options['stderr'] = sp.STDOUT
536 if self.STDOUT in a_usar:
537 options['stdout'] = file('%s.%s.stdout' % (basetmp,
538 comando_ejecutado.id), 'w')
540 options['preexec_fn'].close_stdout = True
541 if self.STDERR in a_usar:
542 options['stderr'] = file('%s.%s.stderr' % (basetmp,
543 comando_ejecutado.id), 'w')
545 options['preexec_fn'].close_stderr = True
546 comando = self.comando + ' ' + prueba.caso_de_prueba.comando # FIXME Esto es propio de ComandoPrueba
547 log.debug(_(u'Ejecutando como root: %s'), comando)
548 os.seteuid(0) # Dios! (para chroot)
552 proc = sp.Popen(comando, **options)
554 os.setegid(user_info.gid) # Mortal de nuevo
555 os.seteuid(user_info.uid)
556 log.debug(_(u'Usuario y grupo efectivos cambiados a %s:%s (%s:%s)'),
557 user_info.user, user_info.group, user_info.uid, user_info.gid)
559 if hasattr(e, 'child_traceback'):
560 log.error(_(u'Error en el hijo: %s'), e.child_traceback)
562 proc.wait() #TODO un sleep grande nos caga todo, ver sercom viejo
563 comando_ejecutado.fin_tareas = datetime.now() # TODO debería rodear solo la ejecución del comando
564 retorno = self.retorno
565 if retorno == self.RET_PRUEBA: # FIXME Esto es propio de ComandoPrueba
566 retorno = prueba.caso_de_prueba.retorno # FIXME Esto es propio de ComandoPrueba
567 if retorno != self.RET_ANY:
568 if retorno == self.RET_FAIL:
569 if proc.returncode == 0:
570 if self.rechazar_si_falla:
572 comando_ejecutado.exito = False
573 comando_ejecutado.observaciones += _(u'Se esperaba que el '
574 u'programa termine con un error (código de retorno '
575 u'distinto de 0) pero terminó bien (código de retorno '
577 log.debug(_(u'Se esperaba que el programa termine '
578 u'con un error (código de retorno distinto de 0) pero '
579 u'terminó bien (código de retorno 0).\n'))
580 elif retorno != proc.returncode:
581 if self.rechazar_si_falla:
583 comando_ejecutado.exito = False
584 if proc.returncode < 0:
585 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
586 u'con un código de retorno %s pero se obtuvo una señal %s '
587 u'(%s).\n') % (retorno, -proc.returncode, -proc.returncode) # TODO poner con texto
588 log.debug(_(u'Se esperaba terminar con un código '
589 u'de retorno %s pero se obtuvo una señal %s (%s).\n'),
590 retorno, -proc.returncode, -proc.returncode)
592 comando_ejecutado.observaciones += _(u'Se esperaba terminar '
593 u'con un código de retorno %s pero se obtuvo %s.\n') \
594 % (retorno, proc.returncode)
595 log.debug(_(u'Se esperaba terminar con un código de retorno '
596 u'%s pero se obtuvo %s.\n'), retorno, proc.returncode)
597 if comando_ejecutado.exito is None:
598 log.debug(_(u'Código de retorno OK'))
601 zip = ZipFile(buffer, 'w')
602 # Guardamos stdout/stderr
603 if self.STDOUTERR in a_guardar:
604 a_guardar.remove(self.STDOUTERR)
605 zip.write('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
608 if self.STDOUT in a_guardar:
609 a_guardar.remove(self.STDOUT)
610 zip.write('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
612 if self.STDERR in a_guardar:
613 a_guardar.remove(self.STDERR)
614 zip.write('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
618 if not os.path.exists(join(path, f)):
619 if self.rechazar_si_falla:
621 comando_ejecutado.exito = False
622 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
623 u'"%s" para guardar pero no fue encontrado.\n') % f
624 log.debug(_(u'Se esperaba un archivo "%s" para guardar pero '
625 u'no fue encontrado'), f)
627 zip.write(join(path, f), f)
629 comando_ejecutado.archivos = buffer.getvalue()
630 def diff(new, zip_in, zip_out, name, longname=None, origname='correcto',
631 newname='entregado'):
634 new = file(new, 'r').readlines()
635 orig = zip_in.read(name).split('\n')
636 udiff = ''.join(list(unified_diff(orig, new, fromfile=name+'.'+origname,
637 tofile=name+'.'+newname)))
639 if self.rechazar_si_falla:
641 comando_ejecutado.exito = False
642 comando_ejecutado.observaciones += _(u'%s no coincide con lo '
643 u'esperado (archivo "%s.diff").\n') % (longname, name)
644 log.debug(_(u'%s no coincide con lo esperado (archivo "%s.diff")'),
646 htmldiff = HtmlDiff().make_file(orig, new,
647 fromdesc=name+'.'+origname, todesc=name+'.'+newname,
648 context=True, numlines=3)
649 zip_out.writestr(name + '.diff', udiff)
650 zip_out.writestr(name + '.diff.html', htmldiff)
656 zip = ZipFile(buffer, 'w')
657 # Comparamos stdout/stderr
658 if self.STDOUTERR in a_comparar:
659 a_comparar.remove(self.STDOUTERR)
660 diff('%s.%s.stdouterr' % (basetmp, comando_ejecutado.id),
661 zip_a_comparar, zip, self.STDOUTERR,
662 _(u'La salida estándar y de error combinada'))
664 if self.STDOUT in a_comparar:
665 a_comparar.remove(self.STDOUT)
666 diff('%s.%s.stdout' % (basetmp, comando_ejecutado.id),
667 zip_a_comparar, zip, self.STDOUT, _(u'La salida estándar'))
668 if self.STDERR in a_comparar:
669 a_comparar.remove(self.STDERR)
670 diff('%s.%s.stderr' % (basetmp, comando_ejecutado.id),
671 zip_a_comparar, zip, self.STDERR, _(u'La salida de error'))
674 if not os.path.exists(join(path, f)):
675 if self.rechazar_si_falla:
677 comando_ejecutado.exito = False
678 comando_ejecutado.observaciones += _(u'Se esperaba un archivo '
679 u'"%s" para comparar pero no fue encontrado') % f
680 log.debug(_(u'Se esperaba un archivo "%s" para comparar pero '
681 u'no fue encontrado'), f)
683 diff(join(path, f), zip_a_comparar, zip, f)
685 comando_ejecutado.diferencias = buffer.getvalue()
686 if comando_ejecutado.exito is None:
687 comando_ejecutado.exito = True
688 elif self.terminar_si_falla:
689 raise ExecutionFailure(self)
691 ComandoPrueba.ejecutar = ejecutar_comando_prueba