def test_from_zip(self): # use the "evil" test case because it has multiple stages host_dir = 'testdata/executor/HOST_DIR' stages_dir = 'testdata/executor/evil' fake_file = six.BytesIO() executor.Stages(stages_dir).save_zip(fake_file) stages = executor.Stages.from_zip(fake_file, 'test_from_zip', host_dir) fake_file2 = six.BytesIO() executor.Stages(host_dir + '/test_from_zip').save_zip(fake_file2) shutil.rmtree(host_dir + '/test_from_zip') with zipfile.ZipFile(fake_file, 'r') as zf: with zipfile.ZipFile(fake_file2, 'r') as zf2: self.assertEqual(sorted(zf.namelist()), sorted(zf2.namelist()))
def test_hello_world_cpp(self): host_dir = 'testdata/executor/HOST_DIR/hello_world_cpp' stages_dir = 'testdata/executor/hello_world_cpp' code_path = 'testdata/executor/hello_world_cpp/code/hello_world.cpp' with EphemeralDir(host_dir): c = executor.DockerExecutor('test_hello_world_cpp', host_dir) c.init() output, errors = c.run_stages(code_path, executor.Stages(stages_dir)) self.assertEqual(errors, []) self.assertEqual(output.strip(), '')
def courses_x_assignments_x_edit(course_name, assignment_name): user = login.current_user instructs_course = user.instructs_course(course_name) if instructs_course: stages = executor.Stages(os.path.join( '../data/files/courses', course_name, 'assignments', assignment_name)) form = flask.request.form for error in _edit_assignment(form, course_name, assignment_name, stages): flask.flash(error) return flask.redirect( u'/courses/{}/assignments/{}'.format(course_name, assignment_name), code=303)
def test_hello_world(self): host_dir = 'testdata/executor/HOST_DIR/hello_world' stages_dir = 'testdata/executor/hello_world' code_path = None with EphemeralDir(host_dir): c = executor.DockerExecutor('test_hello_world', host_dir) c.init() stages = executor.Stages(stages_dir) output, errors = c.run_stages(code_path, stages) self.assertEqual(errors, []) self.assertEqual(stages.stages['stage0'].output.stdout, b'HELLO WORLD\n')
def test_hello_world_cpp(self): host_dir = 'testdata/executor/HOST_DIR/score' stages_dir = 'testdata/executor/score' code_path = None with EphemeralDir(host_dir): c = executor.DockerExecutor('test_score', host_dir) c.init() stages = executor.Stages(stages_dir) output, errors = c.run_stages(code_path, stages) self.assertEqual(errors, []) self.assertEqual(output.strip(), '') self.assertIn('score', stages.stages) self.assertEqual(stages.stages['score'].output.score, 12345)
def test_untrusted(self): host_dir = 'testdata/executor/HOST_DIR/untrusted' stages_dir = 'testdata/executor/untrusted' code_path = None score_map = {} with EphemeralDir(host_dir): c = executor.DockerExecutor('test_unptrusted', host_dir) c.init() output, errors = c.run_stages( code_path, executor.Stages(stages_dir), lambda stage: score_map .update({stage.name: stage.output.score})) self.assertEqual(errors, []) self.assertEqual(output.strip(), '') self.assertEqual(score_map, {'trusted': 123, 'untrusted': None})
def test_save_zip(self): # use the "evil" test case because it has multiple stages stages_dir = 'testdata/executor/evil' stages = executor.Stages(stages_dir) fake_file = six.BytesIO() stages.save_zip(fake_file) expected_files = [ 'metadata.json', 'fork_bomb/', 'fork_bomb/fork_bomb.cpp', 'fork_bomb/main', 'many_open_files/', 'many_open_files/main', 'many_open_files/many_open_files.cpp', 'much_ram/', 'much_ram/much_ram.cpp', 'much_ram/main' ] with zipfile.ZipFile(fake_file, 'r') as zf: self.assertEqual(sorted(zf.namelist()), sorted(expected_files))
def test_big_output(self): host_dir = 'testdata/executor/HOST_DIR/big_output' stages_dir = 'testdata/executor/big_output' code_path = None with EphemeralDir(host_dir): c = executor.DockerExecutor('test_big_output', host_dir) c.init() stages = executor.Stages(stages_dir) output, errors = c.run_stages(code_path, stages) self.assertEqual(errors, []) stage_output = stages.stages['make_output'].output.stdout self.assertGreaterEqual(len(stage_output), 128 * 1024) # >= than 128KB output self.assertLessEqual(len(stage_output), 132 * 1024) # <= than 128KB + 4KB output
def courses_x_assignments_x_download(course_name, assignment_name): user = login.current_user instructs_course = user.instructs_course(course_name) if instructs_course: stages = executor.Stages(os.path.join( '../data/files/courses', course_name, 'assignments', assignment_name)) buf = six.BytesIO() stages.save_zip(buf) response = flask.make_response(buf.getvalue()) response.headers['Content-Disposition'] = ( 'attachment; filename={}.zip'.format(assignment_name)) return response else: return flask.redirect( u'/courses/{}/assignments/{}'.format(course_name, assignment_name), code=303)
def test_unicode_in_env(self): host_dir = 'testdata/executor/HOST_DIR/hello_world' stages_dir = 'testdata/executor/hello_world' code_path = None with EphemeralDir(host_dir): c = executor.DockerExecutor('test_unicode_in_env', host_dir) c.init() stages = executor.Stages(stages_dir) env = { 'DECODED': u'┻━┻ ︵ ¯\(ツ)/¯ ︵ ┻━┻', 'ENCODED': '┻━┻ ︵ ¯\(ツ)/¯ ︵ ┻━┻', } output, errors = c.run_stages(code_path, stages, env=env) self.assertEqual(errors, []) self.assertEqual(stages.stages['stage0'].output.stdout, b'HELLO WORLD\n')
def courses_x_assignments_x(course_name, assignment_name): user = login.current_user course = grade_oven.course(course_name) assignment = course.assignment(assignment_name) student_submission = assignment.student_submission(user.username) instructs_course = user.instructs_course(course.name) takes_course = user.takes_course(course.name) stages = executor.Stages(os.path.join( '../data/files/courses', course.name, 'assignments', assignment.name)) if takes_course: due_date = assignment.due_date() if due_date is None: formatted_due_date = '' else: formatted_due_date = time.strftime('%Y-%m-%d %H:%M', time.localtime(due_date)) submission_output = student_submission.output() submission_has_output_html = bool(student_submission.output_html()) submission_errors = student_submission.errors().strip() else: formatted_due_date = '' submission_output, submission_errors = '', '' submission_has_output_html = False header_row, table = _make_grade_table( course, assignment, show_real_names=instructs_course) return flask.render_template( 'courses_x_assignments_x.html', username=login.current_user.get_id(), instructs_course=instructs_course, takes_course=takes_course, course_name=course.name, assignment_name=assignment.name, formatted_due_date=formatted_due_date, stages=stages.stages.values(), submission_output=submission_output, submission_has_output_html=submission_has_output_html, submission_errors=submission_errors, stages_desc=stages.description, header_row=header_row, table=table)
def test_evil_cpp(self): host_dir = 'testdata/executor/HOST_DIR/evil' stages_dir = 'testdata/executor/evil' code_path = None with EphemeralDir(host_dir): c = executor.DockerExecutor('test_evil', host_dir) c.init() c.timeout_seconds = 5 c.max_num_files = 100 c.max_mem_bytes = 64 * 1024**2 stages = executor.Stages(stages_dir) output, errors = c.run_stages(code_path, stages) self.assertEqual(stages.stages['fork_bomb'].output.errors, [ 'Command "/grade_oven/fork_bomb/main" did not finish in ' '5 seconds and timed out.' ]) self.assertTrue(b'many_open_files: 80 files open' in stages.stages['many_open_files'].output.stdout) self.assertFalse(b'many_open_files: 120 files open' in stages.stages['many_open_files'].output.stdout) self.assertTrue(b'much_ram: Allocated 48MB.' in stages.stages['much_ram'].output.stdout) self.assertFalse(b'much_ram: Allocated 64MB.' in stages.stages['much_ram'].output.stdout)
def _enqueue_student_submission(course_name, assignment_name, username, files): user = grade_oven.user(username) course = grade_oven.course(course_name) assignment = course.assignment(assignment_name) student_submission = assignment.student_submission(username) # If this is a resubmission, but there's no original submission, skip it. # if student_submission.num_submissions() == 0 and not files: # return monitor_variables['assignment_attempts'] += 1 logging.info(u'Student "%s" is attempting assignment "%s/%s".', username, course_name, assignment_name) submission_dir = os.path.join( '../data/files/courses', course_name, 'assignments', assignment_name, 'submissions', username) desc = u'{}_{}_{}'.format(course_name, assignment_name, username) # TODO: Fix the quick hack below. It is only in place to avoid "escaped" # names that are not safe docker container names. container_id = str(abs(hash(desc)))[:32] num_submissions = student_submission.num_submissions() submit_time = student_submission.submit_time() or 0 cur_time = time.time() min_seconds_since_last_submission = min(num_submissions**3, 5.0) priority = (num_submissions, submit_time) stages = executor.Stages(os.path.join( '../data/files/courses', course_name, 'assignments', assignment_name)) submission = GradeOvenSubmission( priority, username, desc, submission_dir, container_id, stages, student_submission) if submission in executor_queue: logging.warning( u'Student "%s" submited assignment "%s/%s" while still in the queue.', username, course_name, assignment_name) flask.flash( u'{} cannot submit assignment {} for {} while in the queue.'.format( username, assignment_name, course_name)) elif cur_time < submit_time + min_seconds_since_last_submission: seconds_left = min_seconds_since_last_submission - (cur_time - submit_time) formatted_time = time.strftime( '%Y-%m-%d %H:%M:%S', time.localtime(submit_time + min_seconds_since_last_submission)) logging.info( u'Student "%s" submitted assignment "%s/%s" ' 'but needs to wait until %s (%s seconds).', username, course_name, assignment_name, formatted_time, seconds_left) flask.flash( u'Please wait until {} ({:.0f} seconds) to submit {} again.'.format( formatted_time, seconds_left, assignment_name)) else: if files: try: shutil.rmtree(submission_dir) except OSError as e: if e.errno != errno.ENOENT: raise e save_files_in_dir(files, submission_dir) # If there are no files being uploaded, then this must be a resubmission. student_submission.set_submit_time() student_submission.set_num_submissions( student_submission.num_submissions() + 1) student_submission.set_status('queued') executor_queue.enqueue(submission)