fix:fix no console output because of programs don't write ResultOutputFile

feat: add json config and seperate
giteaManager.ts out of enhancedmultiFileRunner.ts
This commit is contained in:
fly6516 2025-02-27 10:31:29 +08:00
parent 88ef4e2d63
commit f4099a7805
4 changed files with 120 additions and 141 deletions

View File

@ -0,0 +1,8 @@
{
"Image": "fly6516.synology.me:8080/multilang:latest",
"AttachStdout": true,
"AttachStderr": true,
"Tty": false,
"WorkingDir": "/workspace",
"ResultOutputFile": "result.txt"
}

View File

@ -4,104 +4,9 @@ import Docker from 'dockerode';
import fs from 'fs/promises'; // 使用异步版本的 fs 模块 import fs from 'fs/promises'; // 使用异步版本的 fs 模块
import path from 'path'; import path from 'path';
import axios from 'axios'; import axios from 'axios';
import dockerConfig from './dockerConfig.json';
const docker = new Docker(); const docker = new Docker();
const DOCKER_IMAGE_URL = process.env.DOCKER_IMAGE_URL || 'fly6516.synology.me:8080/multilang:latest'; // 使用环境变量
let giteaRepoUrl: string | undefined;
let giteaToken: string | undefined;
let testDataPath: string = 'testdata'; // 默认测试数据目录
/**
* Gitea URL
* @param url URL
*/
export const setGiteaRepoUrl = async (url: string) => {
if (!url || !url.startsWith('http')) {
throw new Error('Invalid repository URL. Please provide a valid URL starting with "http".');
}
giteaRepoUrl = url;
};
/**
* Gitea Token
* @param token API 访
*/
export const setGiteaToken = async (token: string) => {
if (!token || token.trim().length === 0) {
throw new Error('Invalid token. Please provide a non-empty token.');
}
giteaToken = token;
};
/**
*
* @param path
*/
export const setTestDataPath = async (path: string) => {
if (!path || path.trim().length === 0) {
throw new Error('Invalid test data path. Please provide a non-empty path.');
}
testDataPath = path;
};
/**
* Gitea
* @param dir
* @param tempDir
*/
const fetchFilesFromGitea = async (dir: string, tempDir: string) => {
if (!giteaRepoUrl || !giteaToken) {
throw new Error('Gitea repository URL or token is not set. Please configure them using setGiteaRepoUrl and setGiteaToken.');
}
const targetDir = path.join(tempDir, dir);
await fs.mkdir(targetDir, { recursive: true });
try {
const response = await axios.get(`${giteaRepoUrl}/contents/${dir}`, {
headers: {
Authorization: `token ${giteaToken}`,
},
});
const files = response.data;
if (!Array.isArray(files)) {
throw new Error(`Invalid directory structure for ${dir} in repository.`);
}
// 并行下载文件
await Promise.all(files.filter(file => file.type === 'file').map(async (file) => {
const fileResponse = await axios.get(file.download_url, {
headers: {
Authorization: `token ${giteaToken}`,
},
responseType: 'arraybuffer',
});
const filePath = path.join(targetDir, file.name);
await fs.writeFile(filePath, fileResponse.data);
}));
} catch (error) {
throw new Error(`Failed to fetch files from '${dir}': ${(error as Error).message}`);
}
};
/**
*
* @param resultFilePath
* @param expectedFilePath
* @returns
*/
const compareResults = async (resultFilePath: string, expectedFilePath: string): Promise<boolean> => {
try {
const result = (await fs.readFile(resultFilePath, 'utf-8')).trim();
const expected = (await fs.readFile(expectedFilePath, 'utf-8')).trim();
return result === expected;
} catch (error) {
throw new Error(`Failed to read files for comparison: ${(error as Error).message}`);
}
};
/** /**
* *
@ -113,9 +18,8 @@ export const runMultiFileCodeWithOptionalTestData = async (params: {
language: string; language: string;
testDataFiles?: string[]; testDataFiles?: string[];
expectedAnswerFiles?: string[]; expectedAnswerFiles?: string[];
resultOutputFile?: string;
}) => { }) => {
const { files, language, testDataFiles, expectedAnswerFiles, resultOutputFile } = params; const { files, language, testDataFiles, expectedAnswerFiles } = params;
if (!files || files.length === 0 || !language) { if (!files || files.length === 0 || !language) {
throw new Error('Files and language are required.'); throw new Error('Files and language are required.');
@ -143,28 +47,13 @@ export const runMultiFileCodeWithOptionalTestData = async (params: {
await fs.writeFile(filePath, file.content); await fs.writeFile(filePath, file.content);
})); }));
// 检查是否设置了 Gitea 仓库信息
if (giteaRepoUrl && giteaToken) {
try {
await fetchFilesFromGitea(testDataPath, tempDir);
} catch (error) {
throw new Error(`Failed to fetch test data from '${testDataPath}': ${(error as Error).message}`);
}
} else {
console.info('No Gitea repository connection set. Proceeding without testdata.');
}
// 创建 Docker 容器 // 创建 Docker 容器
const container = await docker.createContainer({ const container = await docker.createContainer({
Image: DOCKER_IMAGE_URL, ...dockerConfig,
Cmd: ['sh', '-c', compileAndRunCmd], Cmd: ['sh', '-c', compileAndRunCmd],
AttachStdout: true,
AttachStderr: true,
Tty: false,
HostConfig: { HostConfig: {
Binds: [`${tempDir}:/workspace`], // 挂载临时目录到容器 Binds: [`${tempDir}:/workspace`], // 挂载临时目录到容器
}, },
WorkingDir: '/workspace',
}); });
await container.start(); await container.start();
@ -183,14 +72,17 @@ export const runMultiFileCodeWithOptionalTestData = async (params: {
return { error: `Execution failed with exit code ${exitCode.StatusCode}. Output: ${output}` }; return { error: `Execution failed with exit code ${exitCode.StatusCode}. Output: ${output}` };
} }
// 如果指定了结果输出文件,读取其内容 // 检查结果输出文件是否存在
if (resultOutputFile) { const resultOutputFile = dockerConfig.ResultOutputFile;
const resultFilePath = path.join(tempDir, resultOutputFile); const resultFilePath = path.join(tempDir, resultOutputFile);
try {
const resultFileContent = await fs.readFile(resultFilePath, 'utf-8'); const resultFileContent = await fs.readFile(resultFilePath, 'utf-8');
return { output, resultFileContent }; return { output, resultFileContent };
} catch (error) {
// 如果结果文件不存在,返回控制台输出
return { output };
} }
return { output };
} catch (error) { } catch (error) {
return { error: `An error occurred: ${(error as Error).message}` }; return { error: `An error occurred: ${(error as Error).message}` };
} finally { } finally {

100
src/actions/giteaManager.ts Normal file
View File

@ -0,0 +1,100 @@
'use server';
import fs from 'fs/promises'; // 使用异步版本的 fs 模块
import path from 'path';
import axios from 'axios';
let giteaRepoUrl: string | undefined;
let giteaToken: string | undefined;
let testDataPath: string = 'testdata'; // 默认测试数据目录
/**
* Gitea URL
* @param url URL
*/
export const setGiteaRepoUrl = async (url: string) => {
if (!url || !url.startsWith('http')) {
throw new Error('Invalid repository URL. Please provide a valid URL starting with "http".');
}
giteaRepoUrl = url;
};
/**
* Gitea Token
* @param token API 访
*/
export const setGiteaToken = async (token: string) => {
if (!token || token.trim().length === 0) {
throw new Error('Invalid token. Please provide a non-empty token.');
}
giteaToken = token;
};
/**
*
* @param path
*/
export const setTestDataPath = async (path: string) => {
if (!path || path.trim().length === 0) {
throw new Error('Invalid test data path. Please provide a non-empty path.');
}
testDataPath = path;
};
/**
* Gitea
* @param dir
* @param tempDir
*/
export const fetchFilesFromGitea = async (dir: string, tempDir: string) => {
if (!giteaRepoUrl || !giteaToken) {
throw new Error('Gitea repository URL or token is not set. Please configure them using setGiteaRepoUrl and setGiteaToken.');
}
const targetDir = path.join(tempDir, dir);
await fs.mkdir(targetDir, { recursive: true });
try {
const response = await axios.get(`${giteaRepoUrl}/contents/${dir}`, {
headers: {
Authorization: `token ${giteaToken}`,
},
});
const files = response.data;
if (!Array.isArray(files)) {
throw new Error(`Invalid directory structure for ${dir} in repository.`);
}
// 并行下载文件
await Promise.all(files.filter(file => file.type === 'file').map(async (file) => {
const fileResponse = await axios.get(file.download_url, {
headers: {
Authorization: `token ${giteaToken}`,
},
responseType: 'arraybuffer',
});
const filePath = path.join(targetDir, file.name);
await fs.writeFile(filePath, fileResponse.data);
}));
} catch (error) {
throw new Error(`Failed to fetch files from '${dir}': ${(error as Error).message}`);
}
};
/**
*
* @param resultFilePath
* @param expectedFilePath
* @returns
*/
export const compareResults = async (resultFilePath: string, expectedFilePath: string): Promise<boolean> => {
try {
const result = (await fs.readFile(resultFilePath, 'utf-8')).trim();
const expected = (await fs.readFile(expectedFilePath, 'utf-8')).trim();
return result === expected;
} catch (error) {
throw new Error(`Failed to read files for comparison: ${(error as Error).message}`);
}
};

View File

@ -6,14 +6,12 @@ import { runMultiFileCodeWithOptionalTestData } from '@/actions/enhancedmultiFil
const TestPage = () => { const TestPage = () => {
const [code, setCode] = useState<string>(''); const [code, setCode] = useState<string>('');
const [language, setLanguage] = useState<string>('c'); const [language, setLanguage] = useState<string>('c');
const [result, setResult] = useState<string>('');
const [consoleOutput, setConsoleOutput] = useState<string>(''); const [consoleOutput, setConsoleOutput] = useState<string>('');
const [loading, setLoading] = useState<boolean>(false);
const [resultFileContent, setResultFileContent] = useState<string>(''); const [resultFileContent, setResultFileContent] = useState<string>('');
const [loading, setLoading] = useState<boolean>(false);
const handleRun = async () => { const handleRun = async () => {
setLoading(true); setLoading(true);
setResult('');
setConsoleOutput(''); setConsoleOutput('');
setResultFileContent(''); setResultFileContent('');
@ -45,10 +43,6 @@ const TestPage = () => {
setConsoleOutput(result.error); setConsoleOutput(result.error);
} else { } else {
setConsoleOutput(result.output); setConsoleOutput(result.output);
if (result.comparisonResults) {
setResult(JSON.stringify(result.comparisonResults, null, 2));
}
// 设置 result.txt 的内容 // 设置 result.txt 的内容
if (result.resultFileContent) { if (result.resultFileContent) {
setResultFileContent(result.resultFileContent); setResultFileContent(result.resultFileContent);
@ -116,21 +110,6 @@ const TestPage = () => {
{consoleOutput || 'No console output yet.'} {consoleOutput || 'No console output yet.'}
</pre> </pre>
</div> </div>
{result && (
<div style={{ marginTop: '20px' }}>
<h2>Result</h2>
<pre
style={{
background: '#f4f4f4',
padding: '10px',
border: '1px solid #ddd',
borderRadius: '5px',
}}
>
{result || 'No results yet.'}
</pre>
</div>
)}
{resultFileContent && ( {resultFileContent && (
<div style={{ marginTop: '20px' }}> <div style={{ marginTop: '20px' }}>
<h2>Result File Content (result.txt)</h2> <h2>Result File Content (result.txt)</h2>