1 | /* |
2 | * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) |
3 | * |
4 | * This program is free software: you can redistribute it and/or modify |
5 | * it under the terms of the GNU General Public License as published by |
6 | * the Free Software Foundation, either version 3 of the License, or |
7 | * (at your option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program. If not, see <https://www.gnu.org/licenses/>. |
16 | */ |
17 | #include "ojproblemcasesrunner.h" |
18 | #include "../utils.h" |
19 | #include "../settings.h" |
20 | #include "../systemconsts.h" |
21 | #include "../widgets/ojproblemsetmodel.h" |
22 | #include <QElapsedTimer> |
23 | #include <QProcess> |
24 | |
25 | |
26 | OJProblemCasesRunner::OJProblemCasesRunner(const QString& filename, const QString& arguments, const QString& workDir, |
27 | const QVector<POJProblemCase>& problemCases, QObject *parent): |
28 | Runner(filename,arguments,workDir,parent), |
29 | mExecTimeout(-1) |
30 | { |
31 | mProblemCases = problemCases; |
32 | mBufferSize = 8192; |
33 | mOutputRefreshTime = 1000; |
34 | setWaitForFinishTime(100); |
35 | } |
36 | |
37 | OJProblemCasesRunner::OJProblemCasesRunner(const QString& filename, const QString& arguments, const QString& workDir, |
38 | POJProblemCase problemCase, QObject *parent): |
39 | Runner(filename,arguments,workDir,parent), |
40 | mExecTimeout(-1) |
41 | { |
42 | mProblemCases.append(problemCase); |
43 | mBufferSize = 8192; |
44 | mOutputRefreshTime = 1000; |
45 | setWaitForFinishTime(100); |
46 | } |
47 | |
48 | void OJProblemCasesRunner::runCase(int index,POJProblemCase problemCase) |
49 | { |
50 | emit caseStarted(problemCase->getId(),index, mProblemCases.count()); |
51 | auto action = finally([this,&index, &problemCase]{ |
52 | emit caseFinished(problemCase->getId(), index, mProblemCases.count()); |
53 | }); |
54 | QProcess process; |
55 | bool errorOccurred = false; |
56 | QByteArray readed; |
57 | QByteArray buffer; |
58 | QByteArray output; |
59 | int noOutputTime = 0; |
60 | QElapsedTimer elapsedTimer; |
61 | bool execTimeouted = false; |
62 | process.setProgram(mFilename); |
63 | process.setArguments(splitProcessCommand(mArguments)); |
64 | process.setWorkingDirectory(mWorkDir); |
65 | QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); |
66 | QString path = env.value("PATH" ); |
67 | QStringList pathAdded; |
68 | bool writeChannelClosed = false; |
69 | if (pSettings->compilerSets().defaultSet()) { |
70 | foreach(const QString& dir, pSettings->compilerSets().defaultSet()->binDirs()) { |
71 | pathAdded.append(dir); |
72 | } |
73 | } |
74 | pathAdded.append(pSettings->dirs().appDir()); |
75 | if (!path.isEmpty()) { |
76 | path= pathAdded.join(PATH_SEPARATOR) + PATH_SEPARATOR + path; |
77 | } else { |
78 | path = pathAdded.join(PATH_SEPARATOR); |
79 | } |
80 | env.insert("PATH" ,path); |
81 | process.setProcessEnvironment(env); |
82 | process.setProcessChannelMode(QProcess::MergedChannels); |
83 | process.connect( |
84 | &process, &QProcess::errorOccurred, |
85 | [&](){ |
86 | errorOccurred= true; |
87 | }); |
88 | problemCase->output.clear(); |
89 | process.start(); |
90 | process.waitForStarted(5000); |
91 | if (process.state()==QProcess::Running) { |
92 | if (fileExists(problemCase->inputFileName)) |
93 | process.write(readFileToByteArray(problemCase->inputFileName)); |
94 | else |
95 | process.write(problemCase->input.toUtf8()); |
96 | process.waitForFinished(0); |
97 | } |
98 | |
99 | elapsedTimer.start(); |
100 | while (true) { |
101 | if (process.bytesToWrite()==0 && !writeChannelClosed) { |
102 | writeChannelClosed = true; |
103 | process.closeWriteChannel(); |
104 | } |
105 | process.waitForFinished(mWaitForFinishTime); |
106 | if (process.state()!=QProcess::Running) { |
107 | break; |
108 | } |
109 | if (mExecTimeout>0) { |
110 | int msec = elapsedTimer.elapsed(); |
111 | if (msec>mExecTimeout) { |
112 | execTimeouted=true; |
113 | } |
114 | } |
115 | if (mStop || execTimeouted) { |
116 | process.terminate(); |
117 | process.kill(); |
118 | break; |
119 | } |
120 | if (errorOccurred) |
121 | break; |
122 | readed = process.read(mBufferSize); |
123 | buffer += readed; |
124 | if (buffer.length()>=mBufferSize || noOutputTime > mOutputRefreshTime) { |
125 | if (!buffer.isEmpty()) { |
126 | emit newOutputGetted(problemCase->getId(),QString::fromLocal8Bit(buffer)); |
127 | output.append(buffer); |
128 | buffer.clear(); |
129 | } |
130 | noOutputTime = 0; |
131 | } else { |
132 | noOutputTime += mWaitForFinishTime; |
133 | } |
134 | } |
135 | problemCase->runningTime=elapsedTimer.elapsed(); |
136 | if (execTimeouted) { |
137 | problemCase->output = tr("Case Timeout" ); |
138 | emit resetOutput(problemCase->getId(), problemCase->output); |
139 | } else { |
140 | if (process.state() == QProcess::ProcessState::NotRunning) |
141 | buffer += process.readAll(); |
142 | emit newOutputGetted(problemCase->getId(),QString::fromLocal8Bit(buffer)); |
143 | output.append(buffer); |
144 | problemCase->output = QString::fromLocal8Bit(output); |
145 | if (errorOccurred) { |
146 | //qDebug()<<"process error:"<<process.error(); |
147 | switch (process.error()) { |
148 | case QProcess::FailedToStart: |
149 | emit runErrorOccurred(tr("The runner process '%1' failed to start." ).arg(mFilename)); |
150 | break; |
151 | // case QProcess::Crashed: |
152 | // if (!mStop) |
153 | // emit runErrorOccurred(tr("The runner process crashed after starting successfully.")); |
154 | // break; |
155 | case QProcess::Timedout: |
156 | emit runErrorOccurred(tr("The last waitFor...() function timed out." )); |
157 | break; |
158 | case QProcess::WriteError: |
159 | emit runErrorOccurred(tr("An error occurred when attempting to write to the runner process." )); |
160 | break; |
161 | case QProcess::ReadError: |
162 | emit runErrorOccurred(tr("An error occurred when attempting to read from the runner process." )); |
163 | break; |
164 | default: |
165 | break; |
166 | } |
167 | } |
168 | } |
169 | } |
170 | |
171 | void OJProblemCasesRunner::run() |
172 | { |
173 | emit started(); |
174 | auto action = finally([this]{ |
175 | emit terminated(); |
176 | }); |
177 | for (int i=0; i < mProblemCases.size(); i++) { |
178 | if (mStop) |
179 | break; |
180 | POJProblemCase problemCase = mProblemCases[i]; |
181 | runCase(i,problemCase); |
182 | } |
183 | } |
184 | |
185 | int OJProblemCasesRunner::execTimeout() const |
186 | { |
187 | return mExecTimeout; |
188 | } |
189 | |
190 | void OJProblemCasesRunner::setExecTimeout(int newExecTimeout) |
191 | { |
192 | mExecTimeout = newExecTimeout; |
193 | } |
194 | |
195 | int OJProblemCasesRunner::waitForFinishTime() const |
196 | { |
197 | return mWaitForFinishTime; |
198 | } |
199 | |
200 | void OJProblemCasesRunner::setWaitForFinishTime(int newWaitForFinishTime) |
201 | { |
202 | mWaitForFinishTime = newWaitForFinishTime; |
203 | } |
204 | |
205 | int OJProblemCasesRunner::outputRefreshTime() const |
206 | { |
207 | return mOutputRefreshTime; |
208 | } |
209 | |
210 | void OJProblemCasesRunner::setOutputRefreshTime(int newOutputRefreshTime) |
211 | { |
212 | mOutputRefreshTime = newOutputRefreshTime; |
213 | } |
214 | |
215 | int OJProblemCasesRunner::bufferSize() const |
216 | { |
217 | return mBufferSize; |
218 | } |
219 | |
220 | void OJProblemCasesRunner::setBufferSize(int newBufferSize) |
221 | { |
222 | mBufferSize = newBufferSize; |
223 | } |
224 | |
225 | |
226 | |