CWSRequests.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. #!/usr/bin/env python3
  2. # Contest Management System - http://cms-dev.github.io/
  3. # Copyright © 2010-2012 Giovanni Mascellani <mascellani@poisson.phc.unipi.it>
  4. # Copyright © 2010-2018 Stefano Maggiolo <s.maggiolo@gmail.com>
  5. # Copyright © 2010-2012 Matteo Boscariol <boscarim@hotmail.com>
  6. # Copyright © 2014 Artem Iglikov <artem.iglikov@gmail.com>
  7. # Copyright © 2016 Luca Wehrstedt <luca.wehrstedt@gmail.com>
  8. # Copyright © 2017 Luca Chiodini <luca@chiodini.org>
  9. #
  10. # This program is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU Affero General Public License as
  12. # published by the Free Software Foundation, either version 3 of the
  13. # License, or (at your option) any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU Affero General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU Affero General Public License
  21. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. import logging
  23. import os
  24. import random
  25. import re
  26. import tempfile
  27. from urllib.parse import parse_qs, urlsplit
  28. from cms import config
  29. from cms.grading.languagemanager import filename_to_language
  30. from cmscommon.crypto import decrypt_number
  31. from cmstestsuite.web import GenericRequest, LoginRequest
  32. logger = logging.getLogger(__name__)
  33. class CWSLoginRequest(LoginRequest):
  34. def test_success(self):
  35. if not LoginRequest.test_success(self):
  36. return False
  37. if self.redirected_to.rstrip("/") != self.base_url.rstrip("/"):
  38. return False
  39. return True
  40. class HomepageRequest(GenericRequest):
  41. """Load the main page of CWS.
  42. """
  43. def __init__(self, browser, username, loggedin, base_url=None):
  44. GenericRequest.__init__(self, browser, base_url)
  45. self.url = self.base_url
  46. self.username = username
  47. self.loggedin = loggedin
  48. def describe(self):
  49. return "check the main page"
  50. def test_success(self):
  51. if not GenericRequest.test_success(self):
  52. return False
  53. username_re = re.compile(self.username)
  54. if self.loggedin:
  55. if username_re.search(self.res_data) is None:
  56. return False
  57. else:
  58. if username_re.search(self.res_data) is not None:
  59. return False
  60. return True
  61. class TaskRequest(GenericRequest):
  62. """Load a task page in CWS.
  63. """
  64. def __init__(self, browser, task_id, base_url=None):
  65. GenericRequest.__init__(self, browser, base_url)
  66. self.url = "%s/tasks/%s/description" % (self.base_url, task_id)
  67. self.task_id = task_id
  68. def describe(self):
  69. return "load page for task %s (%s)" % (self.task_id, self.url)
  70. class TaskStatementRequest(GenericRequest):
  71. """Load a task statement in CWS.
  72. """
  73. def __init__(self, browser, task_id, language_code, base_url=None):
  74. GenericRequest.__init__(self, browser, base_url)
  75. self.url = "%s/tasks/%s/statements/%s" % (self.base_url,
  76. task_id, language_code)
  77. self.task_id = task_id
  78. def describe(self):
  79. return "load statement for task %s (%s)" % (self.task_id, self.url)
  80. def specific_info(self):
  81. return '\nNO DATA DUMP FOR TASK STATEMENTS\n'
  82. class SubmitRequest(GenericRequest):
  83. """Submit a solution in CWS.
  84. """
  85. def __init__(self, browser, task, submission_format,
  86. filenames, language=None, base_url=None):
  87. GenericRequest.__init__(self, browser, base_url)
  88. self.url = "%s/tasks/%s/submit" % (self.base_url, task[1])
  89. self.task = task
  90. self.submission_format = submission_format
  91. self.filenames = filenames
  92. self.data = {}
  93. # If not passed, try to recover the language from the filenames.
  94. if language is None:
  95. for filename in filenames:
  96. lang = filename_to_language(filename)
  97. if lang is not None:
  98. language = lang.name
  99. break
  100. # Only send the language in the request if not None.
  101. if language is not None:
  102. self.data = {"language": language}
  103. def _prepare(self):
  104. GenericRequest._prepare(self)
  105. self.files = list(zip(self.submission_format, self.filenames))
  106. def describe(self):
  107. return "submit sources %s for task %s (ID %d) %s" % \
  108. (repr(self.filenames), self.task[1], self.task[0], self.url)
  109. def specific_info(self):
  110. return 'Task: %s (ID %d)\nFile: %s\n' % \
  111. (self.task[1], self.task[0], repr(self.filenames)) + \
  112. GenericRequest.specific_info(self)
  113. def test_success(self):
  114. if not GenericRequest.test_success(self):
  115. return False
  116. return self.get_submission_id() is not None
  117. def get_submission_id(self):
  118. # Only valid after self.execute()
  119. # Parse submission ID out of redirect.
  120. if self.redirected_to is None:
  121. return None
  122. query = parse_qs(urlsplit(self.redirected_to).query)
  123. if "submission_id" not in query or len(query["submission_id"]) != 1:
  124. logger.warning("Redirected to an unexpected page: `%s'",
  125. self.redirected_to)
  126. return None
  127. try:
  128. submission_id = decrypt_number(query["submission_id"][0],
  129. config.secret_key)
  130. except Exception:
  131. logger.warning("Unable to decrypt submission id from page: `%s'",
  132. self.redirected_to)
  133. return None
  134. return submission_id
  135. class SubmitUserTestRequest(GenericRequest):
  136. """Submit a user test in CWS."""
  137. def __init__(self, browser, task, submission_format,
  138. filenames, language=None, base_url=None):
  139. GenericRequest.__init__(self, browser, base_url)
  140. self.url = "%s/tasks/%s/test" % (self.base_url, task[1])
  141. self.task = task
  142. self.submission_format = submission_format
  143. self.filenames = filenames
  144. self.data = {}
  145. # If not passed, try to recover the language from the filenames.
  146. if language is None:
  147. for filename in filenames:
  148. lang = filename_to_language(filename)
  149. if lang is not None:
  150. language = lang.name
  151. break
  152. # Only send the language in the request if not None.
  153. if language is not None:
  154. self.data = {"language": language}
  155. def _prepare(self):
  156. GenericRequest._prepare(self)
  157. # Let's generate an arbitrary input file.
  158. # TODO: delete this file once we're done with it.
  159. _, temp_filename = tempfile.mkstemp()
  160. self.files = \
  161. list(zip(self.submission_format, self.filenames)) + \
  162. [("input", temp_filename)]
  163. def describe(self):
  164. return "submit user test %s for task %s (ID %d) %s" % \
  165. (repr(self.filenames), self.task[1], self.task[0], self.url)
  166. def specific_info(self):
  167. return 'Task: %s (ID %d)\nFile: %s\n' % \
  168. (self.task[1], self.task[0], repr(self.filenames)) + \
  169. GenericRequest.specific_info(self)
  170. def test_success(self):
  171. if not GenericRequest.test_success(self):
  172. return False
  173. return self.get_user_test_id() is not None
  174. def get_user_test_id(self):
  175. # Only valid after self.execute()
  176. # Parse submission ID out of redirect.
  177. if self.redirected_to is None:
  178. return None
  179. query = parse_qs(urlsplit(self.redirected_to).query)
  180. if "user_test_id" not in query or len(query["user_test_id"]) != 1:
  181. logger.warning("Redirected to an unexpected page: `%s'",
  182. self.redirected_to)
  183. return None
  184. try:
  185. user_test_id = decrypt_number(query["user_test_id"][0],
  186. config.secret_key)
  187. except Exception:
  188. logger.warning("Unable to decrypt user test id from page: `%s'",
  189. self.redirected_to)
  190. return None
  191. return user_test_id
  192. class TokenRequest(GenericRequest):
  193. """Release test a submission.
  194. """
  195. def __init__(self, browser, task, submission_num, base_url=None):
  196. GenericRequest.__init__(self, browser, base_url)
  197. self.url = "%s/tasks/%s/submissions/%s/token" % (self.base_url,
  198. task[1],
  199. submission_num)
  200. self.task = task
  201. self.submission_num = submission_num
  202. self.data = {}
  203. def describe(self):
  204. return "release test the %s-th submission for task %s (ID %d)" % \
  205. (self.submission_num, self.task[1], self.task[0])
  206. def specific_info(self):
  207. return 'Task: %s (ID %d)\nSubmission: %s\n' % \
  208. (self.task[1], self.task[0], self.submission_num) + \
  209. GenericRequest.specific_info(self)
  210. class SubmitRandomRequest(GenericRequest):
  211. """Submit a solution in CWS.
  212. """
  213. def __init__(self, browser, task, base_url=None,
  214. submissions_path=None):
  215. GenericRequest.__init__(self, browser, base_url)
  216. self.url = "%s/tasks/%s/submit" % (self.base_url, task[1])
  217. self.task = task
  218. self.submissions_path = submissions_path
  219. self.data = {}
  220. def _prepare(self):
  221. """Select a random solution and prepare it for submission.
  222. If task/ is the task directory, it might contain files (only
  223. if the submission format is with a single file) and
  224. directory. If it contains a file, it is assumed that it is the
  225. only element in the submission format, and is the basename
  226. without extension of the file. If it is a directory, all files
  227. inside are assumed to be part of the submission format with
  228. their basenames without extension.
  229. """
  230. GenericRequest._prepare(self)
  231. # Select a random directory or file inside the task directory.
  232. task_path = os.path.join(self.submissions_path, self.task[1])
  233. sources = os.listdir(task_path)
  234. source = random.choice(sources)
  235. lang = filename_to_language(source)
  236. if lang is not None:
  237. self.data["language"] = lang.name
  238. self.source_path = os.path.join(task_path, source)
  239. # Compose the submission format
  240. self.files = []
  241. if os.path.isdir(self.source_path):
  242. submission_formats = os.listdir(self.source_path)
  243. self.files = [('%s.%%l' % (os.path.splitext(sf)[0]),
  244. os.path.join(self.source_path, sf))
  245. for sf in submission_formats]
  246. else:
  247. submission_format = os.path.splitext(source)[0]
  248. self.files = [('%s.%%l' % (submission_format), self.source_path)]
  249. def describe(self):
  250. return "submit source %s for task %s (ID %d) %s" % \
  251. (self.source_path, self.task[1], self.task[0], self.url)
  252. def specific_info(self):
  253. return 'Task: %s (ID %d)\nFile: %s\n' % \
  254. (self.task[1], self.task[0], self.source_path) + \
  255. GenericRequest.specific_info(self)