]> git.llucax.com Git - software/sercom.git/blob - sercom/tester.py
7638fc80682fb3e5ccee150fa806d053b7b54335
[software/sercom.git] / sercom / tester.py
1 # vim: set et sw=4 sts=4 encoding=utf-8 foldmethod=marker:
2
3 from sercom import model as mod
4 import zipfile as zf
5 import cStringIO as sio
6 import shutil as shu
7 import datetime as dt
8 from os import path as osp
9 import os
10
11 import logging
12 log = logging.getLogger('sercom.tester')
13
14 #from Queue import Queue
15 #queue = Queue()
16
17 class Error(StandardError): pass
18
19 class ExecutionFailure(Error, RuntimeError): pass
20
21 class RsyncError(Error, EnvironmentError): pass
22
23 error_interno = u'\n**Error interno al preparar la entrega.**'
24
25 def unzip(bytes, dst): # {{{
26     log.debug(_(u'Intentando descomprimir en %s') % dst)
27     if bytes is None:
28         return
29     zfile = zf.ZipFile(sio.StringIO(bytes), 'r')
30     for f in zfile.namelist():
31         if f.endswith(os.sep):
32             log.debug(_(u'Creando directorio %s') % f)
33             os.mkdir(osp.join(dst, f))
34         else:
35             log.debug(_(u'Descomprimiendo archivo %s') % f)
36             file(osp.join(dst, f), 'w').write(zfile.read(f))
37 #}}}
38
39 class Tester(object): #{{{
40
41     def __init__(self, name, path, home, queue): #{{{ y properties
42         self.name = name
43         self.path = path
44         self.home = home
45         self.queue = queue
46
47     @property
48     def build_path(self):
49         return osp.join(self.chroot, self.home, 'build')
50
51     @property
52     def test_path(self):
53         return osp.join(self.chroot, self.home, 'test')
54
55     @property
56     def chroot(self):
57         return osp.join(self.path, 'chroot_' + self.name)
58     #}}}
59
60     @property
61     def orig_chroot(self):
62         return osp.join(self.path, 'chroot')
63
64     def run(self): #{{{
65         entrega_id = self.queue.get() # blocking
66         while entrega_id is not None:
67             entrega = mod.Entrega.get(entrega_id)
68             log.debug(_(u'Nueva entrega para probar en tester %s: %s')
69                 % (self.name, entrega))
70             self.test(entrega)
71             log.debug(_(u'Fin de pruebas de: %s') % entrega)
72             entrega_id = self.queue.get() # blocking
73     #}}}
74
75     def test(self, entrega): #{{{
76         log.debug(_(u'Tester.test(entrega=%s)') % entrega)
77         entrega.inicio_tareas = dt.datetime.now()
78         try:
79             try:
80                 self.setup_chroot(entrega)
81                 self.ejecutar_tareas_fuente(entrega)
82                 self.ejecutar_tareas_prueba(entrega)
83                 self.clean_chroot(entrega)
84             except ExecutionFailure, e:
85                 entrega.correcta = False
86                 log.debug(_(u'Entrega incorrecta: %s') % entrega)
87             except Exception, e:
88                 entrega.observaciones += error_interno
89                 log.exception(_(u'Hubo una excepción inesperada: %s') % e)
90             except:
91                 entrega.observaciones += error_interno
92                 log.exception(_(u'Hubo una excepción inesperada desconocida'))
93             else:
94                 entrega.correcta = True
95                 log.debug(_(u'Entrega correcta: %s') % entrega)
96         finally:
97             entrega.fin_tareas = dt.datetime.now()
98     #}}}
99
100     def setup_chroot(self, entrega): #{{{ y clean_chroot()
101         log.debug(_(u'Tester.setup_chroot(entrega=%s)') % entrega)
102         rsync = 'rsync --stats --itemize-changes --human-readable --archive ' \
103             '--acls --delete-during --force' # TODO config
104         orig_chroot = osp.join(self.orig_chroot, '')
105         cmd = '%s %s %s' % (rsync, orig_chroot, self.chroot)
106         log.debug(_(u'Ejecutando: %s') % cmd)
107         ret = os.system(cmd)
108         if ret != 0:
109             entrega.observaciones += error_interno
110             errstr = _(u'No se pudo hacer rsync al chroot para la prueba,' \
111                 u'falló el comando: %s (con código de error %d)') % (cmd, ret)
112             log.error(errstr)
113             raise RsyncError(errstr)
114         try:
115             unzip(entrega.archivos, self.build_path)
116         except zf.BadZipfile:
117             entrega.correcta = False
118             entrega.observaciones += error_interno
119             log.error(_(u'El archivo adjunto no está en formato ZIP'))
120             raise
121         except IOError, e:
122             entrega.observaciones += error_interno
123             log.error(_(u'Error de IO al descromprimir archivos del ZIP: %s')
124                 % e)
125             raise
126
127     def clean_chroot(self, entrega):
128         log.debug(_(u'Tester.clean_chroot(entrega=%s)') % entrega)
129         pass # Se limpia con el próximo rsync
130     #}}}
131
132     def ejecutar_tareas_fuente(self, entrega): #{{{ y tareas_prueba
133         log.debug(_(u'Tester.ejecutar_tareas_fuente(entrega=%s)') % entrega)
134         tareas = [t for t in entrega.instancia.ejercicio.enunciado.tareas
135                     if isinstance(t, mod.TareaFuente)]
136         for tarea in tareas:
137             tarea.ejecutar(self.build_path, entrega)
138
139     def ejecutar_tareas_prueba(self, entrega):
140         log.debug(_(u'Tester.ejecutar_tareas_prueba(entrega=%s)') % entrega)
141         for caso in entrega.instancia.ejercicio.enunciado.casos_de_prueba:
142             caso.ejecutar(self.test_path, entrega)
143     #}}}
144
145 #}}}
146
147 def ejecutar_caso_de_prueba(self, path, entrega): #{{{
148     log.debug(_(u'CasoDePrueba.ejecutar(path=%s, entrega=%s)')
149         % (path, entrega))
150     tareas = [t for t in entrega.instancia.ejercicio.enunciado.tareas
151                 if isinstance(t, mod.TareaPrueba)]
152     prueba = entrega.add_prueba(self)
153     try:
154         try:
155             for tarea in tareas:
156                 tarea.ejecutar(path, prueba)
157         except ExecutionFailure, e:
158             prueba.pasada = False
159             if self.rechazar_si_falla:
160                 entrega.exito = False
161             if self.terminar_si_falla:
162                 raise ExecutionError(e.comando, e.tarea, prueba)
163         else:
164             prueba.pasada = True
165     finally:
166         prueba.fin = dt.datetime.now()
167 mod.CasoDePrueba.ejecutar = ejecutar_caso_de_prueba
168 #}}}
169
170 def ejecutar_tarea_fuente(self, path, entrega): #{{{
171     log.debug(_(u'TareaFuente.ejecutar(path=%s, entrega=%s)') % (path, entrega))
172     try:
173         for cmd in self.comandos:
174             cmd.ejecutar(path, entrega)
175     except ExecutionFailure, e:
176         if self.rechazar_si_falla:
177             entrega.exito = False
178         if self.terminar_si_falla:
179             raise ExecutionError(e.comando, tarea)
180 mod.TareaFuente.ejecutar = ejecutar_tarea_fuente
181 #}}}
182
183 def ejecutar_tarea_prueba(self, path, prueba): #{{{
184     log.debug(_(u'TareaPrueba.ejecutar(path=%s, prueba=%s)') % (path, prueba))
185     try:
186         for cmd in self.comandos:
187             cmd.ejecutar(path, prueba)
188     except ExecutionFailure, e:
189         if self.rechazar_si_falla:
190             prueba.exito = False
191         if self.terminar_si_falla:
192             raise ExecutionError(e.comando, tarea)
193 mod.TareaPrueba.ejecutar = ejecutar_tarea_prueba
194 #}}}
195
196 def ejecutar_comando_fuente(self, path, entrega): #{{{
197     log.debug(_(u'ComandoFuente.ejecutar(path=%s, entrega=%s)')
198         % (path, entrega))
199     unzip(self.archivos_entrada, path) # TODO try/except
200     comando_ejecutado = entrega.add_comando_ejecutado(self)
201     # TODO ejecutar en chroot (path)
202     comando_ejecutado.fin = dt.datetime.now()
203 #    if no_anda_ejecucion: # TODO
204 #        comando_ejecutado.exito = False
205 #        comando_ejecutado.observaciones += 'No anduvo xxx' # TODO mas info
206 #        if self.rechazar_si_falla:
207 #            entrega.exito = False
208 #        if self.terminar_si_falla: # TODO
209 #            raise ExecutionFailure(self)
210     # XXX ESTO EN REALIDAD EN COMANDOS FUENTE NO IRIA
211     # XXX SOLO HABRÍA QUE CAPTURAR stdout/stderr
212     # XXX PODRIA TENER ARCHIVOS DE SALIDA PERO SOLO PARA MOSTRAR COMO RESULTADO
213 #    for archivo in self.archivos_salida:
214 #        pass # TODO hacer diff
215 #    if archivos_mal: # TODO
216 #        comando_ejecutado.exito = False
217 #        comando_ejecutado.observaciones += 'No anduvo xxx' # TODO mas info
218 #        if self.rechazar_si_falla:
219 #            entrega.exito = False
220 #        if self.terminar_si_falla: # TODO
221 #            raise ExecutionFailure(self)
222 #    else:
223 #        comando_ejecutado.exito = True
224 #        comando_ejecutado.observaciones += 'xxx OK' # TODO
225     comando_ejecutado.exito = True
226     comando_ejecutado.observaciones += 'xxx OK' # TODO
227 mod.ComandoFuente.ejecutar = ejecutar_comando_fuente
228 #}}}
229
230 def ejecutar_comando_prueba(self, path, prueba): #{{{
231     log.debug(_(u'ComandoPrueba.ejecutar(path=%s, prueba=%s)')
232         % (path, prueba))
233     shu.rmtree(path)
234     os.mkdir(path)
235     unzip(prueba.caso_de_prueba.archivos_entrada, path) # TODO try/except
236     unzip(self.archivos_entrada, path) # TODO try/except
237     comando_ejecutado = prueba.add_comando_ejecutado(self)
238     # TODO ejecutar en chroot (path)
239     comando_ejecutado.fin = dt.datetime.now()
240 #    if no_anda_ejecucion: # TODO
241 #        comando_ejecutado.exito = False
242 #        comando_ejecutado.observaciones += 'No anduvo xxx' # TODO
243 #        if self.rechazar_si_falla:
244 #            entrega.exito = False
245 #        if self.terminar_si_falla: # TODO
246 #            raise ExecutionFailure(self) # TODO info de error
247 #    for archivo in self.archivos_salida:
248 #        pass # TODO hacer diff
249 #    if archivos_mal: # TODO
250 #        comando_ejecutado.exito = False
251 #        comando_ejecutado.observaciones += 'No anduvo xxx' # TODO
252 #        if self.rechazar_si_falla:
253 #            entrega.exito = False
254 #        if self.terminar_si_falla: # TODO
255 #            raise ExecutionFailure(comando=self) # TODO info de error
256 #    else:
257 #        comando_ejecutado.exito = True
258 #        comando_ejecutado.observaciones += 'xxx OK' # TODO
259     comando_ejecutado.exito = True
260     comando_ejecutado.observaciones += 'xxx OK' # TODO
261 mod.ComandoPrueba.ejecutar = ejecutar_comando_prueba
262 #}}}
263