def replymail_error( msg, text, CC_to_admin=False, maxsend=None ): """Takes message 'msg' and composes a reply to the sender with body 'text'. If CC_to_admin is true, the message will be sent to the sysadmin as well. The idea of maxsend is that if maxsend is given, we will only attempt maxsend time to deliver email to the same email address. This could be useful if we engage in infinite loops with external spam. The code isn't written for this yet. """ real_name, email_addr = email.Utils.parseaddr( msg["From"] ) log_global.debug("in replymail_error (to %s, subj: %s)" % (real_name,msg["Subject"])) text = "Dear "+str(real_name)+" ("+str(email_addr)+"),\n\n" + \ "An error occured while parsing your email with subject\n" + \ repr(msg["Subject"])+" received at "+time.asctime()+":\n\n"+ \ text if CC_to_admin: subject = "["+conf.ModulecodeSubjectLine+"-admin] submission error, "+time.ctime(time.time())+", "+str(msg["Subject"]) enqueue_outgoing_mails.send_text_message( conf.SysadminEmail, conf.ModuleEmailAddress, text, subject) subject = "["+conf.ModulecodeSubjectLine+"] submission error, "+time.ctime(time.time())+", "+str(msg["Subject"]) return enqueue_outgoing_mails.send_text_message( email_addr, conf.ModuleEmailAddress, text, subject)
def process_one_subtest(job): student_lab_dir = job['student_lab_dir'] testcodefile = conf.subtest_tests[job['assignment']] testcodepath = os.path.join(conf.subtest_testcodedir, job['assignment'], testcodefile) all_submitted_files = conf.assignments[job['assignment']].keys() all_submitted_filepaths=[ os.path.join(student_lab_dir, fn) \ for fn in all_submitted_files] submitted_filepath = None # stop copying files to s.py log_global.info("Run py.test in %s " % student_lab_dir) try: if False: log_global.debug("Will run run_pytest (not constrained)") log_global.warn( "Will run run_pytest (not constrained) -- not secure" ) # student code could # damage files etc test_run_dir = subtest.run_pytest( submitted_filepath, testcodepath, rundirectorypath=student_lab_dir, jobfilepath=job['qfilepath'], maxseconds=conf.subtest_maxseconds, log_global=log_global) else: log_global.debug("Will run run_pytest_constrained") test_run_dir = subtest.run_pytest_constrained( submitted_filepath, testcodepath, rundirectorypath=student_lab_dir, other_submitted_filepaths=all_submitted_filepaths, jobfilepath=job['qfilepath'], maxseconds=conf.subtest_maxseconds, log_global=log_global, pytest_args=conf.pytest_additional_arguments) except PyTestException, msg: log_global.exception("pytest failed: %s" % msg) log_global.error("possible cause: error in test_*.py code?") ins, outs = os.popen4('tail -n 100 ' + conf.subtest_logfile) text = outs.read() + '\n' + 'Error message is:\n' + str(msg) subject = "Urgent: Malfunction in %s at %s (pytest reports problem)" % ( conf.ModulecodeSubjectLine, time.asctime()) enqueue_outgoing_mails.send_text_message(conf.SysadminEmail, conf.ModuleEmailAddress, text, subject) log_global.info("Emailed sysadmin (%s)" % conf.SysadminEmail) raise PyTestException, msg
def replymail_confirm_submission(real_name, email_addr, text, subject, assignment, valid_attachments, q_id=None): """ Sends an email to the student named real_name at address email_addr, which consists of a confirmation of receipt of their email whose subject should be in subject, with a q_id (if assigned) followed by the contents of the traing text. Returns the sent message as string if there are no attachments, or None.""" intro = "Dear "+real_name+" ("+email_addr+"),\n\n" + \ textwrap.fill("this email confirms the receipt of your email\n" + \ "with subject %s at %s." %( repr(subject), time.asctime()))+"\n\n" if q_id: if valid_attachments: intro += textwrap.fill("Your submitted files have been added to the "+\ "testing queue (id=%s).\n" % (q_id) + \ "You will receive a separate email with the "+\ "testing results.")+"\n\n" newsubject = "["+conf.ModulecodeSubjectLine+"] Submission Confirmation "\ +str(assignment)+" ("+time.asctime()+")" else: intro += textwrap.fill("Your files will be archived.")+"\n\n" newsubject = "["+conf.ModulecodeSubjectLine+"] Archive confirmation "+str(assignment)+" ("+time.asctime()+")" return enqueue_outgoing_mails.send_text_message( email_addr, conf.ModuleEmailAddress, intro+text, newsubject)
def send_email_from_file(filename): """Reads filename, which is expected to be in the format returned by the as_string() method of an email.Message.Message obejct, parses it into an email.Message.Message object as an approximation of validation, and tries to send it via SMTP. On success it returns os.EX_OK, otherwise it returns None or a failure code from the os module.""" # Read file as string with open(filename,'r') as f: msg_string = f.read() # Parse string as email message, ensure that the message has at # least certain fields filled in, move to the rejected directory # and warn administrator if we fail. try: msg = email.message_from_string(msg_string) assert msg["To"].count("@") > 0 assert msg["From"].count("@") > 0 assert len(msg["Subject"]) > 0 assert len(msg.get_payload()) > 0 except Exception, errmesg: log_global.warn("Parsing email from queued file {0:s} failed, " "rejecting. ({1:s})".format(filename, errmesg)) text = ("WARNING: invalid entry in outgoing mail queue.\n" "Exception message was: {0:s}\n" "The rest of this message contains the entry.\n".format(errmesg)) text += "\n" + 40*"=" + "\n" text += msg_string + "\n" + 40*"=" + "\n" subject = ("WARNING: Invalid outgoing mail found in queue in " "{0:s} at {1:s}".format(conf.ModulecodeSubjectLine, time.asctime())) enqueue_outgoing_mails.send_text_message(conf.SysadminEmail, conf.ModuleEmailAddress,text, subject) name_only = os.path.split(filename)[-1] if not os.path.exists(conf.outgoingmail_rejected): log_global.info("Directory for rejected outgoing mail {0:s} " "does not exist, will create it now.".format(conf.outgoingmail_rejected)) os.makedirs(conf.outgoingmail_rejected) shutil.move(filename, os.path.join(conf.outgoingmail_rejected, name_only)) return os.EX_DATAERR
def email_admin_smtp_socket_err(continuing=0): ins,outs = os.popen4('tail -n 100 '+conf.outgoingmail_logfile) text = ("There have been {0:d} consecutive SMTP socket errors " "since {1:s}\n\n".format(smtp_socket_errors['smtp_failed_run_count'], smtp_socket_errors['smtp_first_fail_time'])) text += "SMTP errors with their counts were:\n" for this_err in set(smtp_socket_errors['smtp_error_messages']): text += repr ((this_err, smtp_socket_errors['smtp_error_messages'].count(this_err))) + "\n" text += "\nRecent outgoing mail processor log file entries follow:\n\n" + outs.read() if(continuing==0): subject = ("WARNING: SMTP socket error - malfunction in {0:s} at {1:s}" "!!!".format(conf.ModulecodeSubjectLine,time.asctime())) else: subject = ("WARNING: Continuing SMTP socket error - malfunction in {0:s} at {1:s}" "!!!".format(conf.ModulecodeSubjectLine,time.asctime())) enqueue_outgoing_mails.send_text_message(conf.SysadminEmail, conf.ModuleEmailAddress, text, subject)
def record_smtp_socket_success(): """Resets the SMTP socket error statistics file - called when there are no SMTP errors in a session.""" smtp_socket_errors = read_smtp_socket_errfile() # If there were errors before this success, notify admin we # succeeded and are about to reset error counters. Also log the # fact. if (smtp_socket_errors.has_key('smtp_failed_run_count')): err_count = smtp_socket_errors['smtp_failed_run_count'] else: err_count = 0 if err_count > 0: subject = ("INFORMATION: SMTP socket error cleared in {0:s} " "at {1:s}".format(conf.ModulecodeSubjectLine,time.asctime())) text = ("After {0:d} SMTP socket errors, delivery of emails has succeeded.\n" "Will reset error counters.".format(err_count)) enqueue_outgoing_mails.send_text_message(conf.SysadminEmail, conf.ModuleEmailAddress, text, subject, conf ) log_global.info("After {0:d} failures, SMTP socket error cleared".format(err_count)) log_global.info("Emailed sysadmin") # Clear error counter and timestamps, set success time to now. smtp_socket_errors = { 'smtp_failed_run_count':0, 'smtp_first_fail_time':'', 'smtp_last_fail_time':'', 'smtp_last_success_time':time.asctime(), 'smtp_last_admin_mail_time':time.asctime(), 'smtp_error_messages':[] } # Save to disk. with open(conf.smtp_error_file,'w') as f: fcntl.flock(f.fileno(), fcntl.LOCK_EX) f.seek(0) f.truncate(0) cPickle.dump(smtp_socket_errors, f) fcntl.flock(f.fileno(), fcntl.LOCK_UN)
def check_maildaemon( msg, from_addr, domain ): if re.search(_rx_email_daemon,from_addr,re.IGNORECASE): log_global.critical("Received a msg from a mailing daemon (X)! ("+msg["From"]+")") log_global.critical("Forwarding it to administrator (%s)." % (conf.SysadminEmail) ) # We need to delete the "To" key entry first: del msg["To"] # Then write new value msg["To"] = conf.SysadminEmail original_subject = msg["Subject"] del msg["Subject"] msg["Subject"] = "Urgent: [%s] email from daemon: %s" % (conf.ModulecodeSubjectLine,repr(original_subject)) del msg["From"] msg["From"] = conf.ModuleEmailAddress enqueue_outgoing_mails.mailqueue_push(msg) return 1 #check that the email is not from ourselves if string.count( from_addr.lower(), conf.Modulecode.lower()): log_global.critical("Received a msg from myself! ("+msg["From"]+")") log_global.critical("Forwarding it to administrator (%s)." % repr(conf.SysadminEmail) ) subject = "Urgent: [%s] email from system (danger of loop): %s" % (conf.ModulecodeSubjectLine,repr(msg["Subject"])) sendmail = enqueue_outgoing_mails.send_text_message( conf.SysadminEmail, conf.ModuleEmailAddress, msg.as_string(), subject) append_mail_to_mailbox( sendmail, conf.sysadminmailfolder, log_global, "(outgoing mail to SysadminEmail (myself-loop))" ) return 1 if string.count(domain.lower(), "twitter"): log_global.info("Received a msg from twitter: ("+msg["From"]+")") log_global.info("Forwarding it to administrator (%s)." % (conf.SysadminEmail) ) # We need to delete the "To" key entry first: del msg["To"] #Then write new value msg["To"] = conf.SysadminEmail original_subject = msg["Subject"] del msg["Subject"] msg["Subject"] = "[%s] Twitter writes: %s" % (\ conf.ModulecodeSubjectLine,repr(original_subject)) del msg["From"] msg["From"] = conf.ModuleEmailAddress enqueue_outgoing_mails.mailqueue_push(msg) return 1 return 0
def check_student_groups(students, groups, email_admin=True): """Given the dictionary 'students', {'stud1@domain1':('stud1 name','stud1_group'), ...}, and the list 'groups', checks that the group assigned to each student can be found in 'groups'. If this is not the case the error is logged and, by default, the administrator is emailed a warning. Returns a new students dictionary which is the input students dictionary, less any assigned to invalid groups, and a list of those students that were removed.""" output_students = {} ignored_students = [] for stud_email in students: (stud_name, stud_group) = students[stud_email] if stud_group not in groups: ignored_students.append("{0} ({1}) (group: {2})".format( stud_name, stud_email, stud_group)) log_global.error("Student '{0}' ({1}) assigned group '{2}'," " which is not in config file '{3}'.".format( stud_name, stud_email, stud_group, 'config_' + Modulecode.lower())) if email_admin: subject = ( "WARNING: {0}: student assigned to invalid group {1}". format(conf.ModulecodeSubjectLine, stud_group)) text = ( "The student {0} ({1}) is assigned to deadline group {2}," " which was not found in the configuration file {3}. Marks for" " this student will not be recorded.".format( stud_name, stud_email, stud_group, 'config_' + Modulecode.lower())) text = textwrap.fill(text) enqueue_outgoing_mails.send_text_message( conf.SysadminEmail, conf.ModuleEmailAddress, text, subject) else: output_students[stud_email] = students[stud_email] return (output_students, ignored_students)
def email_address_username_and_domain(addr): """Maps an email address such as [email protected] to username and domain part. Note: this routine splits at the last '@' sign, having checked only that the given address contains at least one '@' sign and at least one '.' character. It is possible to have a valid email address with multiple '@' signs, (see e.g. the informal RFC3696), and this routine should work in these cases. """ if (addr.count('@') < 1 or addr.count('.') < 1): subject = "WARNING: Invalid address on incoming email from %s" % repr(addr) text = ("Email from %s, address either has fewer than one '.' character,\n" "or fewer than one '@' character." % repr(addr)) enqueue_outgoing_mails.send_text_message(conf.SysadminEmail, conf.ModuleEmailAddress, text, subject) log_global.info("Emailed sysadmin about regex failure of splitting address %s. " % addr) raise StandardError,"Unusual email address : '%s" % repr(addr) try: parts = addr.split('@') domain = parts[-1] username = '' for p in parts[0:-2]: username = username + p + '@' username = username + parts[-2] except: # otherwise send message to admin subject = "WARNING: Address split failed on incoming email from %s" % repr(addr) text = "Email from %s. We split to find name='%s' and domain='%s'" % (addr,username,domain) enqueue_outgoing_mails.send_text_message(conf.SysadminEmail, conf.ModuleEmailAddress, text, subject) log_global.info("Emailed sysadmin about failure of splitting address %s. " % addr) raise StandardError,"Unusual email address : '%s" % repr(addr) return username, domain
def retrieve_assignment(assignment,message,student_dir,real_name,email_addr,logger): logger.info("Retrieving files for %s" % repr(assignment)) (username,domain)=email_address_username_and_domain(email_addr) #get list of files in student_dir submission_dir = os.path.join(student_dir,assignment) if os.path.exists ( submission_dir ): submitted_files = os.listdir( submission_dir ) else: errormail = replymail_error( message, "It seems that you have not yet submitted any files." ) append_mail_to_mailbox( errormail, username, logger, "(outgoing error mail: no files submitted->retrieval is impossible)" ) return None files_by_type = analyze_filenames(assignment_file_map(assignment), submitted_files, logger) report = submitted_files_report(assignment, files_by_type) body = ["Dear %s (%s),\n\n" % (real_name,email_addr)] body.append("Here is a list of your files found on the server for assignment '%s':\n\n" % assignment) body.append(report) body.append("\n\nPlease find attached to the _next_ email these files\n") body.append("that you submitted for '%s'.\n\n" %assignment) body.append("(In addition, there may be one (or more) files named\n") body.append("'part00?.bin' which contain the body of your email and can \n") body.append("be ignored.)\n\n") subject = "[%s] summary of submitted files for '%s' (%s)" % ( conf.ModulecodeSubjectLine, assignment, time.asctime()) mail = enqueue_outgoing_mails.send_text_message( email_addr, conf.ModuleEmailAddress, string.join(body,""), subject) append_mail_to_mailbox( mail, username, logger, "(outgoing retrieval report mail)" ) #now do retrieve the files and mail those subject = "[%s] retrieved files for '%s' (%s)" % ( conf.ModulecodeSubjectLine, assignment, time.asctime()) From = conf.ModuleEmailAddress to = email_addr retrieval_return_mail = bundle_files_in_directory_in_email( submission_dir,to,From,subject) text = enqueue_outgoing_mails.send_message(retrieval_return_mail) append_mail_to_mailbox( text, 'test', logger, "(outgoing retrieval mail)" ) logger.info("Sent retrieval mail for %s" % repr(assignment))
def parse_pytest_report(dirpath, log): """Given the path to a pytest-directory, returns a tuple (A,B). The first entry A is the report_data data structure: list L of triplets t with t[0] = True if test passed, False if failed t[1] = one line describing test (for example test/test_tenfunc.py:test_count) t[2] = None if test passed, otherwise String (including newlines) with more detailed failure description The second entry B is a list of bools. These are 'True' if py.test terminated and False if it didn't. If py.test did not terminate, the test data will be incomplete, but it is still useful to see which tests have been passed. """ status, reason = conf.read_status(dirpath) log.debug("In parse_pytest_report, dirpath=%s" % dirpath) log.debug("DD status=%s" % status) log.debug("DD reason=%s" % reason) tests = [] path = os.path.join(dirpath, conf.pytest_log) f = open(path, 'r') lines = f.readlines() f.close() n = len(lines) log.debug("Read {} lines from {}: \n'''{}'''".format( n, path, "\n".join(lines))) if reason == "unterminated": #this happened when a job didn't terminate error_report = """When trying to test your submission, the testing code did not terminate within 60 seconds. Either your code runs a very lengthy calculation, or is stuck in some infinite loop. We cannot assess and test your code appropriately if it does not return. Please fix the problem (i.e. identify where it gets stuck), and resubmit, and inform the laboratory leader about this. If you don't understand any of this, please get also in touch with a demonstrator or the laboratory leader. Debug information: status = %s reason = %s Content of pytest_log:\n--\n%s -- """ % (status, reason, "\n".join(lines)) tests.append((False, "unterminated problem in s.py", error_report)) log.warn("auto-testing-problem" + str(tests) + str(status) + "Job has not terminated, need manual intervention??") return tests, status else: pass log.debug("Normal branch, have terminated.") # We may need this in two places further below error_report_import_failed = """ When trying to import your submission, we have encountered an error, and as a result, this submission has failed. A likely problem is non-ASCII characters in your submission, or a syntax error, or an operation that is carried out when the file is imported and which fails. All testing will fail if the file cannot be imported. It is thus important to remove all such errors before submission. We suggest that you either: - Execute the file on its own to check no errors are reported. - Ensure that you are not trying access particular data files that you have on your computer - these will generally not be available for the testing system. - remove any characters that are not encoded as ASCII, and re-sumbit the file, or - specify the encoding of the file in accordance with the advice at http://www.python.org/dev/peps/pep-0263/ For example, add the following comment on the first line of your file: # coding=utf-8 If the above makes no sense (either because you don't understand it, or because you do understand but find this does not match reality), then please contact a demonstrator or the laboratory leader about this. """ #special case: bug_1_description = """September 2011. It seems that syntax errors of this type: def my_foo(x): y = x**2 return y which would result in an "IndentationError: unindent does not match any outer indentation level" can hang the testing with py.test (version 1.3). Upgrading py.test to 2.1.2 'solved' the hang issue. Py.test still fails with this behaviour (see tests/import-fails2.py): - pytest_log is an empty file - std error contains: Traceback (most recent call last): File "/usr/bin/py.test", line 9, in <module> load_entry_point('pytest==2.1.2', 'console_scripts', 'py.test')() File "/usr/local/lib/python2.6/dist-packages/pytest-2.1.2-py2.6.egg/_pytest/core.py", line 457, in main <snip> res = method(**kwargs) File "/usr/local/lib/python2.6/dist-packages/pytest-2.1.2-py2.6.egg/_pytest/resultlog.py", line 92, in pytest_internalerror path = excrepr.reprcrash.path AttributeError: 'str' object has no attribute 'reprcrash'.readlines() - stdout contains ============================= test session starts ============================== platform linux2 -- Python 2.6.6 -- pytest-2.1.2 collecting ... INTERNALERROR> Traceback (most recent call last): INTERNALERROR> File "/usr/local/lib/python2.6/dist-packages/pytest-2.1.2-py2.6.egg/_pytest/main.py", line 67, in wrap_session INTERNALERROR> doit(config, session) INTERNALERROR> File "/usr/local/lib/python2.6/dist-packages/pytest-2.1.2-py2.6.egg/_pytest/main.py", line 95, in _main INTERNALERROR> config.hook.pytest_collection(session=session) INTERNALERROR> File "/usr/local/lib/python2.6/dist-packages/pytest-2.1.2-py2.6.egg/_pytest/core.py", line 411, in __call__ INTERNALERROR> return self._docall(methods, kwargs) <snip> INTERNALERROR> File "/usr/local/lib/python2.6/dist-packages/pytest-2.1.2-py2.6.egg/_pytest/resultlog.py", line 84, in pytest_collectreport INTERNALERROR> longrepr = str(report.longrepr.reprcrash) INTERNALERROR> AttributeError: CollectErrorRepr instance has no attribute 'reprcrash' INTERNALERROR> The detailed files are available in code/python/subtest/bugs/bug-archive-test-import-fails2-py2.1.2 So py.test seems to be buggy and ONLY in combination with the --reportlog switch. Decativing this --reportlog, gives the right error message. We will try to catch this here and ask the user(=student) to make sure they can 'run' their file through the interpreter without getting an error message. """ if n == 0: #the only case where we know this can happen is the bug 1 described above. log.info("n == 0: Reached branch for suspected BUG 1") #let's carry out more checks to see whether this is the case. pytest_stdout_string = open(os.path.join(dirpath, conf.pytest_stdout)).read() if "INTERNALERROR" in pytest_stdout_string: log.info( "Found 'INTERNALERROR' in stdout --> supports suspected bug 1 with py.test 2.1 (try py.test --version)" ) odd = 0 else: log.warn( "Expected 'INTERNALERROR' in stdout but couldn't find. Don't understand." ) odd = 1 pytest_stderr_string = open(os.path.join(dirpath, conf.pytest_stderr)).read() if """AttributeError""" in pytest_stderr_string and "reprcrash" in pytest_stderr_string: log.info( "Found 'AttributeError' in stderr --> supports suspected bug 1 with py.test 2.1" ) odd += 0 else: odd += 1 log.warn( "Expected 'AttributeError <...>` in stderr but couldn't find. Don't understand." ) #Printinternalerror> AttributeError: CollectErrorRepr instance has no attribute 'reprcrash' assert status == True, "This must be true as the test has returned and was not killed." if odd == 0: #clear case for Bug 1 -- no ODDities log.info("n == 0: We think import failed. ") error_report = """When trying to import your submission, we have encountered an error, and as a result, this submission has failed. A likely problem is indentation. You should find that Python reports some error (SyntaxError most likely) when you execute your file MYFILE *before* submission (either by pressing F5 in IDLE, or running "python MYFILE.py"). All testing will fail if the file cannot be imported. It is thus important to remove all such errors before submission. If the above makes no sense (either because you don't understand it, or because you do understand but find this does not match reality), then please contact a demonstrator or the laboratory leader about this.""" tests.append((False, "import s.py", error_report)) log.info("n == 0: Appending failed test for import") return tests, status else: raise RuntimeError,"A new bug has occured -- see logs for detail.\n"+\ "In this case, the problem may originate from a py.test version newer than 2.1.2.\n"+\ "We do a lot of tests to see whether a particular bug is a particular bug (see source for bug 1)\n"+\ "and if any of the output changes significantly, this test will fail.\n\n"+\ "The failed job should be the first one in the testing queue." #special case. bug_2_description = """March 2013. It seems that submissions containing non-ASCII characters which would usually result in an "SyntaxError: Non-ASCII character '\xff' in file..." can cause problems in py.test 2.3.4, which fails (when called with the flags we use; running py.test without the "--resultlog=" flag set gives the correct error) with this behaviour: - pytest_log contains the text in the pytest_log_bug2 variable (defined below). - pytest.stderr is an empty file - pytest.stdout contains the text in the pytest_stdout_bug2 varaible (defined below). The detailed files are available in code/python/subtest/bugs/bug-archive-non-ascii-pytest2.3.4 So py.test seems to be buggy and ONLY in combination with the --resultlog switch. Decativing this --resultlog, gives the right error message. We will try to catch this here and ask the user(=student) to make sure they can 'run' their file through the interpreter without getting an error message. """ pytest_log_bug2 = """! /usr/local/lib/python2.7/dist-packages/_pytest/resultlog.py Traceback (most recent call last): File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 81, in wrap_session doit(config, session) File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 112, in _main config.hook.pytest_collection(session=session) File "/usr/local/lib/python2.7/dist-packages/_pytest/core.py", line 422, in __call__ return self._docall(methods, kwargs) File "/usr/local/lib/python2.7/dist-packages/_pytest/core.py", line 433, in _docall res = mc.execute() File "/usr/local/lib/python2.7/dist-packages/_pytest/core.py", line 351, in execute res = method(**kwargs) File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 116, in pytest_collection return session.perform_collect() File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 465, in perform_collect items = self._perform_collect(args, genitems) File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 498, in _perform_collect self.items.extend(self.genitems(node)) File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 643, in genitems node.ihook.pytest_collectreport(report=rep) File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 157, in call_matching_hooks return hookmethod.pcall(plugins, **kwargs) File "/usr/local/lib/python2.7/dist-packages/_pytest/core.py", line 426, in pcall return self._docall(methods, kwargs) File "/usr/local/lib/python2.7/dist-packages/_pytest/core.py", line 433, in _docall res = mc.execute() File "/usr/local/lib/python2.7/dist-packages/_pytest/core.py", line 351, in execute res = method(**kwargs) File "/usr/local/lib/python2.7/dist-packages/_pytest/resultlog.py", line 88, in pytest_collectreport longrepr = str(report.longrepr.reprcrash) AttributeError: CollectErrorRepr instance has no attribute 'reprcrash'""" pytest_stdout_bug2 = """============================= test session starts ============================== platform linux2 -- Python 2.7.3 -- pytest-2.3.4 INTERNALERROR> Traceback (most recent call last): INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 81, in wrap_session INTERNALERROR> doit(config, session) INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 112, in _main INTERNALERROR> config.hook.pytest_collection(session=session) INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/core.py", line 422, in __call__ INTERNALERROR> return self._docall(methods, kwargs) INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/core.py", line 433, in _docall INTERNALERROR> res = mc.execute() INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/core.py", line 351, in execute INTERNALERROR> res = method(**kwargs) INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 116, in pytest_collection INTERNALERROR> return session.perform_collect() INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 465, in perform_collect INTERNALERROR> items = self._perform_collect(args, genitems) INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 498, in _perform_collect INTERNALERROR> self.items.extend(self.genitems(node)) INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 643, in genitems INTERNALERROR> node.ihook.pytest_collectreport(report=rep) INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/main.py", line 157, in call_matching_hooks INTERNALERROR> return hookmethod.pcall(plugins, **kwargs) INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/core.py", line 426, in pcall INTERNALERROR> return self._docall(methods, kwargs) INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/core.py", line 433, in _docall INTERNALERROR> res = mc.execute() INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/core.py", line 351, in execute INTERNALERROR> res = method(**kwargs) INTERNALERROR> File "/usr/local/lib/python2.7/dist-packages/_pytest/resultlog.py", line 88, in pytest_collectreport INTERNALERROR> longrepr = str(report.longrepr.reprcrash) INTERNALERROR> AttributeError: CollectErrorRepr instance has no attribute 'reprcrash' =========================== 1 error in 0.23 seconds ============================""" # Test for BUG 2. The likely-fragile is in the function we call # here. if (looks_like_bug2(dirpath, log)): # When suspecting the bug, log it, inform the student (via the # returned tuple), and inform the admin (via email directly). log.info("Reached branch for suspected BUG 2") error_report = error_report_import_failed admin_errorreport = """ Suspect BUG 2 in subtest:__init__.py (import of file [with non-ASCII characters? ] fails). This arose in directory {0:s}. The following error report has been included in an email to the student.\n\n""".format(dirpath) + error_report enqueue_outgoing_mails.send_text_message( conf.SysadminEmail, conf.ModuleEmailAddress, admin_errorreport, "Error importing student submission", html=False) tests.append((False, "import s.py", error_report)) return tests, status # otherwise carry on -- this is the normal case. i = 0 while i < n: log.info("i={}, n={}, parsing line '{}'".format(i, n, lines[i])) if len(lines[i]) == 0: raise ValueError, "Empty line [%d] in '%s' -- impossible" % (i, path) testname = lines[i][1:].strip() if lines[i][0] == '.': #passed tests.append((True, testname, None)) i = i + 1 elif lines[i][0] == 'F': #failed #Errors that have ocurred while executing any functions defined as 'def test_' #will be correctly reported in the pytest_log file. For example: #F 5NYr6E/test_tenfunc.py:test_log2 # #If there is an error when the s.py file is imported, this will not be #explained in pytest_log (but in pytest.stdout). The corresponding location #of the error in pytest_log reads: #F _test-2009-09-23-23:00:04/test_lab1.py # #Note that in case 1, the failing function ('test_log2') is mentioned whereas #below that in case2, only the failing file is mentioned ('test_lab1'). #Can use this to distinguish the cases: #Need to catch this case if lines[i].strip().endswith( ".py"): #if this ends with .py, then the import has failed import_error= '\nThere was an error when importing your file:\n'\ +open(os.path.join(dirpath,'pytest.stdout')).read() import_error += error_report_import_failed log.debug( "Have identified an import error: import_error message='{}'" .format(import_error)) else: import_error = "" #import_error = "Not an import error, lines[i]='%s'" % lines[i] i = i + 1 errorreport = "" #Now normal parsing of the next (indented) lines: while lines[i][0] == " ": errorreport += lines[i][1:] i = i + 1 if i == n: break if len(lines[i]) == 0: raise ValueError, "Empty line [%d] in '%s' -- impossible" % ( i, path) errorreport += import_error log.debug("Final error report for {} is '{}'".format( testname, errorreport)) tests.append((False, testname, errorreport)) else: # This line has length but does not start with '.' or # 'F'. Should not happen, warn admin and student. admin_errorreport = """Error in parse_pytest_report. We encountered a line in pytest_log which begins neither with '.' nor 'F'. This may be a bug. The directory in which this error arose is {0:s} and the contents of the pytest_log file follow.""".format(dirpath) admin_errorreport += "\n\n" with open(path, 'r') as fh: admin_errorreport += fh.read() enqueue_outgoing_mails.send_text_message( conf.SysadminEmail, conf.ModuleEmailAddress, admin_errorreport, "Error parsing pytest_log", html=False) error_report = """An error has occurred in parsing the log file pytest_log. This may be due to issues with your submission, or it may be because of a bug in the testing system. The laboratory leader has been informed. Please ensure that you can run your file (either by pressing F5 in IDLE, or running "python MYFILE.py") without getting any errors, *BEFORE* submitting it. If you are submitting C code, please ensure it compiles without errors before submitting. If the above makes no sense (either because you don't understand it, or because you do understand but find this does not match reality), then please contact a demonstrator or the laboratory leader about this.""" tests.append((False, "Error parsing pytest_log", error_report)) return tests, status log.debug( "leaving parse_pytest_report normally. Data: \ntests={}".format(tests)) log.debug("status={}".format(status)) return tests, status
def send_mail( msg, debug = 0 ): """Sends a mail (in email-message format). Expect msg to be of type email.Message.Message(), retuns os.EX_OK on success, None or an os.* error code on error. """ # turn this off for off-line testing network = 1 mailfrom = msg["From"] # For multiple recipients: # msg['To'] ought to look like 'recipient1, recipient2' # whilst smtp.sendmail() should get a list ['recipient1', 'recipient2'] if msg["To"].count(",") == 0: # single recipient mailto = msg["To"] else: mailto = [recip.replace(" ","") for recip in msg["To"].split(",")] assert mailfrom, "No from-addr given: %s" % repr(mailfrom) assert mailto, "No to-addr given: %s" % repr(mailto) log_global.info("send_mail(): email from %s to %s" % (repr(mailfrom),repr(mailto))) log_global.debug("send_mail(): Beginning of content is\n%s" % msg.as_string()[0:1000*80]) if debug: print "Beginning of content is",msg.as_string()[0:1000*80] log_global.info("sending email from %s to %s" % (repr(mailfrom),repr(mailto))) if not(network): log_global.warn("Not sending email (not in network mode)") print "==BEGIN Sent Email=========================" print msg.as_string()[0:1*80] print "==END Sent Email===========================" return os.EX_OK else: log_global.info("(deliver email to %s via smtp to %s)" % (repr(mailto),repr(conf.smtp))) import socket try: smtp = smtplib.SMTP(conf.smtp) smtp.set_debuglevel(0) #put in 1 for messages to stdout/stderr smtp.sendmail(mailfrom, mailto, msg.as_string()) smtp.quit() log_global.debug("finished sending email to %s" % repr(mailto)) record_smtp_socket_success() return os.EX_OK except socket.error,err_msg: # This arises most commonly when the SMTP server is # overloaded or unavailable - our main goal is to keep # re-trying this message (so return None to keep it in the # queue), and notify admin from time to time, as per # config settings. log_global.warn("smtp delivery failed, will retry on next queue run, error: {0:s}".format(err_msg)) process_smtp_socket_error(err_msg) return None except smtplib.SMTPRecipientsRefused,err_msg: # Mostly occurs if we mistakenly reply to spam. It is not # desired to stop the system in this case, so we inform # the administrator, including the message that we could # not send to its original recipient, and carry on. We # return EX_OK in order that the undeliverable message # be removed from the queue of messages for sending. undelivered_string = msg.as_string() text = ("WARNING: could not deliver email.\n" "The rest of this message contains the mail that was undeliverable,\n" "followed by the error log and exception error message.\n\n\n") text += "\n" + 40*"=" + "\n" text += undelivered_string + "\n" + 40*"=" + "\n" ins,outs = os.popen4('tail -n 100 '+conf.Logfile) text += outs.read()+'\n'+'Error message is:\n'+str(err_msg) subject = "WARNING: Malfunction in %s at %s (probably spam) (could not deliver email but will carry on, might have been spam)" % (conf.ModulecodeSubjectLine,time.asctime()) enqueue_outgoing_mails.send_text_message( conf.SysadminEmail, conf.ModuleEmailAddress,text, subject) log_global.info("Emailed sysadmin") log_global.warning("Failed to send email to %s" % repr(mailto)) record_smtp_socket_success() return os.EX_OK
log_global.info("Other process running, quitting") else: #all well log_global.debug("About to read queue") if live: try: process_queue() unlock_semaphore(lock) except: log_global.exception("Something went wrong (caught globally)") log_global.critical("Preparing email to sysadmin (%s)" % repr(conf.SysadminEmail)) ins,outs = os.popen4('tail -n 100 '+conf.outgoingmail_logfile) text = outs.read() subject = "URGENT: Malfunction in process_outgoing_mails %s at %s !!!" % (conf.ModulecodeSubjectLine,time.asctime()) enqueue_outgoing_mails.send_text_message( conf.SysadminEmail, conf.ModuleEmailAddress,text, subject) log_global.info("Leaving now (not removing lockfile).") raise else: process_queue() unlock_semaphore(lock) import time f=open(conf.outgoingmail_pulsefile,'w') data = {'now-secs':time.time(),'now-ascii':time.ctime(),'module':conf.ModulecodeSubjectLine, 'what':"process-outgoingmail"} f.write("%s" % repr(data)) f.close() log_global.debug("About to leave, updated pulse.")
This submission attempt is not a valid submission -- please improve code and resubmit. More detailed analysis ---------------------- Below, we provide the outcome (ok/failed) of those tests that have been run and terminated. The problem of non-termination is likely to be with the next test to be run. """ +post_test_analysis.summary_text(report)+\ """\n\nAsk a demonstrator for help if this does not make sense to you.\n""" enqueue_outgoing_mails.send_text_message(job['email'], conf.ModuleEmailAddress, text, subject) admtext = """Unterminated code detected in job id {0:d} (student lab directory was {1:s}). Student {2:s} ({3:s}) was sent the following email. """.format(job['id'], student_lab_dir, job['real_name'], job['login']) admtext = admtext + text admsubject = subject + " (" + job['login'] + " " + job[ 'real_name'] + ")" enqueue_outgoing_mails.send_text_message(conf.SysadminEmail, conf.ModuleEmailAddress,