Skip to content

Commit

Permalink
Merge pull request #159 from kalviumcommunity/enh/nodejs-eval-PMF
Browse files Browse the repository at this point in the history
New eval mode in multifile
  • Loading branch information
anipnwr7777 authored Aug 13, 2024
2 parents 10f8b1a + a527d8d commit e941f18
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 22 deletions.
3 changes: 3 additions & 0 deletions enums/supportedPMFOutputFormats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
JUNIT: 'junit'
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
FRONTEND_STATIC_JASMINE: 'frontend_static_jasmine',
FRONTEND_REACT_JASMINE: 'frontend_react_jasmine',
NODEJS_JUNIT: 'nodejs_junit',
}
54 changes: 54 additions & 0 deletions helpers/childProcess.helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const logger = require('../loader').helpers.l

const initExeca = async () => {
const module = await import('execa')
return module.execa
}

const runCommand = async (command, workingDir) => {
const execa = await initExeca()
logger.info(`Executing: ${command}`);
try {
const result = await execa(command, {
cwd: workingDir,
shell: true, // Usefull for npm commands having '&&'
stdio: 'inherit',
preferLocal: true, // Use local binaries if available
localDir: workingDir,
reject: false, // Don't throw on non-zero exit code
})

if (result.failed) {
throw new Error(`Command failed with exit code ${result.exitCode}`)
}
return result
} catch (error) {
if (error.exitCode !== undefined) {
logger.error(`Command exited with code ${error.exitCode}`)
if (error.stderr) logger.error(`Error output: ${error.stderr}`)
} else if (error.signal) {
logger.error(`Command was killed with signal ${error.signal}`)
} else {
logger.error(`Command failed to execute: ${error.message}`)
}
throw error
}
}

const runCommandsSequentially = async (
commands,
workingDir = process.cwd()
) => {
for (const command of commands) {
try {
await runCommand(command, workingDir)
} catch (error) {
logger.error(`Error executing "${command}"`)
if (error.stdout) logger.info(`Command output: ${error.stdout}`)
throw new Error(`Command sequence failed at: ${command}`)
}
}
logger.info("All commands completed successfully")
}

module.exports = { runCommandsSequentially }
44 changes: 44 additions & 0 deletions helpers/fileParser.helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const logger = require('../loader').helpers.l
const fs = require('fs')
const xml2js = require('xml2js')

// recursively process all the test suites
const processTestSuite = (testsuite, success, failed) => {
if (testsuite.testsuite) {
testsuite.testsuite.forEach(suite => processTestSuite(suite, success, failed))
}
if (testsuite.testcase) {
testsuite.testcase.forEach(testcase => {
const testName = testcase.$.name

if (testcase.failure) {
failed.push(testName)
} else {
success.push(testName)
}
})
}
}

const extractTestCasesJunit = async (xmlFilePath) => {
try {
const xmlData = await fs.promises.readFile(xmlFilePath, 'utf8')
const parser = new xml2js.Parser()
const result = await parser.parseStringPromise(xmlData)

const success = []
const failed = []

// Start processing from the root
if (result.testsuites) {
processTestSuite(result.testsuites, success, failed)
}

return { success, failed }
} catch (error) {
logger.error(`Error processing XML file: ${error.message}`)
throw error
}
}

module.exports = { extractTestCasesJunit }
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"compression": "^1.7.4",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"execa": "^9.3.0",
"express": "^4.18.2",
"helmet": "^6.0.1",
"husky": "^8.0.1",
Expand All @@ -26,7 +27,8 @@
"sqlite-parser": "^1.0.1",
"sqlite3": "^5.1.7",
"uuid": "^9.0.0",
"winston": "^3.11.0"
"winston": "^3.11.0",
"xml2js": "^0.6.2"
},
"devDependencies": {
"eslint": "8.22.0",
Expand Down
58 changes: 39 additions & 19 deletions services/code.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ const express = require('express')
const http = require('http')
const { spawn } = require('child_process');
const appConfig = require('../configs/app.config.js')
const { FRONTEND_STATIC_JASMINE } = require('../enums/supportedMultifileSetupTypes.js')
const { FRONTEND_STATIC_JASMINE, NODEJS_JUNIT, FRONTEND_REACT_JASMINE } = require('../enums/supportedPMFTypes.js')
const axios = require('axios')
const supportedLanguages = require('../enums/supportedLanguages')
const { generate } = require('@builder.io/sqlgenerate')
const parser = require('sqlite-parser')
const crypto = require('crypto')
const { JUNIT } = require('../enums/supportedPMFOutputFormats.js')
const { runCommandsSequentially } = require('../helpers/childProcess.helper.js')
const { extractTestCasesJunit } = require('../helpers/fileParser.helper.js')

const _runScript = async (cmd, res, runMemoryCheck = false) => {
let initialMemory = 0
Expand Down Expand Up @@ -802,8 +805,9 @@ const _killProcessOnPort = async (port) => {
})
}

const _preCleanUp = async () => {
const _preCleanUp = async (multifileType) => {
try {
if(multifileType === NODEJS_JUNIT) return;
await _killProcessOnPort(appConfig.multifile.jasminePort)
// TODO: add pre cleanup for puppeteer and jasmine server to prevent memory leak
} catch (err) {
Expand Down Expand Up @@ -832,10 +836,17 @@ const _checkIntegrity = async (non_editable_files) => {
return true
}

const parseResults = async (filePath, testCaseFormat) => {
switch (testCaseFormat) {
case JUNIT:
return extractTestCasesJunit(filePath)
}
}

const _executeMultiFile = async (req, res, response) => {
logger.info(`serving ${req.type}`)
try {
await _preCleanUp()
await _preCleanUp(req.type)
const fileContent = await _getSubmissionDataFromGCS(req.url, appConfig.multifile.submissionFileDownloadPath)
await _writeFilesToDisk(fileContent, appConfig.multifile.workingDir)
} catch (err) {
Expand All @@ -849,22 +860,31 @@ const _executeMultiFile = async (req, res, response) => {
const isValidSubmission = await _checkIntegrity(req.non_editable_files)
if(!isValidSubmission) throw new Error(`A non editable file has been modified, exiting...`)
}
if (req.type === FRONTEND_STATIC_JASMINE) {
const staticServerInstance = await _startStaticServer(appConfig.multifile.staticServerPath)
jasmineResults = await _runTests()
if (staticServerInstance) {
staticServerInstance.close(() => {
logger.error('Static server closed')
});
}
} else {
if (!fs.existsSync(appConfig.multifile.workingDir + 'package.json')) {
throw new Error(`No package.json found`)
}
await _installDependencies(appConfig.multifile.workingDir)
const jasmineServer = await _startJasmineServer()
jasmineResults = await _runTests()
process.kill(-jasmineServer.pid) // kill entire process group including child process and transitive child processes
switch (req.type) {
case FRONTEND_STATIC_JASMINE:
const staticServerInstance = await _startStaticServer(appConfig.multifile.staticServerPath)
jasmineResults = await _runTests()
if (staticServerInstance) {
staticServerInstance.close(() => {
logger.error('Static server closed')
})
}
break
case FRONTEND_REACT_JASMINE:
if (!fs.existsSync(appConfig.multifile.workingDir + 'package.json')) {
throw new Error(`No package.json found`)
}
await _installDependencies(appConfig.multifile.workingDir)
const jasmineServer = await _startJasmineServer()
jasmineResults = await _runTests()
process.kill(-jasmineServer.pid) // kill entire process group including child process and transitive child processes
break
case NODEJS_JUNIT:
if (!fs.existsSync(appConfig.multifile.workingDir + 'package.json')) {
throw new Error(`No package.json found`)
}
await runCommandsSequentially(req.commands, appConfig.multifile.workingDir)
jasmineResults = await parseResults(appConfig.multifile.workingDir + req.output_file, req.output_format)
}

await _cleanUpDir(appConfig.multifile.workingDir, appConfig.multifile.submissionFileDownloadPath)
Expand Down
21 changes: 19 additions & 2 deletions validators/code.validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ const {
const {
FRONTEND_REACT_JASMINE,
FRONTEND_STATIC_JASMINE,
} = require('../enums/supportedMultifileSetupTypes')
NODEJS_JUNIT,
} = require('../enums/supportedPMFTypes')
const { JUNIT } = require('../enums/supportedPMFOutputFormats')

const _getBaseSchema = () => {
return Joi.object({
Expand All @@ -33,11 +35,26 @@ const _getMultiFileSchema = () => {
url: Joi.string().trim().required(),
type: Joi.string()
.trim()
.valid(FRONTEND_REACT_JASMINE, FRONTEND_STATIC_JASMINE)
.valid(FRONTEND_REACT_JASMINE, FRONTEND_STATIC_JASMINE, NODEJS_JUNIT)
.required(),
non_editable_files: Joi.object()
.pattern(Joi.string(), Joi.string().pattern(/^[a-fA-F0-9]{64}$/))
.optional(),
commands: Joi.alternatives().conditional('type', {
is: NODEJS_JUNIT,
then: Joi.array().items(Joi.string().required()),
otherwise: Joi.optional(),
}),
output_file: Joi.alternatives().conditional('type', {
is: NODEJS_JUNIT,
then: Joi.string().required(),
otherwise: Joi.optional(),
}),
output_format: Joi.alternatives().conditional('type', {
is: NODEJS_JUNIT,
then: Joi.string().valid(JUNIT).required(),
otherwise: Joi.optional(),
}),
})
}

Expand Down

0 comments on commit e941f18

Please sign in to comment.