Skip to content

Commit

Permalink
Merge pull request #264 from doc-detective/runCode
Browse files Browse the repository at this point in the history
Add runCode function to execute temporary scripts in various languages
  • Loading branch information
hawkeyexl authored Jan 25, 2025
2 parents 6a2b404 + 88593f7 commit 2362ddb
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 37 deletions.
62 changes: 37 additions & 25 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "doc-detective-core",
"version": "2.18.2",
"version": "2.19.0",
"description": "The doc testing framework.",
"main": "src/index.js",
"scripts": {
Expand Down Expand Up @@ -36,7 +36,7 @@
"appium-geckodriver": "^1.4.0",
"appium-safari-driver": "^3.5.18",
"axios": "^1.7.7",
"doc-detective-common": "^1.21.0",
"doc-detective-common": "1.22.0-dev.1",
"dotenv": "^16.4.5",
"edgedriver": "^5.6.1",
"geckodriver": "^4.5.1",
Expand All @@ -52,6 +52,14 @@
"webdriverio": "^8.40.5"
},
"optionalDependencies": {
"@ffmpeg-installer/darwin-arm64": "4.1.5",
"@ffmpeg-installer/darwin-x64": "4.1.0",
"@ffmpeg-installer/linux-arm": "4.1.3",
"@ffmpeg-installer/linux-arm64": "4.1.4",
"@ffmpeg-installer/linux-ia32": "4.1.0",
"@ffmpeg-installer/linux-x64": "4.1.0",
"@ffmpeg-installer/win32-ia32": "4.1.0",
"@ffmpeg-installer/win32-x64": "4.1.0",
"@img/sharp-darwin-arm64": "0.33.5",
"@img/sharp-darwin-x64": "0.33.5",
"@img/sharp-libvips-darwin-arm64": "1.0.4",
Expand All @@ -71,15 +79,7 @@
"@img/sharp-linuxmusl-x64": "0.33.5",
"@img/sharp-wasm32": "0.33.5",
"@img/sharp-win32-ia32": "0.33.5",
"@img/sharp-win32-x64": "0.33.5",
"@ffmpeg-installer/darwin-arm64": "4.1.5",
"@ffmpeg-installer/darwin-x64": "4.1.0",
"@ffmpeg-installer/linux-arm": "4.1.3",
"@ffmpeg-installer/linux-arm64": "4.1.4",
"@ffmpeg-installer/linux-ia32": "4.1.0",
"@ffmpeg-installer/linux-x64": "4.1.0",
"@ffmpeg-installer/win32-ia32": "4.1.0",
"@ffmpeg-installer/win32-x64": "4.1.0"
"@img/sharp-win32-x64": "0.33.5"
},
"devDependencies": {
"depcheck": "^1.4.7",
Expand Down
6 changes: 5 additions & 1 deletion src/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const { startRecording } = require("./tests/startRecording");
const { stopRecording } = require("./tests/stopRecording");
const { setVariables } = require("./tests/setVariables");
const { httpRequest } = require("./tests/httpRequest");
const { runCode } = require("./tests/runCode");
const fs = require("fs");
const path = require("path");
const { spawn } = require("child_process");
Expand Down Expand Up @@ -95,7 +96,7 @@ function getDriverCapabilities(config, name, options) {
args.push(`--auto-select-desktop-capture-source=RECORD_ME`);
// if (name === "edge") args.push("--disable-features=msEdgeIdentityFeatures");
if (options.headless) args.push("--headless", "--disable-gpu");
if (process.env.CONTAINER) args.push("--no-sandbox");
if (process.platform === "linux") args.push("--no-sandbox");
// Set capabilities
capabilities = {
platformName: config.environment.platform,
Expand Down Expand Up @@ -601,6 +602,9 @@ async function runStep(config, context, step, driver, options = {}) {
case "runShell":
actionResult = await runShell(config, step);
break;
case "runCode":
actionResult = await runCode(config, step);
break
case "checkLink":
actionResult = await checkLink(config, step);
break;
Expand Down
147 changes: 147 additions & 0 deletions src/tests/runCode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
const { validate, resolvePaths } = require("doc-detective-common");
const {
spawnCommand,
log,
calculatePercentageDifference,
} = require("../utils");
const { runShell } = require("./runShell");
const fs = require("fs");
const path = require("path");
const os = require("os");

exports.runCode = runCode;

// Create a temporary script file
function createTempScript(code, language) {
let extension;
switch (language) {
case "python":
case "py":
extension = ".py";
break;
case "javascript":
case "js":
case "node":
extension = ".js";
break;
case "bash":
extension = ".sh";
break;
default:
extension = "";
}
const tmpDir = os.tmpdir();
const tmpFile = path.join(tmpDir, `doc-detective-${Date.now()}${extension}`);
fs.writeFileSync(tmpFile, code);
return tmpFile;
}

// Run gather, compile, and run code.
async function runCode(config, step) {
const result = {
status: "PASS",
description: "Executed code.",
exitCode: "",
stdout: "",
stderr: "",
};

// Validate step object
const isValidStep = validate("runCode_v2", step);
if (!isValidStep.valid) {
result.status = "FAIL";
result.description = `Invalid step definition: ${isValidStep.errors}`;
return result;
}

// Create temporary script file
let scriptPath = createTempScript(step.code, step.language);
log(config, "debug", `Created temporary script at: ${scriptPath}`);

try {
if (!step.command) {
step.command =
step.language.toLowerCase() === "python"
? "python"
: step.language.toLowerCase() === "javascript"
? "node"
: "bash";
}
const command = step.command;
// Make sure the command is available
const commandExists = await spawnCommand(command, ["--version"]);
if (commandExists.exitCode !== 0) {
result.status = "FAIL";
result.description = `Command ${command} is unavailable. Make sure it's installed and in your PATH.`;
return result;
}

// if Windows and command is bash
if (os.platform() === "win32" && command === "bash") {
result.status = "FAIL";
result.description = `runCode currently doesn't support bash on Windows. Use a different command, a different language, or a runShell step.`;
return result;
}

// Prepare shell command based on language
const shellStep = {
...step,
action: "runShell",
command:
step.language.toLowerCase() === "python"
? "python"
: step.language.toLowerCase() === "javascript"
? "node"
: "bash",
args: [scriptPath, ...step.args],
};
if (step.code) delete shellStep.code;
if (step.language) delete shellStep.language;
if (step.file) delete shellStep.file;
if (step.group) delete shellStep.group;

console.log("shellStep", shellStep);

// Execute script using runShell
const shellResult = await runShell(config, shellStep);

// Copy results
result.status = shellResult.status;
result.description = shellResult.description;
result.stdout = shellResult.stdout;
result.stderr = shellResult.stderr;
result.exitCode = shellResult.exitCode;
} catch (error) {
result.status = "FAIL";
result.description = error.message;
} finally {
// Clean up temporary script file
try {
fs.unlinkSync(scriptPath);
log(config, "debug", `Removed temporary script: ${scriptPath}`);
} catch (error) {
log(config, "warn", `Failed to remove temporary script: ${scriptPath}`);
}
}

return result;
}

// If run directly, perform runCode
if (require.main === module) {
const config = {
logLevel: "debug",
};
const step = {
action: "runCode",
code: `print("Hello, world!")`,
language: "python",
};
runCode(config, step)
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
}
40 changes: 40 additions & 0 deletions test/artifacts/runCode.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"id": "runCode",
"tests": [
{
"contexts": [
{
"app": { "name": "firefox" },
"platforms": ["linux", "mac", "windows"]
}
],
"steps": [
{
"action": "runCode",
"language": "javascript",
"code": "console.log('Hello, World!');",
"output": "Hello, World!"
},
{
"action": "runCode",
"language": "python",
"code": "print('Hello, World!')",
"output": "Hello, World!"
}
]
},
{
"contexts": [
{ "app": { "name": "firefox" }, "platforms": ["linux", "mac"] }
],
"steps": [
{
"action": "runCode",
"language": "bash",
"code": "echo 'Hello, World!'",
"output": "Hello, World!"
}
]
}
]
}

0 comments on commit 2362ddb

Please sign in to comment.