Exemplo n.º 1
0
        public async Task <ActionResult <GradeDto> > Grade([FromBody] GradeSubmissionDto gradeSubmissionDto)
        {
            // Get language info
            ComputerLanguage lang = await _context.ComputerLanguages.FindAsync(gradeSubmissionDto.LangId);

            if (lang == null)
            {
                return(BadRequest(new { Message = "The supplied computer language id doesn't exist!" }));
            }

            string executionRootDir = Grader.GetExecutionRootDir();
            string randomId         = RandomString(32);

            string sourceFileName = $"{randomId}.{lang.Extension}";
            string binaryFileName = randomId;

            string sourceFilePath = $"{executionRootDir}{sourceFileName}";
            string binaryFilePath = $"{executionRootDir}{binaryFileName}";

            // Create file from source code inside rootDir
            System.IO.File.WriteAllText(sourceFilePath, gradeSubmissionDto.SourceCode);

            GradeDto result = new GradeDto(); // this will be returned by the method

            bool readyToRun = true;

            // Check if compiled language
            if (!string.IsNullOrEmpty(lang.CompileCmd))
            {
                // Compile submission
                CompilationOutputDto co = Grader.Compile(lang, sourceFileName, binaryFileName);

                if (co.ExitCode != 0)
                {
                    // Compile error
                    result.Status = "CE";     // Mark status as Compile Error
                    result.Error  = co.Error; // Set message as compile error message
                    readyToRun    = false;
                }
            }

            if (readyToRun)
            {
                // Compiled successfully or interpreted language, so we're ready to run the solution

                string fileName = string.IsNullOrEmpty(lang.CompileCmd) ? sourceFileName : binaryFileName;

                // Grade solution
                result = Grader.Grade(lang, fileName, gradeSubmissionDto.Input, gradeSubmissionDto.ExpectedOutput, gradeSubmissionDto.TimeLimit, gradeSubmissionDto.MemoryLimit);

                // Delete binary file
                System.IO.File.Delete(binaryFilePath);
            }

            // Delete source file
            System.IO.File.Delete(sourceFilePath);

            return(Ok(result));
        }
Exemplo n.º 2
0
        // Compile a source code file into a binary file
        public static CompilationOutputDto Compile(ComputerLanguage lang, string sourceFileName, string binaryFileName)
        {
            string rootDir        = GetExecutionRootDir();
            string sourceFilePath = $"{rootDir}{sourceFileName}";
            string binaryFilePath = $"{rootDir}{binaryFileName}";
            string compileCommand = $"{lang.CompilerFileName} {string.Format(lang.CompileCmd, sourceFilePath, binaryFilePath)}";

            BashExecutor executor = new BashExecutor(compileCommand);

            executor.Execute();

            return(new CompilationOutputDto
            {
                ExitCode = (int)executor.ExitCode,
                Error = !string.IsNullOrEmpty(executor.Error) ? executor.Error.Replace(sourceFilePath, "") : "",
            });
        }
Exemplo n.º 3
0
        // Run program, grade a single test case and return the results
        public static GradeDto Grade(ComputerLanguage lang, string fileName, string input, string expectedOutput, int timeLimitMs = 0, int memoryLimitB = 0)
        {
            string rootDir            = GetExecutionRootDir();
            string timeOutputFilePath = $"{rootDir}time{fileName}.txt";

            // create cgroup
            $"sudo cgcreate -g memory,pids:{fileName}".Bash();

            // increase the initial memory limit for the cgroup a bit due to overhead and also set a minimum
            int pMemLimitB = Math.Max(memoryLimitB + 750000, 1150000);

            // set memory and max process limit
            $"sudo cgset -r memory.limit_in_bytes={pMemLimitB} -r memory.swappiness=0 -r pids.max=1 {fileName}".Bash();

            // enforce maximum time limit
            if (timeLimitMs <= 0 || timeLimitMs > 10000)
            {
                timeLimitMs = 10000;
            }

            // timeout uses a longer time limit value because it measures real time and not cpu time.
            // we let the process run longer just in case, and after we inspect its cpu time from /usr/bin/time output.
            // timeout of zero means the associated timeout is disabled.
            float timeoutS = (timeLimitMs << 2) / 1000.0f;

            // prepare execution command string
            string escapedExecCmd = $"/usr/bin/time -p -o {timeOutputFilePath} sudo timeout --preserve-status {timeoutS} sudo cgexec -g memory,pids:{fileName} chroot --userspec=coderunner:no-network {rootDir} {string.Format(lang.ExecuteCmd, fileName)}".Replace("\"", "\\\"");

            // set initial return values
            GradeDto grade = new GradeDto
            {
                Status          = "AC", // will stay accepted if test case passes
                ExecutionTime   = 0,
                ExecutionMemory = 0,
                Output          = "",
                Error           = "",
                SubmittedAt     = DateTime.Now,
            };

            using (Process q = new Process())
            {
                string output = "";
                string error  = "";

                q.StartInfo.FileName               = "/bin/bash";
                q.StartInfo.Arguments              = $"-c \"{escapedExecCmd}\"";
                q.StartInfo.RedirectStandardInput  = true;
                q.StartInfo.RedirectStandardOutput = true;
                q.StartInfo.RedirectStandardError  = true;
                q.StartInfo.CreateNoWindow         = true;
                q.StartInfo.UseShellExecute        = false;
                q.OutputDataReceived              += new DataReceivedEventHandler((sender, e) =>
                {
                    if (!string.IsNullOrEmpty(e.Data))
                    {
                        output += e.Data + "\n";
                    }
                });
                q.ErrorDataReceived += new DataReceivedEventHandler((sender, e) =>
                {
                    if (!string.IsNullOrEmpty(e.Data))
                    {
                        error += e.Data + "\n";
                    }
                });

                q.Start();
                q.BeginOutputReadLine();
                q.BeginErrorReadLine();
                q.StandardInput.WriteLineAsync(input);

                q.WaitForExit();

                // fetch and check if execution CPU time actually meets the limit
                double userCpuTimeS   = double.Parse($"grep -oP '(?<=user ).*' {timeOutputFilePath}".Bash());
                double sysCpuTimeS    = double.Parse($"grep -oP '(?<=sys ).*' {timeOutputFilePath}".Bash());
                int    totalCpuTimeMs = (int)((userCpuTimeS + sysCpuTimeS) * 1000 + 0.5); // round
                grade.ExecutionTime = totalCpuTimeMs;

                if (q.ExitCode != 0)
                {
                    // Unsuccessfully executed
                    grade.Error = error;

                    if (q.ExitCode == 137)
                    {
                        // cgroup sent SIGKILL
                        // the process exited with code 137
                        // meaning the memory limit was breached
                        grade.Status = "MLE";
                    }
                    else if (q.ExitCode == 143)
                    {
                        // timeout sent SIGTERM
                        // the process exited with code 137
                        // meaning the time limit was definitely breached
                        grade.Status        = "TLE";
                        grade.ExecutionTime = timeLimitMs;
                    }
                    else
                    {
                        // Runtime error
                        // Rejected
                        grade.Status = "RTE";
                    }
                }
                else if (totalCpuTimeMs > timeLimitMs)
                {
                    // Successfully executed but actual CPU time breaches the limit
                    grade.Status        = "TLE";
                    grade.ExecutionTime = timeLimitMs;
                }
                else
                {
                    // Successfully executed
                    grade.Output = output;

                    // Check if submission output matches the expected output of test case
                    bool correctSoFar = true;

                    string[] outputLines = output.Trim().Split(
                        new[] { "\r\n", "\r", "\n" },
                        StringSplitOptions.None
                        );
                    string[] tcOutputLines = expectedOutput.Trim().Split(
                        new[] { "\r\n", "\r", "\n" },
                        StringSplitOptions.None
                        );

                    if (outputLines.Length != tcOutputLines.Length)
                    {
                        correctSoFar = false;
                    }

                    int idx = 0;
                    while (correctSoFar && idx < outputLines.Length)
                    {
                        if (!outputLines.ElementAt(idx).TrimEnd().Equals(tcOutputLines.ElementAt(idx).TrimEnd()))
                        {
                            correctSoFar = false;
                        }
                        ++idx;
                    }

                    if (!correctSoFar)
                    {
                        // Mismatch, set status as wrong answer
                        grade.Status = "WA";
                    }
                }
            }

            // get memory amount used
            string maxMemoryUsed = $"cgget -n -v -r memory.max_usage_in_bytes {fileName}".Bash().TrimEnd('\r', '\n');

            grade.ExecutionMemory = Int32.Parse(maxMemoryUsed);

            // delete cgroup
            $"sudo cgdelete -g memory,pids:{fileName}".Bash();

            // delete time output file
            System.IO.File.Delete(timeOutputFilePath);

            return(grade);
        }
Exemplo n.º 4
0
        public async Task <ActionResult <SubmissionDto> > PostSubmission(int taskId, [FromBody] SubmissionDto submissionDto)
        {
            int currentUserId = int.Parse(User.FindFirst(JwtRegisteredClaimNames.Sub).Value);

            // Get task info
            Entities.Task task = await _context.Tasks.SingleOrDefaultAsync(t => t.Id == taskId && (t.IsPublic || t.UserId == currentUserId));

            if (task == null)
            {
                return(BadRequest());
            }

            // Get language info
            ComputerLanguage lang = await _context.ComputerLanguages.FindAsync(submissionDto.LangId);

            if (lang == null)
            {
                return(BadRequest());
            }

            // Get test cases
            await _context.Entry(task).Collection(t => t.TestCases).LoadAsync();

            Submission submission = new Submission
            {
                SourceCode      = submissionDto.SourceCode,
                LangId          = submissionDto.LangId,
                UserId          = currentUserId,
                TimeSubmitted   = DateTime.Now,
                TaskId          = taskId,
                Status          = "UD",
                ExecutionTime   = 0,
                ExecutionMemory = 0,
            };

            // Save initial submission to DB to get a unique id
            _context.Submissions.Add(submission);
            await _context.SaveChangesAsync();

            string executionRootDir = Grader.GetExecutionRootDir();

            string sourceFileName = $"{submission.Id}.{lang.Extension}";
            string binaryFileName = submission.Id.ToString();

            string sourceFilePath = $"{executionRootDir}{sourceFileName}";
            string binaryFilePath = $"{executionRootDir}{binaryFileName}";

            // Create file from source code inside rootDir
            System.IO.File.WriteAllText(sourceFilePath, submissionDto.SourceCode);

            bool readyToRun = true;

            // Check if compiled language
            if (!string.IsNullOrEmpty(lang.CompileCmd))
            {
                // Compile submission
                CompilationOutputDto co = Grader.Compile(lang, sourceFileName, binaryFileName);

                if (co.ExitCode != 0)
                {
                    // Compile error
                    submission.Status     = "CE";     // Mark status as Compile Error
                    submissionDto.Message = co.Error; // Set message as compile error message
                    readyToRun            = false;
                }
            }

            if (readyToRun)
            {
                // Compiled successfully or interpreted language, so we're ready to run the solution

                submission.Status = "AC"; // Submission status will stay accepted if all test cases pass (or if there aren't any TCs)
                string fileName   = string.IsNullOrEmpty(lang.CompileCmd) ? sourceFileName : binaryFileName;
                int    maxTimeMs  = 0;    // Track max execution time of test cases
                int    maxMemoryB = 0;    // Track max execution memory of test cases

                bool correctSoFar = true;
                for (int i = 0; correctSoFar && i < task.TestCases.Count; ++i)
                {
                    TestCase tc = task.TestCases.ElementAt(i);

                    GradeDto grade = Grader.Grade(lang, fileName, tc.Input, tc.Output, task.TimeLimit, task.MemoryLimit);

                    maxTimeMs         = Math.Max(maxTimeMs, grade.ExecutionTime);
                    maxMemoryB        = Math.Max(maxMemoryB, grade.ExecutionMemory);
                    submission.Status = grade.Status;

                    correctSoFar = grade.Status.Equals("AC");
                }

                submission.ExecutionTime   = maxTimeMs;  // Set submission execution time as max out of all test cases
                submission.ExecutionMemory = maxMemoryB; // Set submission execution memory as max out of all test cases

                // Delete binary file if compiled
                if (!string.IsNullOrEmpty(lang.CompileCmd))
                {
                    System.IO.File.Delete(binaryFilePath);
                }
            }

            // Edit submission object status and stats
            await _context.SaveChangesAsync();

            // Delete source file
            System.IO.File.Delete(sourceFilePath);

            // Prepare response DTO
            SubmissionDto responseDto = mapper.Map <SubmissionDto>(submission);

            responseDto.Message          = submissionDto.Message;
            responseDto.Task             = null;
            responseDto.ComputerLanguage = null;
            responseDto.User             = null;

            return(CreatedAtAction("GetSubmission", new { id = submission.Id }, responseDto));
        }