Commit 77dc4f5e21a0e7d35b6205d029473642b6a9d2f3

Authored by Miguel Barão
1 parent bab5f360
Exists in master and in 1 other branch dev

refactor checking questions.

add check for correction of code and textarea.
1 1
2 # BUGS 2 # BUGS
3 3
  4 +- JOBE correct async
4 - em caso de timeout na submissão (e.g. JOBE ou script nao responde) a correcção não termina e o teste não é guardado. 5 - em caso de timeout na submissão (e.g. JOBE ou script nao responde) a correcção não termina e o teste não é guardado.
5 - QuestionCode falta reportar nos comments os vários erros que podem ocorrer (timeout, etc) 6 - QuestionCode falta reportar nos comments os vários erros que podem ocorrer (timeout, etc)
6 - permitir remover alunos que estão online para poderem comecar de novo. 7 - permitir remover alunos que estão online para poderem comecar de novo.
perguntations/__init__.py
@@ -32,7 +32,7 @@ proof of submission and for review. @@ -32,7 +32,7 @@ proof of submission and for review.
32 ''' 32 '''
33 33
34 APP_NAME = 'perguntations' 34 APP_NAME = 'perguntations'
35 -APP_VERSION = '2020.11.dev3' 35 +APP_VERSION = '2020.11.dev4'
36 APP_DESCRIPTION = __doc__ 36 APP_DESCRIPTION = __doc__
37 37
38 __author__ = 'Miguel Barão' 38 __author__ = 'Miguel Barão'
perguntations/testfactory.py
@@ -43,7 +43,7 @@ class TestFactory(dict): @@ -43,7 +43,7 @@ class TestFactory(dict):
43 super().__init__({ # defaults 43 super().__init__({ # defaults
44 'title': '', 44 'title': '',
45 'show_points': True, 45 'show_points': True,
46 - 'scale': None, # or [0, 20] 46 + 'scale': None,
47 'duration': 0, # 0=infinite 47 'duration': 0, # 0=infinite
48 'autosubmit': False, 48 'autosubmit': False,
49 'debug': False, 49 'debug': False,
@@ -66,12 +66,10 @@ class TestFactory(dict): @@ -66,12 +66,10 @@ class TestFactory(dict):
66 len(qrefs), len(self["questions"])) 66 len(qrefs), len(self["questions"]))
67 67
68 # --- load and build question factories 68 # --- load and build question factories
69 - self.question_factory = {} 69 + self['question_factory'] = {}
70 70
71 - counter = 1  
72 for file in self["files"]: 71 for file in self["files"]:
73 fullpath = path.normpath(path.join(self["questions_dir"], file)) 72 fullpath = path.normpath(path.join(self["questions_dir"], file))
74 - (dirname, filename) = path.split(fullpath)  
75 73
76 logger.info('Loading "%s"...', fullpath) 74 logger.info('Loading "%s"...', fullpath)
77 questions = load_yaml(fullpath) # , default=[]) 75 questions = load_yaml(fullpath) # , default=[])
@@ -88,8 +86,8 @@ class TestFactory(dict): @@ -88,8 +86,8 @@ class TestFactory(dict):
88 logger.warning('Missing ref set to "%s"', question["ref"]) 86 logger.warning('Missing ref set to "%s"', question["ref"])
89 87
90 # check for duplicate refs 88 # check for duplicate refs
91 - if question['ref'] in self.question_factory:  
92 - other = self.question_factory[question['ref']] 89 + if question['ref'] in self['question_factory']:
  90 + other = self['question_factory'][question['ref']]
93 otherfile = path.join(other.question['path'], 91 otherfile = path.join(other.question['path'],
94 other.question['filename']) 92 other.question['filename'])
95 msg = (f'Duplicate reference "{question["ref"]}" in files ' 93 msg = (f'Duplicate reference "{question["ref"]}" in files '
@@ -98,36 +96,23 @@ class TestFactory(dict): @@ -98,36 +96,23 @@ class TestFactory(dict):
98 96
99 # make factory only for the questions used in the test 97 # make factory only for the questions used in the test
100 if question['ref'] in qrefs: 98 if question['ref'] in qrefs:
101 - # question.setdefault('type', 'information')  
102 - question.update({  
103 - 'filename': filename,  
104 - 'path': dirname,  
105 - 'index': i # position in the file, 0 based  
106 - }) 99 + question.update(zip(('path', 'filename', 'index'),
  100 + path.split(fullpath) + (i,)))
107 if question['type'] == 'code' and 'server' not in question: 101 if question['type'] == 'code' and 'server' not in question:
108 try: 102 try:
109 question['server'] = self['jobe_server'] 103 question['server'] = self['jobe_server']
110 except KeyError as exc: 104 except KeyError as exc:
111 msg = f'Missing JOBE server in "{question["ref"]}"' 105 msg = f'Missing JOBE server in "{question["ref"]}"'
112 - raise TestFactoryException(msg)  
113 -  
114 - self.question_factory[question['ref']] = QFactory(question)  
115 -  
116 - # check if all the questions can be correctly generated  
117 - # TODO and corrected  
118 - try:  
119 - self.question_factory[question['ref']].generate()  
120 - except Exception as exc:  
121 - msg = f'Failed to generate "{question["ref"]}"'  
122 - raise TestFactoryException(msg) from exc  
123 - else:  
124 - logger.info('%4d. "%s" Ok.', counter, question["ref"])  
125 - counter += 1  
126 -  
127 - qmissing = qrefs.difference(set(self.question_factory.keys())) 106 + raise TestFactoryException(msg) from exc
  107 +
  108 + self['question_factory'][question['ref']] = QFactory(question)
  109 +
  110 + qmissing = qrefs.difference(set(self['question_factory'].keys()))
128 if qmissing: 111 if qmissing:
129 raise TestFactoryException(f'Could not find questions {qmissing}.') 112 raise TestFactoryException(f'Could not find questions {qmissing}.')
130 113
  114 + self.check_questions()
  115 +
131 logger.info('Test factory ready. No errors found.') 116 logger.info('Test factory ready. No errors found.')
132 117
133 118
@@ -231,6 +216,32 @@ class TestFactory(dict): @@ -231,6 +216,32 @@ class TestFactory(dict):
231 self.check_grade_scaling() 216 self.check_grade_scaling()
232 217
233 # ------------------------------------------------------------------------ 218 # ------------------------------------------------------------------------
  219 + def check_questions(self) -> None:
  220 + '''
  221 + checks if questions can be correctly generated and corrected
  222 + '''
  223 + logger.info('Checking if questions can be generated and corrected...')
  224 + for i, (qref, qfact) in enumerate(self['question_factory'].items()):
  225 + logger.info('%4d. %s:', i, qref)
  226 + try:
  227 + question = qfact.generate()
  228 + except Exception as exc:
  229 + msg = f'Failed to generate "{qref}"'
  230 + raise TestFactoryException(msg) from exc
  231 + else:
  232 + logger.info(' generate Ok')
  233 +
  234 + if question['type'] in ('code', 'textarea'):
  235 + try:
  236 + question.set_answer('')
  237 + question.correct()
  238 + except Exception as exc:
  239 + msg = f'Failed to correct "{qref}"'
  240 + raise TestFactoryException(msg) from exc
  241 + else:
  242 + logger.info(' correct Ok')
  243 +
  244 + # ------------------------------------------------------------------------
234 async def generate(self): 245 async def generate(self):
235 ''' 246 '''
236 Given a dictionary with a student dict {'name':'john', 'number': 123} 247 Given a dictionary with a student dict {'name':'john', 'number': 123}
@@ -250,7 +261,7 @@ class TestFactory(dict): @@ -250,7 +261,7 @@ class TestFactory(dict):
250 for qref in qrefs: 261 for qref in qrefs:
251 # generate instance of question 262 # generate instance of question
252 try: 263 try:
253 - question = await self.question_factory[qref].gen_async() 264 + question = await self['question_factory'][qref].gen_async()
254 except QuestionException: 265 except QuestionException:
255 logger.error('Can\'t generate question "%s". Skipping.', qref) 266 logger.error('Can\'t generate question "%s". Skipping.', qref)
256 nerr += 1 267 nerr += 1