Description:
[grader] added memory usage status report, better running time report git-svn-id: http://theory.cpe.ku.ac.th/grader/judge/trunk/scripts@165 6386c4cd-e34a-4fa8-8920-d93eb39b512e
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r42:9669362454bd - - 6 files changed: 747 inserted, 573 deleted

This diff has been collapsed as it changes many lines, (663 lines changed) Show them Hide them
@@ -0,0 +1,663
1 + /*
2 + * A Simple Testing Sandbox
3 + *
4 + * (c) 2001--2004 Martin Mares <mj@ucw.cz>
5 + */
6 +
7 + #define _LARGEFILE64_SOURCE
8 + //#define _GNU_SOURCE
9 +
10 + #include <errno.h>
11 + #include <stdio.h>
12 + #include <fcntl.h>
13 + #include <stdlib.h>
14 + #include <string.h>
15 + #include <stdarg.h>
16 + #include <unistd.h>
17 + #include <getopt.h>
18 + #include <time.h>
19 + #include <sys/wait.h>
20 + #include <sys/user.h>
21 + #include <sys/time.h>
22 + #include <sys/ptrace.h>
23 + #include <sys/signal.h>
24 + #include <sys/sysinfo.h>
25 + #include <sys/syscall.h>
26 + #include <sys/resource.h>
27 +
28 + #define NONRET __attribute__((noreturn))
29 + #define UNUSED __attribute__((unused))
30 +
31 + static int filter_syscalls; /* 0=off, 1=liberal, 2=totalitarian */
32 + static int timeout;
33 + static int pass_environ;
34 + static int use_wall_clock;
35 + static int file_access;
36 + static int verbose;
37 + static int memory_limit;
38 + static int allow_times;
39 + static char *redir_stdin, *redir_stdout;
40 + static char *set_cwd;
41 +
42 + static pid_t box_pid;
43 + static int is_ptraced;
44 + static volatile int timer_tick;
45 + static time_t start_time;
46 + static int ticks_per_sec;
47 +
48 + #if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ > 0
49 + /* glibc 2.1 or newer -> has lseek64 */
50 + #define long_seek(f,o,w) lseek64(f,o,w)
51 + #else
52 + /* Touching clandestine places in glibc */
53 + extern loff_t llseek(int fd, loff_t pos, int whence);
54 + #define long_seek(f,o,w) llseek(f,o,w)
55 + #endif
56 +
57 + static void NONRET
58 + box_exit(void)
59 + {
60 + if (box_pid > 0)
61 + {
62 + if (is_ptraced)
63 + ptrace(PTRACE_KILL, box_pid);
64 + kill(-box_pid, SIGKILL);
65 + kill(box_pid, SIGKILL);
66 + }
67 + exit(1);
68 + }
69 +
70 + static void
71 + box_kill(void)
72 + {
73 + if (box_pid > 0)
74 + {
75 + if (is_ptraced)
76 + ptrace(PTRACE_KILL, box_pid);
77 + kill(-box_pid, SIGKILL);
78 + kill(box_pid, SIGKILL);
79 + }
80 + }
81 +
82 + static void NONRET __attribute__((format(printf,1,2)))
83 + die(char *msg, ...)
84 + {
85 + va_list args;
86 + va_start(args, msg);
87 + vfprintf(stderr, msg, args);
88 + fputc('\n', stderr);
89 + box_exit();
90 + }
91 +
92 + static void __attribute__((format(printf,1,2)))
93 + die_report(char *msg, ...)
94 + {
95 + va_list args;
96 + va_start(args, msg);
97 + vfprintf(stderr, msg, args);
98 + fputc('\n', stderr);
99 + box_kill();
100 + }
101 +
102 + static void __attribute__((format(printf,1,2)))
103 + log(char *msg, ...)
104 + {
105 + va_list args;
106 + va_start(args, msg);
107 + if (verbose)
108 + {
109 + vfprintf(stderr, msg, args);
110 + fflush(stderr);
111 + }
112 + va_end(args);
113 + }
114 +
115 + static void
116 + valid_filename(unsigned long addr)
117 + {
118 + char namebuf[4096], *p, *end;
119 + static int mem_fd;
120 +
121 + if (!file_access)
122 + die("File access forbidden.");
123 + if (file_access >= 9)
124 + return;
125 +
126 + if (!mem_fd)
127 + {
128 + sprintf(namebuf, "/proc/%d/mem", (int) box_pid);
129 + mem_fd = open(namebuf, O_RDONLY);
130 + if (mem_fd < 0)
131 + die("open(%s): %m", namebuf);
132 + }
133 + p = end = namebuf;
134 + do
135 + {
136 + if (p >= end)
137 + {
138 + int remains = PAGE_SIZE - (addr & (PAGE_SIZE-1));
139 + int l = namebuf + sizeof(namebuf) - end;
140 + if (l > remains)
141 + l = remains;
142 + if (!l)
143 + die("Access to file with name too long.");
144 + if (long_seek(mem_fd, addr, SEEK_SET) < 0)
145 + die("long_seek(mem): %m");
146 + remains = read(mem_fd, end, l);
147 + if (remains < 0)
148 + die("read(mem): %m");
149 + if (!remains)
150 + die("Access to file with name out of memory.");
151 + end += l;
152 + addr += l;
153 + }
154 + }
155 + while (*p++);
156 +
157 + log("[%s] ", namebuf);
158 + if (file_access >= 3)
159 + return;
160 + if (!strchr(namebuf, '/') && strcmp(namebuf, ".."))
161 + return;
162 + if (file_access >= 2)
163 + {
164 + if ((!strncmp(namebuf, "/etc/", 5) ||
165 + !strncmp(namebuf, "/lib/", 5) ||
166 + !strncmp(namebuf, "/usr/lib/", 9))
167 + && !strstr(namebuf, ".."))
168 + return;
169 + if (!strcmp(namebuf, "/dev/null") ||
170 + !strcmp(namebuf, "/dev/zero") ||
171 + !strcmp(namebuf, "/proc/meminfo") ||
172 + !strcmp(namebuf, "/proc/self/stat") ||
173 + !strncmp(namebuf, "/usr/share/zoneinfo/", 20))
174 + return;
175 + }
176 + die("Forbidden access to file `%s'.", namebuf);
177 + }
178 +
179 + static int
180 + valid_syscall(struct user *u)
181 + {
182 + switch (u->regs.orig_eax)
183 + {
184 + case __NR_execve:
185 + {
186 + static int exec_counter;
187 + return !exec_counter++;
188 + }
189 + case __NR_open:
190 + case __NR_creat:
191 + case __NR_unlink:
192 + case __NR_oldstat:
193 + case __NR_access:
194 + case __NR_oldlstat:
195 + case __NR_truncate:
196 + case __NR_stat:
197 + case __NR_lstat:
198 + case __NR_truncate64:
199 + case __NR_stat64:
200 + case __NR_lstat64:
201 + valid_filename(u->regs.ebx);
202 + return 1;
203 + case __NR_exit:
204 + case __NR_read:
205 + case __NR_write:
206 + case __NR_close:
207 + case __NR_lseek:
208 + case __NR_getpid:
209 + case __NR_getuid:
210 + case __NR_oldfstat:
211 + case __NR_dup:
212 + case __NR_brk:
213 + case __NR_getgid:
214 + case __NR_geteuid:
215 + case __NR_getegid:
216 + case __NR_dup2:
217 + case __NR_ftruncate:
218 + case __NR_fstat:
219 + case __NR_personality:
220 + case __NR__llseek:
221 + case __NR_readv:
222 + case __NR_writev:
223 + case __NR_getresuid:
224 + #ifdef __NR_pread64
225 + case __NR_pread64:
226 + case __NR_pwrite64:
227 + #else
228 + case __NR_pread:
229 + case __NR_pwrite:
230 + #endif
231 + case __NR_ftruncate64:
232 + case __NR_fstat64:
233 + case __NR_fcntl:
234 + case __NR_fcntl64:
235 + case __NR_mmap:
236 + case __NR_munmap:
237 + case __NR_ioctl:
238 + case __NR_uname:
239 + case 252:
240 + case 243:
241 + return 1;
242 + // case __NR_time:
243 + case __NR_alarm:
244 + // case __NR_pause:
245 + case __NR_signal:
246 + case __NR_fchmod:
247 + case __NR_sigaction:
248 + case __NR_sgetmask:
249 + case __NR_ssetmask:
250 + case __NR_sigsuspend:
251 + case __NR_sigpending:
252 + case __NR_getrlimit:
253 + case __NR_getrusage:
254 + case __NR_gettimeofday:
255 + case __NR_select:
256 + case __NR_readdir:
257 + case __NR_setitimer:
258 + case __NR_getitimer:
259 + case __NR_sigreturn:
260 + case __NR_mprotect:
261 + case __NR_sigprocmask:
262 + case __NR_getdents:
263 + case __NR_getdents64:
264 + case __NR__newselect:
265 + case __NR_fdatasync:
266 + case __NR_mremap:
267 + case __NR_poll:
268 + case __NR_getcwd:
269 + case __NR_nanosleep:
270 + case __NR_rt_sigreturn:
271 + case __NR_rt_sigaction:
272 + case __NR_rt_sigprocmask:
273 + case __NR_rt_sigpending:
274 + case __NR_rt_sigtimedwait:
275 + case __NR_rt_sigqueueinfo:
276 + case __NR_rt_sigsuspend:
277 + case __NR_mmap2:
278 + case __NR__sysctl:
279 + return (filter_syscalls == 1);
280 + case __NR_times:
281 + return allow_times;
282 + case __NR_kill:
283 + if (u->regs.ebx == box_pid)
284 + die("Commited suicide by signal %d.", (int)u->regs.ecx);
285 + return 0;
286 + default:
287 + return 0;
288 + }
289 + }
290 +
291 + static void
292 + signal_alarm(int unused UNUSED)
293 + {
294 + /* Time limit checks are synchronous, so we only schedule them there. */
295 + timer_tick = 1;
296 +
297 + //NOTE: do not use alarm, changed to setitimer for precision
298 + // alarm(1);
299 + }
300 +
301 + static void
302 + signal_int(int unused UNUSED)
303 + {
304 + /* Interrupts are fatal, so no synchronization requirements. */
305 + die("Interrupted.");
306 + }
307 +
308 + static void
309 + check_timeout(void)
310 + {
311 + int sec;
312 +
313 + if (use_wall_clock)
314 + sec = time(NULL) - start_time;
315 + else
316 + {
317 + char buf[4096], *x;
318 + int c, utime, stime;
319 + static int proc_status_fd;
320 + if (!proc_status_fd)
321 + {
322 + sprintf(buf, "/proc/%d/stat", (int) box_pid);
323 + proc_status_fd = open(buf, O_RDONLY);
324 + if (proc_status_fd < 0)
325 + die("open(%s): %m", buf);
326 + }
327 + lseek(proc_status_fd, 0, SEEK_SET);
328 + if ((c = read(proc_status_fd, buf, sizeof(buf)-1)) < 0)
329 + die("read on /proc/$pid/stat: %m");
330 + if (c >= (int) sizeof(buf) - 1)
331 + die("/proc/$pid/stat too long");
332 + buf[c] = 0;
333 + x = buf;
334 + while (*x && *x != ' ')
335 + x++;
336 + while (*x == ' ')
337 + x++;
338 + if (*x++ != '(')
339 + die("proc syntax error 1");
340 + while (*x && (*x != ')' || x[1] != ' '))
341 + x++;
342 + while (*x == ')' || *x == ' ')
343 + x++;
344 + if (sscanf(x, "%*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %d", &utime, &stime) != 2)
345 + die("proc syntax error 2");
346 + //printf("%s - %d\n",x,ticks_per_sec);
347 + sec = (utime + stime + ticks_per_sec-1)/ticks_per_sec;
348 + }
349 + if (verbose > 1)
350 + fprintf(stderr, "[timecheck: %d seconds]\n", sec);
351 + if (sec > timeout) {
352 + die_report("Time limit exceeded.");
353 + }
354 + }
355 +
356 + int max_mem_used = 0;
357 +
358 + static void
359 + check_memory_usage()
360 + {
361 + char proc_fname[100];
362 + sprintf(proc_fname,"/proc/%d/statm",box_pid);
363 + //printf("proc fname: %s\n",proc_fname);
364 + FILE *fp = fopen(proc_fname,"r");
365 + if(fp!=NULL) {
366 + char line[1000];
367 + fgets(line,999,fp);
368 + //printf("%s\n",line);
369 + int m;
370 +
371 + if(sscanf(line,"%d",&m)==1)
372 + if(m>max_mem_used)
373 + max_mem_used = m;
374 +
375 + fclose(fp);
376 + }
377 + }
378 +
379 + static void
380 + boxkeeper(void)
381 + {
382 + int syscall_count = 0;
383 + struct sigaction sa;
384 +
385 + is_ptraced = 1;
386 + bzero(&sa, sizeof(sa));
387 + sa.sa_handler = signal_int;
388 + sigaction(SIGINT, &sa, NULL);
389 + start_time = time(NULL);
390 + ticks_per_sec = sysconf(_SC_CLK_TCK);
391 + if (ticks_per_sec <= 0)
392 + die("Invalid ticks_per_sec!");
393 +
394 + check_memory_usage();
395 +
396 + sa.sa_handler = signal_alarm;
397 + sigaction(SIGALRM, &sa, NULL);
398 + //alarm(1);
399 +
400 + struct itimerval val;
401 + val.it_interval.tv_sec = 0;
402 + val.it_interval.tv_usec = 50000;
403 + val.it_value.tv_sec = 0;
404 + val.it_value.tv_usec = 50000;
405 + setitimer(ITIMER_REAL,&val,NULL);
406 +
407 + /*
408 + --- add alarm handler no matter what..
409 + if (timeout)
410 + {
411 + sa.sa_handler = signal_alarm;
412 + sigaction(SIGALRM, &sa, NULL);
413 + alarm(1);
414 + }
415 + */
416 +
417 + for(;;)
418 + {
419 + struct rusage rus;
420 + int stat;
421 + pid_t p;
422 +
423 + if (timer_tick)
424 + {
425 + check_timeout();
426 + timer_tick = 0;
427 + }
428 + p = wait4(box_pid, &stat, WUNTRACED, &rus);
429 +
430 + if(!WIFEXITED(stat)) {
431 + // printf("CHECKING\n");
432 + check_memory_usage();
433 + }
434 +
435 + if (p < 0)
436 + {
437 + if (errno == EINTR)
438 + continue;
439 + die("wait4: %m");
440 + }
441 + if (p != box_pid)
442 + die("wait4: unknown pid %d exited!", p);
443 + if (WIFEXITED(stat))
444 + {
445 + struct timeval total;
446 + int wall;
447 + wall = time(NULL) - start_time;
448 + timeradd(&rus.ru_utime, &rus.ru_stime, &total);
449 +
450 + box_pid = 0;
451 + if (WEXITSTATUS(stat))
452 + fprintf(stderr,"Exited with error status %d.", WEXITSTATUS(stat));
453 + else if ((use_wall_clock ? wall : total.tv_sec) > timeout)
454 + fprintf(stderr,"Time limit exceeded.");
455 + else
456 + // report OK and statistics
457 + fprintf(stderr,"OK\n");
458 +
459 + fprintf(stderr,"%dr%.4lfu%.4lfs%dm\n",
460 + wall,
461 + (double) rus.ru_utime.tv_sec +
462 + ((double) rus.ru_utime.tv_usec/1000000.0),
463 + (double) rus.ru_stime.tv_sec +
464 + ((double) rus.ru_stime.tv_usec/1000000.0),
465 + max_mem_used);
466 + /*
467 + (%.4lf sec real (%d), %d sec wall, %d syscalls, %d kb)\n",
468 + (double) total.tv_sec + ((double)total.tv_usec / 1000000.0),
469 + (int) total.tv_usec,
470 + wall,
471 + syscall_count,
472 + max_mem_used);
473 + */
474 + exit(0);
475 + }
476 + if (WIFSIGNALED(stat))
477 + {
478 + box_pid = 0;
479 + fprintf(stderr,"Caught fatal signal %d.", WTERMSIG(stat));
480 +
481 + struct timeval total;
482 + int wall;
483 + wall = time(NULL) - start_time;
484 + timeradd(&rus.ru_utime, &rus.ru_stime, &total);
485 + fprintf(stderr,"%dr%.4lfu%.4lfs%dm\n",
486 + wall,
487 + (double) rus.ru_utime.tv_sec +
488 + ((double) rus.ru_utime.tv_usec/1000000.0),
489 + (double) rus.ru_stime.tv_sec +
490 + ((double) rus.ru_stime.tv_usec/1000000.0),
491 + max_mem_used);
492 + exit(0);
493 + }
494 + if (WIFSTOPPED(stat))
495 + {
496 + int sig = WSTOPSIG(stat);
497 + if (sig == SIGTRAP)
498 + {
499 + struct user u;
500 + static int stop_count = -1;
501 + if (ptrace(PTRACE_GETREGS, box_pid, NULL, &u) < 0)
502 + die("ptrace(PTRACE_GETREGS): %m");
503 + stop_count++;
504 + if (!stop_count) /* Traceme request */
505 + log(">> Traceme request caught\n");
506 + else if (stop_count & 1) /* Syscall entry */
507 + {
508 + log(">> Syscall %3ld (%08lx,%08lx,%08lx) ", u.regs.orig_eax, u.regs.ebx, u.regs.ecx, u.regs.edx);
509 + syscall_count++;
510 + if (!valid_syscall(&u))
511 + {
512 + /*
513 + * Unfortunately, PTRACE_KILL kills _after_ the syscall completes,
514 + * so we have to change it to something harmless (e.g., an undefined
515 + * syscall) and make the program continue.
516 + */
517 + unsigned int sys = u.regs.orig_eax;
518 + u.regs.orig_eax = 0xffffffff;
519 + if (ptrace(PTRACE_SETREGS, box_pid, NULL, &u) < 0)
520 + die("ptrace(PTRACE_SETREGS): %m");
521 + die("Forbidden syscall %d.", sys);
522 + }
523 + }
524 + else /* Syscall return */
525 + log("= %ld\n", u.regs.eax);
526 + ptrace(PTRACE_SYSCALL, box_pid, 0, 0);
527 + }
528 + else if (sig != SIGSTOP && sig != SIGXCPU && sig != SIGXFSZ)
529 + {
530 + log(">> Signal %d\n", sig);
531 + ptrace(PTRACE_SYSCALL, box_pid, 0, sig);
532 + }
533 + else
534 + die("Received signal %d.", sig);
535 + }
536 + else
537 + die("wait4: unknown status %x, giving up!", stat);
538 + }
539 + }
540 +
541 + static void
542 + box_inside(int argc, char **argv)
543 + {
544 + struct rlimit rl;
545 + char *args[argc+1];
546 + char *env[1] = { NULL };
547 +
548 + memcpy(args, argv, argc * sizeof(char *));
549 + args[argc] = NULL;
550 + if (set_cwd && chdir(set_cwd))
551 + die("chdir: %m");
552 + if (redir_stdin)
553 + {
554 + close(0);
555 + if (open(redir_stdin, O_RDONLY) != 0)
556 + die("open(\"%s\"): %m", redir_stdin);
557 + }
558 + if (redir_stdout)
559 + {
560 + close(1);
561 + if (open(redir_stdout, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1)
562 + die("open(\"%s\"): %m", redir_stdout);
563 + }
564 + dup2(1, 2);
565 + setpgrp();
566 + if (memory_limit)
567 + {
568 + rl.rlim_cur = rl.rlim_max = memory_limit * 1024;
569 + if (setrlimit(RLIMIT_AS, &rl) < 0)
570 + die("setrlimit: %m");
571 + }
572 + rl.rlim_cur = rl.rlim_max = 64;
573 + if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
574 + die("setrlimit: %m");
575 + if (filter_syscalls && ptrace(PTRACE_TRACEME) < 0)
576 + die("ptrace(PTRACE_TRACEME): %m");
577 + execve(args[0], args, (pass_environ ? environ : env));
578 + die("execve(\"%s\"): %m", args[0]);
579 + }
580 +
581 + static void
582 + usage(void)
583 + {
584 + fprintf(stderr, "Invalid arguments!\n");
585 + printf("\
586 + Usage: box [<options>] -- <command> <arguments>\n\
587 + \n\
588 + Options:\n\
589 + -a <level>\tSet file access level (0=none, 1=cwd, 2=/etc,/lib,..., 3=whole fs, 9=no checks; needs -f)\n\
590 + -c <dir>\tChange directory to <dir> first\n\
591 + -e\t\tPass full environment of parent process\n\
592 + -f\t\tFilter system calls (-ff=very restricted)\n\
593 + -i <file>\tRedirect stdin from <file>\n\
594 + -m <size>\tLimit address space to <size> KB\n\
595 + -o <file>\tRedirect stdout to <file>\n\
596 + -t <time>\tStop after <time> seconds\n\
597 + -T\t\tAllow syscalls for measuring run time\n\
598 + -v\t\tBe verbose\n\
599 + -w\t\tMeasure wall clock time instead of run time\n\
600 + ");
601 + exit(1);
602 + }
603 +
604 + int
605 + main(int argc, char **argv)
606 + {
607 + int c;
608 + uid_t uid;
609 +
610 + while ((c = getopt(argc, argv, "a:c:efi:m:o:t:Tvw")) >= 0)
611 + switch (c)
612 + {
613 + case 'a':
614 + file_access = atol(optarg);
615 + break;
616 + case 'c':
617 + set_cwd = optarg;
618 + break;
619 + case 'e':
620 + pass_environ = 1;
621 + break;
622 + case 'f':
623 + filter_syscalls++;
624 + break;
625 + case 'i':
626 + redir_stdin = optarg;
627 + break;
628 + case 'm':
629 + memory_limit = atol(optarg);
630 + break;
631 + case 'o':
632 + redir_stdout = optarg;
633 + break;
634 + case 't':
635 + timeout = atol(optarg);
636 + break;
637 + case 'T':
638 + allow_times++;
639 + break;
640 + case 'v':
641 + verbose++;
642 + break;
643 + case 'w':
644 + use_wall_clock = 1;
645 + break;
646 + default:
647 + usage();
648 + }
649 + if (optind >= argc)
650 + usage();
651 +
652 + uid = geteuid();
653 + if (setreuid(uid, uid) < 0)
654 + die("setreuid: %m");
655 + box_pid = fork();
656 + if (box_pid < 0)
657 + die("fork: %m");
658 + if (!box_pid)
659 + box_inside(argc-optind, argv+optind);
660 + else
661 + boxkeeper();
662 + die("Internal error: fell over edge of the world");
663 + }
@@ -0,0 +1,29
1 + /*
2 + LANG: C
3 + */
4 + #include <stdio.h>
5 + #include <stdlib.h>
6 +
7 + int main()
8 + {
9 + int a,b,d;
10 + int i,j;
11 + char *c = malloc(100000);
12 +
13 + scanf("%d %d",&a,&b);
14 + d = a+b;
15 + // printf("%d\n",a+b);
16 + for(j=0; j<1; j++)
17 + for(i=0; i<100000000; i++) {
18 + b+=a;
19 + a++;
20 + }
21 + if((c!=NULL) || (b<100))
22 + b++;
23 + if(b==100)
24 + printf("hello");
25 + else
26 + printf("%d\n",d);
27 + return 0;
28 + }
29 +
@@ -27,180 +27,189
27 27 def find_problem_home(test_request)
28 28 problem_name = test_request.problem_name
29 29
30 30 template_dir = "#{@config.test_request_problem_templates_dir}/" + problem_name
31 31
32 32 raise "Test Request: error template not found" if !File.exists?(template_dir)
33 33
34 34 problem_home = problem_home_dir(test_request)
35 35 FileUtils.mkdir_p(problem_home)
36 36
37 37 copy_problem_template(template_dir,problem_home)
38 38 link_input_file(test_request,problem_home)
39 39
40 40 problem_home
41 41 end
42 42
43 43 def save_source(test_request,source_name)
44 44 dir = self.produce_grading_room(test_request)
45 45 submission = test_request.submission
46 46 f = File.open("#{dir}/#{source_name}","w")
47 47 f.write(submission.source)
48 48 f.close
49 49 end
50 50
51 51 def clean_up(test_request)
52 52 problem_home = problem_home_dir(test_request)
53 53 remove_data_files(problem_home)
54 54 end
55 55
56 56 protected
57 57 def grading_room_dir(test_request)
58 58 problem_name = test_request.problem_name
59 59 user = test_request.user
60 60 "#{@config.user_result_dir}" +
61 61 "/#{user.login}/test_request" +
62 62 "/#{problem_name}/#{test_request.id}"
63 63 end
64 64
65 65 def problem_home_dir(test_request)
66 66 problem_name = test_request.problem_name
67 67 user = test_request.user
68 68 "#{@config.user_result_dir}" +
69 69 "/#{user.login}/test_request/#{problem_name}"
70 70 end
71 71
72 72 def copy_problem_template(template_dir,problem_home)
73 73 cmd = "cp -R #{template_dir}/* #{problem_home}"
74 74 system_and_raise_when_fail(cmd,"Test Request: cannot copy problem template")
75 75 end
76 76
77 77 def link_input_file(test_request,problem_home)
78 78 cmd = "ln -s #{test_request.input_file_name} #{problem_home}/test_cases/1/input-1.txt"
79 79 system_and_raise_when_fail(cmd,"Test Request: cannot link input file")
80 80 end
81 81
82 82 def remove_data_files(problem_home)
83 83 if File.exists?("#{problem_home}/test_cases/1/input-1.txt")
84 84 cmd = "rm #{problem_home}/test_cases/1/*"
85 85 system_and_raise_when_fail(cmd,"Test Request: cannot remove data files")
86 86 end
87 87 end
88 88
89 89 def system_and_raise_when_fail(cmd,msg)
90 90 if !system(cmd)
91 91 raise msg
92 92 end
93 93 end
94 94
95 95 end
96 96
97 97 class TestRequestReporter
98 98 def initialize
99 99 @config = Grader::Configuration.get_instance
100 100 end
101 101
102 102 def report(test_request,test_result_dir)
103 103 save_result(test_request,read_result(test_result_dir))
104 104 end
105 105
106 106 def report_error(test_request, msg)
107 107 save_result(test_request, {:running_stat => {:msg => "#{msg}"}})
108 108 end
109 109
110 110 protected
111 111 def read_result(test_result_dir)
112 112 # TODO:
113 113 cmp_msg_fname = "#{test_result_dir}/compiler_message"
114 114 cmp_file = File.open(cmp_msg_fname)
115 115 cmp_msg = cmp_file.read
116 116 cmp_file.close
117 117
118 118 result_file_name = "#{test_result_dir}/1/result"
119 119
120 120 if File.exists?(result_file_name)
121 121 output_file_name = "#{test_result_dir}/1/output.txt"
122 122 results = File.open("#{test_result_dir}/1/result").readlines
123 - stat = format_running_stat(results)
123 + stat = extract_running_stat(results)
124 124
125 125 return {
126 126 :output_file_name => output_file_name,
127 127 :running_stat => stat,
128 128 :comment => "",
129 129 :cmp_msg => cmp_msg}
130 130 else
131 131 return {
132 132 :running_stat => nil,
133 133 :comment => "Compilation error",
134 134 :cmp_msg => cmp_msg}
135 135 end
136 136 end
137 137
138 - def format_running_stat(results)
139 - running_time_line = results[-1]
138 + def extract_running_stat(results)
139 + running_stat_line = results[-1]
140 140
141 141 # extract exit status line
142 142 run_stat = ""
143 143 if !(/[Cc]orrect/.match(results[0]))
144 144 run_stat = results[0].chomp
145 145 else
146 146 run_stat = 'Program exited normally'
147 147 end
148 148
149 149 # extract running time
150 - if res = /r(.*)u(.*)s/.match(running_time_line)
150 + if res = /r(.*)u(.*)s/.match(running_stat_line)
151 151 seconds = (res[1].to_f + res[2].to_f)
152 152 time_stat = "Time used: #{seconds} sec."
153 153 else
154 154 seconds = nil
155 155 time_stat = "Time used: n/a sec."
156 156 end
157 +
158 + # extract memory usage
159 + if res = /s(.*)m/.match(running_stat_line)
160 + memory_used = res[1].to_i
161 + else
162 + memory_used = -1
163 + end
164 +
157 165 return {
158 166 :msg => "#{run_stat}\n#{time_stat}",
159 167 :running_time => seconds,
160 - :exit_status => run_stat
168 + :exit_status => run_stat,
169 + :memory_usage => memory_used
161 170 }
162 171 end
163 172
164 173 def save_result(test_request,result)
165 174 if result[:output_file_name]!=nil
166 175 test_request.output_file_name = link_output_file(test_request,
167 176 result[:output_file_name])
168 177 end
169 178 test_request.graded_at = Time.now
170 179 test_request.compiler_message = (result[:cmp_msg] or '')
171 180 test_request.grader_comment = (result[:comment] or '')
172 181 if result[:running_stat]!=nil
173 182 test_request.running_stat = (result[:running_stat][:msg] or '')
174 183 test_request.running_time = (result[:running_stat][:running_time] or nil)
175 - test_request.exit_status = (result[:running_stat][:exit_status])
176 - test_request.memory_usage = nil # should be added later
184 + test_request.exit_status = result[:running_stat][:exit_status]
185 + test_request.memory_usage = result[:running_stat][:memory_usage]
177 186 else
178 187 test_request.running_stat = ''
179 188 end
180 189 test_request.save
181 190 end
182 191
183 192 protected
184 193 def link_output_file(test_request, fname)
185 194 target_file_name = random_output_file_name(test_request.user,
186 195 test_request.problem)
187 196 FileUtils.mkdir_p(File.dirname(target_file_name))
188 197 cmd = "ln -s #{fname} #{target_file_name}"
189 198 if !system(cmd)
190 199 raise "TestRequestReporter: cannot move output file"
191 200 end
192 201 return target_file_name
193 202 end
194 203
195 204 def random_output_file_name(user,problem)
196 205 problem_name = TestRequest.name_of(problem)
197 206 begin
198 207 tmpname = "#{@config.test_request_output_base_dir}" +
199 208 "/#{user.login}/#{problem_name}/#{rand(10000)}"
200 209 end while File.exists?(tmpname)
201 210 tmpname
202 211 end
203 212
204 213 end
205 214
206 215 end
@@ -1,149 +1,155
1 1 #!/usr/bin/ruby
2 2
3 3 def log(str='')
4 4 if ENV['TALKATIVE']!=nil
5 5 puts str
6 6 end
7 7 if ENV['GRADER_LOGGING']!=nil
8 8 log_fname = ENV['GRADER_LOGGING']
9 9 fp = File.open(log_fname,"a")
10 10 fp.puts("run: #{Time.new.strftime("%H:%M")} #{str}")
11 11 fp.close
12 12 end
13 13 end
14 14
15 15 def extract_time(t)
16 + # puts "TIME: #{t}"
16 17 if (result=/^(.*)r(.*)u(.*)s/.match(t))
17 18 {:real => result[1], :user => result[2], :sys => result[3]}
18 19 else
19 - raise "Error reading running time"
20 + #{:real => 0, :user => 0, :sys => 0}
21 + #puts "ERROR READING RUNNING TIME: #{t}"
22 + raise "Error reading running time: #{t}"
20 23 end
21 24 end
22 25
23 26 def compile_box(source,bin)
24 - system("gcc #{source} -o #{bin}")
27 + system("g++ #{source} -o #{bin}")
25 28 end
26 29
27 30 if ARGV.length < 2 || ARGV.length > 3
28 31 puts "Usage: run <language> <test-num> [<program-name>]"
29 32 exit(127)
30 33 end
31 34
32 35 language = ARGV[0]
33 36 test_num = ARGV[1].to_i
34 37 if ARGV.length > 2
35 38 program_name = ARGV[2]
36 39 else
37 40 program_name = "a.out"
38 41 end
39 42
40 43 problem_home = ENV['PROBLEM_HOME']
41 44 require "#{problem_home}/script/test_dsl.rb"
42 45 load "#{problem_home}/test_cases/all_tests.cfg"
43 46 problem = Problem.get_instance
44 47
45 48 if problem.well_formed? == false
46 49 log "The problem specification is not well formed."
47 50 exit(127)
48 51 end
49 52
50 53 # Check if the test number is okay.
51 54 if test_num <= 0 || test_num > problem.num_tests
52 55 log "You have specified a wrong test number."
53 56 exit(127)
54 57 end
55 58
56 59 #####################################
57 60 # Set the relavant file names here. #
58 61 #####################################
59 62
60 63 input_file_name = "#{problem_home}/test_cases/#{test_num}/input-#{test_num}.txt"
61 64
62 65 #####################################
63 66
64 67 time_limit = problem.get_time_limit test_num
65 68 mem_limit = problem.get_mem_limit(test_num) * 1024
66 69
67 70 # Copy the input file.
68 71 #`cp #{problem_home}/test_cases/#{test_num}/#{input_file_name} .`
69 72
70 - time_output_format = "%Er%Uu%Ss"
71 -
72 73 # check if box is there, if not, compile it!
73 74 if !File.exists?("#{problem_home}/script/box")
74 75 log "WARNING: Compiling box: to increase efficiency, it should be compile manually"
75 - compile_box("#{problem_home}/script/box.c",
76 + compile_box("#{problem_home}/script/box.cc",
76 77 "#{problem_home}/script/box")
77 78 end
78 79
79 80 # Hide PROBLEM_HOME
80 81 ENV['PROBLEM_HOME'] = nil
81 82
82 83 # Run the program.
83 - run_command = "/usr/bin/time -f \"#{time_output_format}\" 2>run_result #{problem_home}/script/box -a 2 -f -t #{time_limit} -m #{mem_limit} -i #{input_file_name} -o output.txt #{program_name}"
84 + #run_command = "/usr/bin/time -f \"#{time_output_format}\" 2>run_result #{problem_home}/script/box_new -a 2 -f -t #{time_limit} -m #{mem_limit} -i #{input_file_name} -o output.txt #{program_name}"
85 + run_command = "#{problem_home}/script/box -a 2 -f -t #{time_limit} -m #{mem_limit} -i #{input_file_name} -o output.txt #{program_name} 2>run_result"
84 86 log "Running test #{test_num}..."
85 87 log run_command
86 88 log
87 89 system(run_command)
88 90
89 91 # Restore PROBLEM_HOME
90 92 ENV['PROBLEM_HOME'] = problem_home
91 93
92 94 # Create the result file.
93 95 result_file = File.new("result", "w")
94 96 comment_file = File.new("comment", "w")
95 97
96 98 # Check if the program actually produced any output.
97 99 run_result_file = File.new("run_result", "r")
98 100 run_result = run_result_file.readlines
99 101 run_result_file.close
100 - time_elapsed = run_result[run_result.length-1]
101 - running_time = extract_time(time_elapsed)
102 +
103 + run_stat = run_result[run_result.length-1]
104 + running_time = extract_time(run_stat)
102 105
103 106 report = lambda{ |status, points, comment|
104 107 result_file.write status.strip
105 108 result_file.write "\n"
106 109 result_file.write points.to_s.strip
107 110 result_file.write "\n"
108 - result_file.write time_elapsed.strip
111 + result_file.write run_stat.strip
109 112 result_file.write "\n"
110 113 result_file.close
111 114 `rm run_result`
112 115 # `rm output.txt` --- keep the output
113 116
114 117 comment_file.write comment
118 +
119 + # added for debuggin --- jittat
115 120 comment_file.write "--run-result--\n"
116 121 run_result.each do |l|
117 122 comment_file.write l
118 123 end
124 +
119 125 comment_file.close
120 126
121 127 log "Done!"
122 128 exit(0)
123 129 }
124 130
125 131 if run_result[0][0,2] != "OK"
126 132 log "There was a runtime error."
127 133 report.call(run_result[0], 0, "No comment.\n")
128 134 end
129 135
130 136 if running_time[:user].to_f + running_time[:sys].to_f > time_limit
131 137 log "Time limit exceeded."
132 138 report.call("Time limit exceeded", 0, "No comment.\n")
133 139 end
134 140
135 141 # Run 'check' to evaluate the output.
136 142 #puts "There was no runtime error. Proceed to checking the output."
137 143 check_command = "#{problem_home}/script/check #{language} #{test_num}"
138 144 log "Checking the output..."
139 145 log check_command
140 146 if not system(check_command)
141 147 log "Problem with check script"
142 148 report.call("Incorrect",0,"Check script error.\n")
143 149 exit(127)
144 150 end
145 151
146 152 check_file = File.new("check_result", "r")
147 153 check_file_lines = check_file.readlines
148 154
149 155 report.call(check_file_lines[0], check_file_lines[1], "No comment.\n")
@@ -158,135 +158,159
158 158 @config = Grader::Configuration.get_instance
159 159 @engine = Grader::Engine.new(Grader::TestRequestRoomMaker.new,
160 160 Grader::TestRequestReporter.new)
161 161 init_sandbox
162 162 end
163 163
164 164 it "should report error if there is no problem template" do
165 165 problem = stub(Problem,
166 166 :id => 1, :name => 'nothing')
167 167 grader_should(:grade => 'test1_correct.c',
168 168 :on => problem,
169 169 :with => 'in1.txt',
170 170 :and_report => {
171 171 :graded_at= => nil,
172 172 :compiler_message= => '',
173 173 :grader_comment= => '',
174 174 :running_stat= => /template not found/,
175 175 :running_time= => nil,
176 176 :exit_status= => nil,
177 177 :memory_usage= => nil,
178 178 :save => nil})
179 179 end
180 180
181 181 it "should run test request and produce output file" do
182 182 problem = stub(Problem,
183 183 :id => 1, :name => 'test_normal')
184 184 grader_should(:grade => 'test1_correct.c',
185 185 :on => problem,
186 186 :with => 'in1.txt',
187 187 :and_report => {
188 188 :graded_at= => nil,
189 189 :compiler_message= => '',
190 190 :grader_comment= => '',
191 191 :running_stat= => /0.0 sec./,
192 192 :output_file_name= => lambda { |fname|
193 193 File.exists?(fname).should be_true
194 194 },
195 195 :running_time= => nil,
196 196 :exit_status= => nil,
197 197 :memory_usage= => nil,
198 198 :save => nil})
199 199 end
200 200
201 201 it "should clean up problem directory after running test request" do
202 202 problem = stub(Problem,
203 203 :id => 1, :name => 'test_normal')
204 204 grader_should(:grade => 'test1_correct.c',
205 205 :on => problem,
206 206 :with => 'in1.txt',
207 207 :and_report => {
208 208 :graded_at= => nil,
209 209 :compiler_message= => '',
210 210 :grader_comment= => '',
211 211 :running_stat= => nil,
212 212 :output_file_name= => nil,
213 213 :running_time= => nil,
214 214 :exit_status= => nil,
215 215 :memory_usage= => nil,
216 216 :save => nil})
217 217 File.exists?(@config.user_result_dir + "/test_request/test_normal/test_cases/1/input-1.txt").should be_false
218 218 end
219 219
220 220 it "should compile test request with error and report compilation error" do
221 221 problem = stub(Problem,
222 222 :id => 1, :name => 'test_normal')
223 223 grader_should(:grade => 'test1_compile_error.c',
224 224 :on => problem,
225 225 :with => 'in1.txt',
226 226 :and_report => {
227 227 :graded_at= => nil,
228 228 :compiler_message= => /.+/,
229 229 :grader_comment= => /[Cc]ompil.*error/,
230 230 :running_stat= => '',
231 231 :save => nil})
232 232 end
233 233
234 234 it "should report exit status" do
235 235 problem = stub(Problem,
236 236 :id => 1, :name => 'test_normal')
237 237 grader_should(:grade => 'add_nonzero_exit_status.c',
238 238 :on => problem,
239 239 :with => 'in1.txt',
240 240 :and_report => {
241 241 :graded_at= => nil,
242 242 :compiler_message= => '',
243 243 :grader_comment= => '',
244 244 :running_stat= => /[Ee]xit.*status.*10.*0\.0 sec/m,
245 245 :output_file_name= => lambda { |fname|
246 246 File.exists?(fname).should be_true
247 247 },
248 248 :running_time= => nil,
249 249 :exit_status= => /10/,
250 250 :memory_usage= => nil,
251 251 :save => nil})
252 252 end
253 253
254 + it "should produce running statistics for normal submission" do
255 + problem = stub(Problem,
256 + :id => 1, :name => 'test_normal')
257 + grader_should(:grade => 'test_run_stat.c',
258 + :on => problem,
259 + :with => 'in1.txt',
260 + :and_report => {
261 + :graded_at= => nil,
262 + :compiler_message= => '',
263 + :grader_comment= => '',
264 + :running_stat= => nil,
265 + :output_file_name= => lambda { |fname|
266 + File.exists?(fname).should be_true
267 + },
268 + :running_time= => lambda { |t|
269 + (t>=0.14) and (t<=0.16)
270 + },
271 + :exit_status= => nil,
272 + :memory_usage= => lambda { |s|
273 + (s>=500) and (s<=1000)
274 + },
275 + :save => nil})
276 + end
277 +
254 278 protected
255 279 def grader_should(args)
256 280 @user1 = stub(User,
257 281 :id => 1, :login => 'user1')
258 282
259 283 problem = args[:on]
260 284 input_file = @config.test_request_input_base_dir + "/" + args[:with]
261 285
262 286 submission =
263 287 create_submission_from_file(1, @user1, args[:on], args[:grade])
264 288
265 289 test_request = stub(TestRequest,
266 290 :id => 1,
267 291 :user => @user1,
268 292 :problem => problem,
269 293 :submission => submission,
270 294 :input_file_name => input_file,
271 295 :language => submission.language,
272 296 :problem_name => problem.name)
273 297
274 298 expectations = args[:and_report]
275 299
276 300 expectations.each do |key,val|
277 301 if val==nil
278 302 test_request.should_receive(key)
279 303 elsif val.class == Proc
280 304 test_request.should_receive(key) { |fname|
281 305 val.call(fname)
282 306 }
283 307 else
284 308 test_request.should_receive(key).with(val)
285 309 end
286 310 end
287 311
288 312 @engine.grade(test_request)
289 313 end
290 314
291 315 end
292 316
deleted file
This diff has been collapsed as it changes many lines, (557 lines changed) Show them Hide them
You need to be logged in to leave comments. Login now