Skip to content

Commit 16dd35d

Browse files
authored
Merge pull request #366 from gkreitz/346_default_validator_message_truncation
Truncate long tokens in default_validator's judge message. #346
2 parents 0790fd1 + 3a8e701 commit 16dd35d

36 files changed

+121
-11
lines changed

support/default_validator/default_validator.cc

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ FILE *diffpos = NULL;
1717
int judgeans_pos = 0, stdin_pos = 0;
1818
int judgeans_line = 1, stdin_line = 1;
1919

20+
// At some point we should rewrite this to something more C++. Now that we truncate long tokens, having this
21+
// require c_str() calls gets even messier with object lifetimes.
2022
void wrong_answer(const char *err, ...) {
2123
va_list pvar;
2224
va_start(pvar, err);
@@ -64,6 +66,49 @@ FILE *openfeedback(const char *feedbackdir, const char *feedback, const char *wh
6466
return res;
6567
}
6668

69+
/* Truncate string to avoid huge messages when teams forgot to print spaces.
70+
* If string is longer than limit (plus 5, as we don't want to replace just a
71+
* few characters with ...), we truncate and append "...". String may be in
72+
* arbitrary encoding, but as utf-8 is common, we make a small attempt to avoid
73+
* cutting in a utf-8 character. So output can be a few bytes longer than limit.
74+
*/
75+
std::string truncate(const std::string &str, size_t limit = 30) {
76+
if (str.length() <= limit + 5) {
77+
return str;
78+
}
79+
size_t cut = limit;
80+
// Heuristic to avoid cutting in the middle of a UTF-8 character.
81+
// A continuation byte in UTF-8 starts with binary 10.
82+
// We scan forwards from the limit to include the rest of a character,
83+
// but at most 3 extra bytes (for up to a 4-byte character).
84+
while (cut < str.length() && cut < limit + 4 && (str[cut] & 0xC0) == 0x80) {
85+
cut++;
86+
}
87+
return str.substr(0, cut) + "...";
88+
}
89+
90+
/* Truncate a pair of strings (judge and user tokens). This preserves the first
91+
* few bytes of the common prefix, then adds ..., and then the first few bytes
92+
* starting from where the strings differ.
93+
*/
94+
std::pair<std::string, std::string> truncate_pair(const std::string &str1, const std::string &str2) {
95+
size_t diff_idx = 0;
96+
while (diff_idx < str1.length() && diff_idx < str2.length() && str1[diff_idx] == str2[diff_idx]) {
97+
diff_idx++;
98+
}
99+
100+
std::string common_prefix = str1.substr(0, diff_idx);
101+
std::string s1 = str1.substr(diff_idx);
102+
std::string s2 = str2.substr(diff_idx);
103+
104+
std::string p_part = truncate(common_prefix, 15);
105+
106+
return std::make_pair(
107+
p_part + truncate(s1, 15),
108+
p_part + truncate(s2, 15)
109+
);
110+
}
111+
67112
const char *USAGE = "Usage: %s judge_in judge_ans feedback_file [options] < user_out";
68113

69114
int main(int argc, char **argv) {
@@ -82,7 +127,7 @@ int main(int argc, char **argv) {
82127
double float_rel_tol = -1;
83128

84129
for (int a = 4; a < argc; ++a) {
85-
if (!strcmp(argv[a], "case_sensitive")) {
130+
if (!strcmp(argv[a], "case_sensitive")) {
86131
case_sensitive = true;
87132
} else if (!strcmp(argv[a], "space_change_sensitive")) {
88133
space_change_sensitive = true;
@@ -108,7 +153,7 @@ int main(int argc, char **argv) {
108153
}
109154
use_floats = float_abs_tol >= 0 || float_rel_tol >= 0;
110155

111-
std::string judge, team;
156+
std::string judge, team, judge_trunc, team_trunc;
112157
for (int token = 0; true; token++) {
113158
// Space! Can't live with it, can't live without it...
114159
while (isspace(judgeans.peek())) {
@@ -140,46 +185,57 @@ int main(int argc, char **argv) {
140185
if (!(std::cin >> team)) {
141186
if (token == 0) {
142187
if (stdin_pos == 0) {
188+
judge_trunc = truncate(judge);
143189
wrong_answer(
144190
"User EOF while judge had more output; user output was empty.\n(Next judge token: %s)",
145-
judge.c_str()
191+
judge_trunc.c_str()
146192
);
147193
} else {
194+
judge_trunc = truncate(judge);
148195
wrong_answer(
149196
"User EOF while judge had more output; user output contained only whitespace.\n(Next judge token: %s)",
150-
judge.c_str()
197+
judge_trunc.c_str()
151198
);
152199
}
153200
} else {
154-
wrong_answer("User EOF while judge had more output\n(Next judge token: %s)", judge.c_str());
201+
judge_trunc = truncate(judge);
202+
wrong_answer("User EOF while judge had more output\n(Next judge token: %s)", judge_trunc.c_str());
155203
}
156204
}
157-
205+
158206
double jval, tval;
159207
if (use_floats && isfloat(judge.c_str(), jval)) {
160208
if (!isfloat(team.c_str(), tval)) {
161-
wrong_answer("Expected float, got: %s", team.c_str());
209+
team_trunc = truncate(team);
210+
wrong_answer("Expected float, got: %s", team_trunc.c_str());
162211
}
163212
if (!(fabs(jval - tval) <= float_abs_tol) &&
164213
!(fabs(jval - tval) <= float_rel_tol * fabs(jval))) {
214+
// We don't want to truncate as a pair here, that just gets more confusing for floats (and something has
215+
// gone very wrong if we're dealing with floats so long we need to truncate anyway :)
216+
judge_trunc = truncate(judge);
217+
team_trunc = truncate(team);
165218
wrong_answer("Too large difference.\n Judge: %s\n User: %s\n Difference: %le\n (abs tol %le rel tol %le)",
166-
judge.c_str(), team.c_str(), jval-tval, float_abs_tol, float_rel_tol);
219+
judge_trunc.c_str(), team_trunc.c_str(), jval-tval, float_abs_tol, float_rel_tol);
167220
}
168221
} else if (case_sensitive) {
169222
if (strcmp(judge.c_str(), team.c_str()) != 0) {
170-
wrong_answer("String tokens mismatch\nJudge: \"%s\"\nUser: \"%s\"", judge.c_str(), team.c_str());
223+
std::tie(judge_trunc, team_trunc) = truncate_pair(judge, team);
224+
wrong_answer("String tokens mismatch\nJudge: \"%s\"\nUser: \"%s\"", judge_trunc.c_str(), team_trunc.c_str());
171225
}
172226
} else {
173227
if (strcasecmp(judge.c_str(), team.c_str()) != 0) {
174-
wrong_answer("String tokens mismatch\nJudge: \"%s\"\nUser: \"%s\"", judge.c_str(), team.c_str());
228+
std::tie(judge_trunc, team_trunc) = truncate_pair(judge, team);
229+
wrong_answer("String tokens mismatch\nJudge: \"%s\"\nUser: \"%s\"", judge_trunc.c_str(), team_trunc.c_str());
175230
}
176231
}
177232
judgeans_pos += judge.length();
178233
stdin_pos += team.length();
179234
}
180235

181236
if (std::cin >> team) {
182-
wrong_answer("Trailing output:\n%s", team.c_str());
237+
team_trunc = truncate(team);
238+
wrong_answer("Trailing output:\n%s", team_trunc.c_str());
183239
}
184240

185241
exit(EXIT_AC);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
43
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Wrong answer on line 1 of output (corresponding to line 1 in answer file)
2+
User EOF while judge had more output; user output was empty.
3+
(Next judge token: Hello)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello World!

tests/default_validator_tests/test_empty_wa/user.out

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
float_absolute_tolerance 0.5
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
43
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Wrong answer on line 1 of output (corresponding to line 1 in answer file)
2+
Expected float, got: 1.4🚀🚀🚀🚀🚀🚀🚀...
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.4🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀

0 commit comments

Comments
 (0)