Skip to content

Commit

Permalink
Refactor runner for rust condition (#15)
Browse files Browse the repository at this point in the history
* chore(feat): impl case for rust without test

* chore(feat): add prod base image build
  • Loading branch information
nully0x authored Dec 7, 2024
1 parent b4ed18b commit 051ae40
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 44 deletions.
49 changes: 49 additions & 0 deletions .github/workflows/prod-build-base-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Build Base Images

on:
workflow_dispatch: # Manual trigger
push:
paths:
- "baseImages/**"
branches:
- main

jobs:
build-base-images:
name: Build Base Images on Droplet
runs-on: ubuntu-latest
strategy:
matrix:
image:
- { name: "rust", tag: "rs-base", path: "baseImages/rust" }
- {
name: "typescript",
tag: "ts-base",
path: "baseImages/typescript",
}
- { name: "python", tag: "py-base", path: "baseImages/python" }

steps:
- name: Checkout Code
uses: actions/checkout@v3

- name: Configure SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.PROD_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.PROD_DROPLET_IP }} >> ~/.ssh/known_hosts
# Create directory and copy Dockerfile
- name: Copy Dockerfile to Droplet
run: |
ssh ${{ secrets.PROD_DROPLET_USER }}@${{ secrets.PROD_DROPLET_IP }} "mkdir -p ~/base-images/${{ matrix.image.name }}"
scp ${{ matrix.image.path }}/Dockerfile ${{ secrets.PROD_DROPLET_USER }}@${{ secrets.PROD_DROPLET_IP }}:~/base-images/${{ matrix.image.name }}/Dockerfile
# Build the image on the droplet
- name: Build Base Image
run: |
ssh ${{ secrets.PROD_DROPLET_USER }}@${{ secrets.PROD_DROPLET_IP }} << 'EOF'
cd ~/base-images/${{ matrix.image.name }}
docker build -t ${{ matrix.image.tag }}:latest .
EOF
40 changes: 40 additions & 0 deletions src/utils/runScriptGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { TestRepoManager } from "./testRepoManager";
import path from "path";
import fs from "fs/promises";

export async function generateRunScript(
repoDir: string,
language: string,
currentStep: number,
testContent: string | null,
): Promise<void> {
const runScript = `#!/bin/bash
set -e # Exit on any error
if [ -f "requirements.txt" ]; then
# For Python projects
${
testContent
? `pytest ./app/stage${currentStep}${TestRepoManager.getTestExtension(language)} -v`
: "python ./app/main.py"
}
elif [ -f "Cargo.toml" ]; then
# For Rust projects
cargo build ${testContent ? "--quiet" : ""}
${
testContent ? `cargo test --test stage${currentStep}_test` : "cargo run"
}
else
# For TypeScript projects
${
testContent
? `bun test ./app/stage${currentStep}${TestRepoManager.getTestExtension(language)}`
: "bun run start"
}
fi
`;

const runScriptPath = path.join(repoDir, ".hxckr", "run.sh");
await fs.writeFile(runScriptPath, runScript);
await fs.chmod(runScriptPath, 0o755); // Making it executable
}
53 changes: 20 additions & 33 deletions src/utils/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ProgressResponse } from "../models/types";
import { TestRepoManager } from "./testRepoManager";
import SSELogger from "./sseLogger";
import { SSEManager } from "./sseManager";
import { generateRunScript } from "./runScriptGenerator";

export async function runTestProcess(request: TestRunRequest): Promise<void> {
const { repoUrl, branch, commitSha } = request;
Expand Down Expand Up @@ -55,47 +56,31 @@ export async function runTestProcess(request: TestRunRequest): Promise<void> {
const languageConfig = getLanguageConfig(language);
imageName = `test-image-${commitSha}`;

// Load and write test file
// Load test file (might be null)
const testContent = await loadTestFile(
challengeId,
language,
progress.progress_details.current_step,
);

if (language === "rust") {
const testsDir = path.join(repoDir, "tests");
await fs.mkdir(testsDir, { recursive: true });
// Write test file if it exists
if (testContent) {
const testFileName = `stage${progress.progress_details.current_step}${TestRepoManager.getTestExtension(language)}`;
await fs.writeFile(path.join(testsDir, testFileName), testContent);
} else {
const appDir = path.join(repoDir, "app");
await fs.mkdir(appDir, { recursive: true });
const testFileName = `stage${progress.progress_details.current_step}${TestRepoManager.getTestExtension(language)}`;
await fs.writeFile(path.join(appDir, testFileName), testContent);
const testFilePath =
language === "rust"
? path.join(repoDir, "tests", testFileName)
: path.join(repoDir, "app", testFileName);
await fs.mkdir(path.dirname(testFilePath), { recursive: true });
await fs.writeFile(testFilePath, testContent);
}

// Create run.sh with modified commands
const runScript = `#!/bin/bash
set -e # Exit on any error
if [ -f "requirements.txt" ]; then
# For Python projects
pytest ./app/stage${progress.progress_details.current_step}${TestRepoManager.getTestExtension(language)} -v
elif [ -f "Cargo.toml" ]; then
# For Rust projects
# Build first (quietly)
cargo build > /dev/null 2>&1
# Run tests and show only test output
cargo test --test stage${progress.progress_details.current_step}_test
else
# For TypeScript projects
bun test ./app/stage${progress.progress_details.current_step}${TestRepoManager.getTestExtension(language)}
fi
`;

const runScriptPath = path.join(repoDir, ".hxckr", "run.sh");
await fs.writeFile(runScriptPath, runScript);
await fs.chmod(runScriptPath, 0o755); // Make executable
// Generate run script
await generateRunScript(
repoDir,
language,
progress.progress_details.current_step,
testContent,
);

// Build Docker image
await buildDockerImage(repoDir, imageName, languageConfig.dockerfilePath);
Expand All @@ -112,7 +97,9 @@ export async function runTestProcess(request: TestRunRequest): Promise<void> {
if (testResult.stdout) {
SSELogger.log(commitSha, `Test output:\n${testResult.stdout}`);
}
if (testResult.stderr) {

// Only log stderr if it contains actual errors
if (testResult.stderr && testResult.exitCode !== 0) {
SSELogger.log(commitSha, `Test errors:\n${testResult.stderr}`);
}

Expand Down
23 changes: 16 additions & 7 deletions src/utils/testLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export async function loadTestFile(
challengeId: string,
language: string,
stage: number,
): Promise<string> {
): Promise<string | null> {
// Changed return type
try {
const testRepo = TestRepoManager.getInstance();
const testContent = await testRepo.getTestContent(
Expand All @@ -14,11 +15,19 @@ export async function loadTestFile(
stage,
);

logger.info("Test file loaded successfully", {
challengeId,
language,
stage,
});
if (testContent) {
logger.info("Test file loaded successfully", {
challengeId,
language,
stage,
});
} else {
logger.info("No test file found, will run code directly", {
challengeId,
language,
stage,
});
}

return testContent;
} catch (error) {
Expand All @@ -28,6 +37,6 @@ export async function loadTestFile(
language,
stage,
});
throw new Error(`Test file not found for stage ${stage}`);
return null; // Return null instead of throwing error
}
}
8 changes: 4 additions & 4 deletions src/utils/testRepoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export class TestRepoManager {
challengeId: string,
language: string,
stage: number,
): Promise<string> {
): Promise<string | null> {
// changed return type to allow null in case of no test file(need to review this flow)
const repoDir = await this.ensureRepoUpdated();
const testPath = path.join(
repoDir,
Expand All @@ -85,14 +86,13 @@ export class TestRepoManager {
logger.info("Test content retrieved successfully");
return content;
} catch (error) {
logger.error("Error reading test file", {
logger.info("No test file found, will run code directly", {
testPath,
error,
challengeId,
language,
stage,
});
throw new Error(`Test file not found: ${testPath}`);
return null; // Return null instead of throwing error
}
}

Expand Down

0 comments on commit 051ae40

Please sign in to comment.