Skip to content

Commit

Permalink
feature: add moss test
Browse files Browse the repository at this point in the history
  • Loading branch information
tc-imba committed Sep 30, 2019
1 parent ae84f0b commit df97d3c
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 37 deletions.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ PyYAML
git+https://github.com/iceb0y/aiomongo
oauthlib
mosspy
rarfile
3 changes: 3 additions & 0 deletions vj4/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
options.define('oauth_client_id', default='', help='OAuth client id')
options.define('oauth_client_secret', default='', help='OAuth client secret')

# moss user id
options.define('moss_user_id', default=987654321, help='Stanford Moss User ID')

_logger = logging.getLogger(__name__)


Expand Down
18 changes: 16 additions & 2 deletions vj4/handler/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import datetime
import functools
import io
import logging
import pytz
import yaml
import zipfile
Expand All @@ -26,6 +27,8 @@
from vj4.util import pagination
from vj4.util.misc import filter_language, filter_content_type

_logger = logging.getLogger(__name__)


def _parse_pids(pids_str):
pid_list = list(map(document.convert_doc_id, pids_str.split(',')))
Expand Down Expand Up @@ -931,6 +934,10 @@ async def _post_homework(self, *, ctype: str, tid: objectid.ObjectId):

@app.route('/{ctype:contest|homework}/{tid}/moss', 'contest_moss')
class ContestMosstHandler(ContestMixin, base.Handler):
def split_tags(self, s):
s = s.replace(',', ',') # Chinese ', '
return list(filter(lambda _: _ != '', map(lambda _: _.strip(), s.split(','))))

@base.route_argument
async def post(self, *, ctype: str, **kwargs):
if ctype == 'homework':
Expand All @@ -947,12 +954,19 @@ async def post(self, *, ctype: str, **kwargs):
@base.post_argument
@base.require_csrf_token
@base.sanitize
async def _post_homework(self, *, ctype: str, tid: objectid.ObjectId):
async def _post_homework(self, *, ctype: str, tid: objectid.ObjectId, language: str, wildcards: str,
ignore_limit: int = 10):
tdoc = await contest.get(self.domain_id, document.TYPE_HOMEWORK, tid)
if not self.own(tdoc, builtin.PERM_EDIT_HOMEWORK_SELF):
self.check_perm(builtin.PERM_EDIT_HOMEWORK)
doc_type = constant.contest.CTYPE_TO_DOCTYPE[ctype]

rdocs = await self.get_latest_records(doc_type, tid)
data = await moss.moss_test(rdocs, language='cc')
wildcards = self.split_tags(wildcards)

_logger.info('Submit Moss for %s', tid)
moss_url = await moss.moss_test(rdocs, language=language, wildcards=wildcards)
if moss_url:
await contest.update_moss_result(self.domain_id, document.TYPE_HOMEWORK, tid, moss_url=moss_url)

self.json_or_redirect(self.reverse_url('contest_system_test', ctype=ctype, tid=tid))
4 changes: 4 additions & 0 deletions vj4/model/adaptor/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@ async def edit(domain_id: str, doc_type: int, tid: objectid.ObjectId, **kwargs):
return await document.set(domain_id, doc_type, tid, **kwargs)


async def update_moss_result(domain_id: str, doc_type: int, tid: objectid.ObjectId, moss_url: str):
return await document.set(domain_id, doc_type, tid, moss_url=moss_url)


def get_multi(domain_id: str, doc_type: int, fields=None, **kwargs):
# TODO(twd2): projection.
return document.get_multi(domain_id=domain_id,
Expand Down
46 changes: 35 additions & 11 deletions vj4/model/adaptor/moss.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from bson import objectid
from os import path, mkdir
from shutil import rmtree
from tempfile import mkdtemp
Expand All @@ -10,13 +11,18 @@
from vj4 import db
from vj4 import error
from vj4.model import fs
from vj4.util import options


# @TODO(tc-imba) the extraction is not safe now.

def extract_text_file(file_obj, dest_dir, lang):
# @TODO add extension
with open(path.join(dest_dir, 'main' + '.cpp'), mode='wb') as file:
wildcards = constant.language.LANG_MOSS_WILDCARDS.get(lang, [])
if wildcards:
name = wildcards[0].replace('*', 'main')
else:
name = 'main.txt'
with open(path.join(dest_dir, name), mode='wb') as file:
file.write(file_obj.read())


Expand All @@ -43,22 +49,40 @@ def extract_rar_file(file_obj, dest_dir, lang):
}


async def moss_test(rdocs: list, language: str):
async def moss_test(rdocs: list, language: str, ignore_limit: int = 10, wildcards: list = None):
# check the language is supported by moss
if language not in constant.language.LANG_MOSS:
raise error.LanguageNotSupportedError(language)

ignore_limit = int(ignore_limit)
if ignore_limit < 2:
raise error.InvalidArgumentError('ignore_limit')

if wildcards is None or len(wildcards) == 0:
wildcards = constant.language.LANG_MOSS_WILDCARDS.get(language, [])

moss_dir = mkdtemp(prefix='cb4.moss.')
try:
moss = Moss(options.moss_user_id, language)
moss.setDirectoryMode(1)
moss.setIgnoreLimit(ignore_limit)

for rdoc in rdocs:
grid_out = await fs.get(rdoc['code'])
file_obj = BytesIO(await grid_out.read())
dest_dir = path.join(moss_dir, str(rdoc['uid']))
mkdir(dest_dir)
if rdoc['code_type'] in EXTRACT_OPEN_FUNC:
EXTRACT_OPEN_FUNC[rdoc['code_type']](file_obj, dest_dir, language)

try:
grid_out = await fs.get(rdoc['code'])
file_obj = BytesIO(await grid_out.read())
dest_dir = path.join(moss_dir, str(rdoc['uid']))
mkdir(dest_dir)
EXTRACT_OPEN_FUNC[rdoc['code_type']](file_obj, dest_dir, language)
for wildcard in wildcards:
moss.addFilesByWildcard(path.join(dest_dir, wildcard))
except Exception as e:
print(e)

url = moss.send()
return url

finally:
print(moss_dir)
# print(moss_dir)
rmtree(moss_dir, ignore_errors=True)

56 changes: 32 additions & 24 deletions vj4/ui/constant/language.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,28 +71,36 @@ export const LANG_CODEMIRROR_MODES = {
attachObjectMeta(LANG_CODEMIRROR_MODES, 'exportToPython', false);

export const LANG_MOSS = {
c: 'c',
cc: 'cc',
java: 'java',
ml: 'ml',
pascal: 'pascal',
ada: 'ada',
lisp: 'lisp',
scheme: 'scheme',
haskell: 'haskell',
fortran: 'fortran',
ascii: 'ascii',
vhdl: 'vhdl',
perl: 'perl',
matlab: 'matlab',
python: 'python',
mips: 'mips',
prolog: 'prolog',
spice: 'spice',
vb: 'vb',
csharp: 'csharp',
modula2: 'modula2',
a8086: 'a8086',
javascript: 'javascript',
plsql: 'plsql',
c: 'C',
cc: 'C++',
java: 'Java',
// ml: 'ml',
// pascal: 'pascal',
// ada: 'ada',
// lisp: 'lisp',
// scheme: 'scheme',
// haskell: 'haskell',
// fortran: 'fortran',
// ascii: 'ascii',
// vhdl: 'vhdl',
// perl: 'perl',
matlab: 'MATLAB / GNU Octave',
python: 'Python',
// mips: 'mips',
// prolog: 'prolog',
// spice: 'spice',
// vb: 'vb',
// csharp: 'csharp',
// modula2: 'modula2',
// a8086: 'a8086',
// javascript: 'javascript',
// plsql: 'plsql',
};

export const LANG_MOSS_WILDCARDS = {
c: ['*.c', '*.h'],
cc: ['*.cpp', '*.cc', '*.c', '*.h', '*.hh', '*.hpp'],
java: ['*.java'],
matlab: ['*.m'],
python: ['*.py'],
};
18 changes: 18 additions & 0 deletions vj4/ui/pages/contest_system_test.page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NamedPage } from 'vj/misc/PageLoader';
import { LANG_MOSS_WILDCARDS } from 'vj/constant/language';

const page = new NamedPage('contest_system_test', async () => {
const $language = $('[name="language"]');
const $wildcards = $('[name="wildcards"]');

const changeWildcards = () => {
const lang = $language.val();
const wildcards = LANG_MOSS_WILDCARDS[lang] || [];
$wildcards.val(wildcards.join(', '));
};

$language.on('change', changeWildcards);
changeWildcards();
});

export default page;
10 changes: 10 additions & 0 deletions vj4/ui/templates/homework_system_test.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,18 @@ <h1 class="section__title">{{ _('Export') }}</h1>
<div class="section__header">
<h1 class="section__title">{{ _('Moss') }}</h1>
</div>
{% if 'moss_url' in tdoc %}
<div class="section__body typo">
Last result: <a target="_blank" href="{{ tdoc['moss_url'] }}">{{ tdoc['moss_url'] }}</a>
</div>
{% endif %}
<div class="section__body">
<form method="post" action="moss">
<div class="row">
{{ form.form_select2(columns=3, label='Code language', name='language', options=vj4.constant.language.LANG_MOSS.items(), row=false) }}
{{ form.form_text(columns=6, label='Filter Wildcards', help_text='Splitted by \', \'. Use default settings of the selected language if empty.', name='wildcards', value='', row=false) }}
{{ form.form_text(columns=3, label='Ignore Threshold', help_text='Ignore if a range of code appears in a number of submissions. Minimum is 2. Default is 10.', name='ignore_limit', value=10, row=false) }}
</div>
<div class="row">
<div class="columns">
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
Expand Down

0 comments on commit df97d3c

Please sign in to comment.