Browse Source

Add custom test

pull/6/head
Menci 7 years ago
parent
commit
5df3e94421
  1. 3
      models/judge_state.js
  2. 7
      models/waiting_judge.js
  3. 53
      modules/api_v2.js
  4. 61
      modules/problem.js
  5. 3
      static/style.css
  6. 30
      views/custom_test_content.ejs
  7. 86
      views/problem.ejs
  8. 3
      views/util.ejs

3
models/judge_state.js

@ -217,7 +217,8 @@ class JudgeState extends Model {
let WaitingJudge = syzoj.model('waiting_judge');
let waiting_judge = await WaitingJudge.create({
judge_id: this.id,
priority: 2
priority: 2,
type: 'submission'
});
await waiting_judge.save();

7
models/waiting_judge.js

@ -29,7 +29,12 @@ let model = db.define('waiting_judge', {
judge_id: { type: Sequelize.INTEGER },
// Smaller is higher
priority: { type: Sequelize.INTEGER }
priority: { type: Sequelize.INTEGER },
type: {
type: Sequelize.ENUM,
values: ['submission', 'custom-test']
}
}, {
timestamps: false,
tableName: 'waiting_judge',

53
modules/api_v2.js

@ -89,30 +89,32 @@ app.apiRouter.post('/api/v2/judge/peek', async (req, res) => {
let WaitingJudge = syzoj.model('waiting_judge');
let JudgeState = syzoj.model('judge_state');
let judge_state;
let judge_state, custom_test;
await syzoj.utils.lock('/api/v2/judge/peek', async () => {
let waiting_judge = await WaitingJudge.findOne({ order: [['priority', 'ASC'], ['id', 'ASC']] });
if (!waiting_judge) {
return;
}
judge_state = await waiting_judge.getJudgeState();
await judge_state.loadRelationships();
await judge_state.problem.loadRelationships();
if (waiting_judge.type === 'submission') {
judge_state = await waiting_judge.getJudgeState();
await judge_state.loadRelationships();
} else {
custom_test = await waiting_judge.getCustomTest();
await custom_test.loadRelationships();
}
await waiting_judge.destroy();
});
if (judge_state) {
await judge_state.loadRelationships();
await judge_state.problem.loadRelationships();
if (judge_state.problem.type === 'submit-answer') {
res.send({
have_task: 1,
judge_id: judge_state.id,
answer_file: judge_state.code,
testdata: judge_state.problem.id,
problem_type: judge_state.problem.type
problem_type: judge_state.problem.type,
type: 'submission'
});
} else {
res.send({
@ -126,9 +128,25 @@ app.apiRouter.post('/api/v2/judge/peek', async (req, res) => {
file_io: judge_state.problem.file_io,
file_io_input_name: judge_state.problem.file_io_input_name,
file_io_output_name: judge_state.problem.file_io_output_name,
problem_type: judge_state.problem.type
problem_type: judge_state.problem.type,
type: 'submission'
});
}
} else if (custom_test) {
console.log({
have_task: 1,
judge_id: custom_test.id,
code: custom_test.code,
language: custom_test.language,
testdata: custom_test.problem.id,
time_limit: custom_test.problem.time_limit,
memory_limit: custom_test.problem.memory_limit,
file_io: custom_test.problem.file_io,
file_io_input_name: custom_test.problem.file_io_input_name,
file_io_output_name: custom_test.problem.file_io_output_name,
problem_type: custom_test.problem.type,
type: 'custom_test'
});
} else {
res.send({ have_task: 0 });
}
@ -141,11 +159,18 @@ app.apiRouter.post('/api/v2/judge/update/:id', async (req, res) => {
try {
if (req.query.session_id !== syzoj.config.judge_token) return res.status(404).send({ err: 'Permission denied' });
let JudgeState = syzoj.model('judge_state');
let judge_state = await JudgeState.fromID(req.params.id);
await judge_state.updateResult(JSON.parse(req.body.result));
await judge_state.save();
await judge_state.updateRelatedInfo();
if (req.body.type === 'custom-test') {
let CustomTest = syzoj.model('custom_test');
let custom_test = CustomTest.fromID(req.params.id);
await custom_test.updateResult(JSON.parse(req.body.result));
await custom_test.save();
} else if (req.body.type === 'submission') {
let JudgeState = syzoj.model('judge_state');
let judge_state = await JudgeState.fromID(req.params.id);
await judge_state.updateResult(JSON.parse(req.body.result));
await judge_state.save();
await judge_state.updateRelatedInfo();
}
res.send({ return: 0 });
} catch (e) {

61
modules/problem.js

@ -21,6 +21,7 @@
let Problem = syzoj.model('problem');
let JudgeState = syzoj.model('judge_state');
let CustomTest = syzoj.model('custom_test');
let WaitingJudge = syzoj.model('waiting_judge');
let Contest = syzoj.model('contest');
let ProblemTag = syzoj.model('problem_tag');
@ -638,7 +639,8 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1
let waiting_judge = await WaitingJudge.create({
judge_id: judge_state.id,
priority: 1
priority: 1,
type: 'submission'
});
await waiting_judge.save();
@ -817,3 +819,60 @@ app.get('/problem/:id/statistics/:type', async (req, res) => {
});
}
});
app.post('/problem/:id/custom-test', app.multer.fields([{ name: 'code_upload', maxCount: 1 }, { name: 'input_file', maxCount: 1 }]), async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': syzoj.utils.makeUrl(['problem', id]) }) });
if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
let filepath;
if (req.files['input_file'][0]) {
if (req.files['input_file'][0].size > syzoj.config.limit.custom_test_input) throw new ErrorMessage('输入数据过长。');
filepath = req.files['input_file'][0].path;
} else {
if (req.body.input_file_textarea.length > syzoj.config.limit.custom_test_input) throw new ErrorMessage('输入数据过长。');
filepath = await require('tmp-promise').tmpName({ template: '/tmp/tmp-XXXXXX' });
await require('fs-extra').writeFileAsync(filepath, req.body.input_file_textarea);
}
let code;
if (req.files['code_upload'][0]) {
if (req.files['code_upload'][0].size > syzoj.config.limit.submit_code) throw new ErrorMessage('代码过长。');
code = (await require('fs-extra').readFileAsync(req.files['code_upload'][0].path)).toString();
} else {
if (req.body.code.length > syzoj.config.limit.submit_code) throw new ErrorMessage('代码过长。');
code = req.body.code;
}
let custom_test = await CustomTest.create({
input_filepath: filepath,
code: code,
language: req.body.language,
user_id: res.locals.user.id,
problem_id: id
});
await custom_test.save();
let waiting_judge = await WaitingJudge.create({
judge_id: custom_test.id,
priority: 3,
type: 'custom_test'
});
await waiting_judge.save();
res.send({
id: custom_test.id
});
} catch (e) {
syzoj.log(e);
res.send({
err: e
});
}
});

3
static/style.css

@ -361,6 +361,9 @@ body > .ui.page.dimmer {
}
*/
:not(.status_detail).status.success,
.title:hover .status_detail.status.success,
.title.active .status_detail.status.success,
:not(.status_detail).status.submitted,
.title:hover .status_detail.status.submitted,
.title.active .status_detail.status.submitted,

30
views/custom_test_content.ejs

@ -0,0 +1,30 @@
<% include util %>
<div style="padding-bottom: 1.5rem; ">
<div class="ui styled fluid accordion" id="custom-test-accordion">
<div class="title"<% if (custom_test.pending) { %> style="cursor: auto; "<% } %>>
<div class="ui grid">
<div class="four wide column"><i class="dropdown icon"></i>自定义测试</div>
<div class="four wide column status status_detail <%= getStatusMeta(custom_test.status).toLowerCase().split(' ').join('_') %>">
<i class="<%= icon[getStatusMeta(custom_test.status)] || 'remove' %> icon"></i>
<%= custom_test.status %></div>
<% if (!custom_test.pending) { %>
<div class="four wide column">用时:<span style="font-weight: normal; "><%= custom_test.time %> ms</span></div>
<div class="four wide column">内存:<span style="font-weight: normal; "><%= custom_test.memory %> KiB</span></div>
<% } %>
</div>
</div>
<% if (!custom_test.pending) { %>
<div class="content">
<strong>选手输出</strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= custom_test.result.user_out %></code></pre></div>
</div>
<% } %>
</div>
<% if (!custom_test.pending) { %>
<script>
$(function () {
$('#custom-test-accordion').accordion();
});
</script>
<% } %>
</div>

86
views/problem.ejs

@ -300,6 +300,7 @@ div[class*=ace_br] {
</div>
</div>
</div>
<div class="ui center aligned vertical segment" style="padding-bottom: 0; "><button type="submit" class="ui button">提交</button></div>
<% } else { %>
<input name="language" type="hidden" id="form">
<input name="code" type="hidden">
@ -330,8 +331,11 @@ div[class*=ace_br] {
</div>
</div>
</div>
<div class="ui center aligned vertical segment" style="padding-bottom: 0; ">
<button type="submit" class="ui button">提交</button>
<div onclick="$('#modal-custom-test').modal('show')" class="ui positive button">自定义测试</div>
</div>
<% } %>
<div class="ui center aligned vertical segment" style="padding-bottom: 0; "><button type="submit" class="ui button">提交</button></div>
</form>
</div>
</div>
@ -376,6 +380,86 @@ div[class*=ace_br] {
});
});
</script>
<div class="ui modal" id="modal-custom-test">
<div class="header">
自定义测试
</div>
<div class="content" style="padding-bottom: 0; ">
<div class="ui form">
<form id="form-custom-test" action="<%= syzoj.utils.makeUrl(['problem', problem.id, 'custom-test']) %>">
<input type="hidden" name="code">
<input type="hidden" name="language">
<div class="field">
<label>输入文件</label>
<textarea name="input_file_textarea" style="font-family: 'Roboto Mono', 'Bitstream Vera Sans Mono', 'Menlo', 'Consolas', 'Lucida Console', monospace; "></textarea>
</div>
<div class="inline fields" style="width: 100%; ">
<div class="field" style="margin: 0 auto; ">
<label for="answer">或者,上传输入文件</label>
<input type="file" name="input_file">
</div>
</div>
</form>
</div>
<div id="custom-test-result"></div>
</div>
<div class="actions" style="text-align: center; ">
<div class="ui green button" id="submit-custom-test">提交</div>
</div>
</div>
<script>
var custom_test_id;
$('#submit-custom-test').click(function () {
$('#submit-custom-test').addClass('disabled');
var form = document.getElementById('form-custom-test');
$(form).find('[name=language]').val($('#languages-menu .item.active').data('value'));
$(form).find('[name=code]').val(editor.getValue());
var code_upload = $('#answer')[0].cloneNode(true);
code_upload.style.display = 'none';
code_upload.name = 'code_upload';
form.appendChild(code_upload);
$.ajax({
url: form.action,
type: 'post',
data: new FormData(form),
cache: false,
contentType: false,
processData: false,
success: function (data) {
custom_test_id = data.id;
update_custom_test_result();
}
});
form.removeChild(code_upload);
});
function update_custom_test_result() {
$.get('/custom-test/' + custom_test_id + '/ajax', function (data) {
if (data.err) {
alert(data.err);
}
if ($('#custom-test-result').html() !== data.html) {
$('#custom-test-result').html(data.html);
}
if (data.pending) {
setTimeout(function () {
update_custom_test_result();
}, 500);
} else {
$('#submit-custom-test').removeClass('disabled');
}
});
}
</script>
<% } else { %>
<script>
function submit_code() {

3
views/util.ejs

@ -19,6 +19,7 @@ this.alpha = number => {
this.icon = {
'Accepted': 'checkmark',
'Success': 'checkmark', // Custom test
'Wrong Answer': 'remove',
'Runtime Error': 'bomb',
'Time Limit Exceeded': 'clock',
@ -29,7 +30,7 @@ this.icon = {
'Running': 'spinner',
'Compiling': 'spinner',
'Compile Error': 'code',
'Submitted': 'checkmark',
'Submitted': 'checkmark', // NOI contests
'System Error': 'server',
'No Testdata': 'folder open outline',
'Partially Correct': 'minus',

Loading…
Cancel
Save