Skip to content

Commit 87c1be4

Browse files
committed
major code quality improvements
1 parent 5dde9b1 commit 87c1be4

File tree

14 files changed

+462
-140
lines changed

14 files changed

+462
-140
lines changed

bun.lock

Lines changed: 245 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bunfig.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
coveragePathIgnorePatterns = [
44
"src/lib/docker.ts",
55
"src/lib/files.ts"
6-
]
6+
]

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
"zod": "^4.0.14"
1515
},
1616
"devDependencies": {
17-
"@types/bun": "^1.3.1"
17+
"@types/bun": "^1.3.1",
18+
"@typescript-eslint/eslint-plugin": "^8.46.2",
19+
"@typescript-eslint/parser": "^8.46.2",
20+
"eslint": "^9.38.0"
1821
}
1922
}

pyproject.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[tool.pyright]
2+
# Global settings
3+
reportIncompatibleMethodOverride = false
4+
5+
# Main configuration for src/lib/python
6+
include = ["src/lib/python/**/*.py"]
7+
reportOptionalMemberAccess = false
8+
reportMissingImports = false
9+
reportAny = false
10+
11+
[[tool.pyright.executionEnvironments]]
12+
# Here, pretty much all rules must be disabled
13+
# This code will be executed in a container
14+
# And so, the linter does not know the context, and is unhappy about it.
15+
root = "tests/data"
16+
reportIncompatibleMethodOverride = false
17+
reportMissingImports = false
18+
reportAny = false
19+
reportMissingTypeArgument = false

src/controllers/metrics.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import { printMetrics } from "../lib/prometheus";
77

88
const app = new OpenAPIHono();
99

10+
// Path is overriden, as I prefer to have the
11+
// full path in lib/openapi.ts to keep the
12+
// semantics there and in index.ts
1013
app.openapi(
1114
{ ...metricsRoute, path: "/" },
1215
async (c): Promise<RouteConfigToTypedResponse<typeof metricsRoute>> => {
@@ -22,8 +25,9 @@ app.openapi(
2225
401,
2326
);
2427
}
25-
// @ts-ignore This will return 200
26-
return printMetrics(c);
28+
return printMetrics(c) as unknown as RouteConfigToTypedResponse<
29+
typeof metricsRoute
30+
>;
2731
},
2832
);
2933

src/controllers/status.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
import { OpenAPIHono, type RouteConfigToTypedResponse } from "@hono/zod-openapi";
1+
import {
2+
OpenAPIHono,
3+
type RouteConfigToTypedResponse,
4+
} from "@hono/zod-openapi";
25
import { statusRoute } from "../lib/openapi";
36
import { getActiveContainers } from "../lib/docker";
47

58
const app = new OpenAPIHono();
69

10+
// Path is overriden, as I prefer to have the
11+
// full path in lib/openapi.ts to keep the
12+
// semantics there and in index.ts
713
app.openapi(
814
{ ...statusRoute, path: "/" },
915
async (c): Promise<RouteConfigToTypedResponse<typeof statusRoute>> => {
1016
return c.json({ runningContainers: getActiveContainers().size }, 200);
1117
},
1218
);
1319

14-
export default app;
20+
export default app;

src/controllers/unit-tests.ts

Lines changed: 86 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,114 @@
11
import {
2-
OpenAPIHono,
3-
type RouteConfigToTypedResponse,
2+
OpenAPIHono,
3+
type RouteConfigToTypedResponse,
44
} from "@hono/zod-openapi";
55
import { runUnitTestRequestSchema } from "../lib/validation";
66
import { v4 } from "uuid";
77
import {
8-
cleanupSessionDirectory,
9-
createSessionDirectory,
10-
prepareUnitTestFiles,
8+
cleanupSessionDirectory,
9+
createSessionDirectory,
10+
prepareUnitTestFiles,
1111
} from "../lib/files";
1212
import {
13-
buildDockerCommand,
14-
getContainerName,
15-
trackContainer,
16-
untrackContainer,
13+
buildDockerCommand,
14+
getContainerName,
15+
trackContainer,
16+
untrackContainer,
1717
} from "../lib/docker";
1818
import { unitTestRoute } from "../lib/openapi";
1919

2020
const app = new OpenAPIHono();
2121

22+
// Path is overriden, as I prefer to have the
23+
// full path in lib/openapi.ts to keep the
24+
// semantics there and in index.ts
2225
app.openapi(
23-
{ ...unitTestRoute, path: "/" },
24-
async (c): Promise<RouteConfigToTypedResponse<typeof unitTestRoute>> => {
25-
let body = null;
26+
{ ...unitTestRoute, path: "/" },
27+
async (c): Promise<RouteConfigToTypedResponse<typeof unitTestRoute>> => {
28+
let body = null;
2629

27-
try {
28-
body = await c.req.json();
29-
} catch {
30-
return c.json({ success: false, error: "Invalid JSON" }, 400);
31-
}
30+
try {
31+
body = await c.req.json();
32+
} catch {
33+
return c.json({ success: false, error: "Invalid JSON" }, 400);
34+
}
3235

33-
const validatedData = runUnitTestRequestSchema.safeParse(body);
36+
const validatedData = runUnitTestRequestSchema.safeParse(body);
3437

35-
if (!validatedData.success) {
36-
return c.json(
37-
{ success: false, error: validatedData.error.message },
38-
400,
39-
);
40-
}
38+
if (!validatedData.success) {
39+
return c.json(
40+
{ success: false, error: validatedData.error.message },
41+
400,
42+
);
43+
}
4144

42-
const { user_code, unit_tests, api_key, config } = validatedData.data;
45+
const { user_code, unit_tests, api_key, config } = validatedData.data;
4346

44-
if (api_key !== Bun.env.API_KEY) {
45-
return c.json({ success: false, error: "Invalid API key" }, 401);
46-
}
47+
if (api_key !== Bun.env.API_KEY) {
48+
return c.json({ success: false, error: "Invalid API key" }, 401);
49+
}
4750

48-
// Create unique session
49-
const sessionId = v4();
50-
const sessionPath = await createSessionDirectory(sessionId);
51-
const containerName = getContainerName(sessionId);
51+
// Create unique session
52+
const sessionId = v4();
53+
const sessionPath = await createSessionDirectory(sessionId);
54+
const containerName = getContainerName(sessionId);
5255

53-
try {
54-
// Setup files and container
55-
await prepareUnitTestFiles(sessionPath, user_code, unit_tests);
56-
trackContainer(containerName);
56+
try {
57+
// Setup files and container
58+
await prepareUnitTestFiles(sessionPath, user_code, unit_tests);
59+
trackContainer(containerName);
5760

58-
// Execute in Docker
59-
const command = buildDockerCommand(
60-
sessionId,
61-
sessionPath,
62-
"unit-test",
63-
config,
64-
);
65-
const proc = Bun.spawn(command, {
66-
stdout: "pipe",
67-
stderr: "pipe",
68-
});
61+
// Execute in Docker
62+
const command = buildDockerCommand(sessionId, sessionPath, config);
63+
const proc = Bun.spawn(command, {
64+
stdout: "pipe",
65+
stderr: "pipe",
66+
});
6967

70-
const stdout = await new Response(proc.stdout).text();
71-
const stderr = await new Response(proc.stderr).text();
68+
const stdout = await new Response(proc.stdout).text();
69+
const stderr = await new Response(proc.stderr).text();
7270

73-
// Wait for process to complete and get exit code
74-
const exitCode = await proc.exited;
71+
// Wait for process to complete and get exit code
72+
const exitCode = await proc.exited;
7573

76-
const result = stdout ? JSON.parse(stdout) : undefined;
74+
const result = stdout ? JSON.parse(stdout) : undefined;
7775

78-
return c.json(
79-
{
80-
success: exitCode == 0,
81-
runalyzer_output: result,
82-
runalyzer_errors:
83-
stderr || (exitCode === 124 ? "Time limit exceeded" : ""),
84-
exit_code: exitCode,
85-
},
86-
200,
87-
);
88-
} catch (error) {
89-
console.error(error);
90-
return c.json({ success: false, error: "Internal server error" }, 500);
91-
} finally {
92-
untrackContainer(containerName);
93-
await cleanupSessionDirectory(sessionPath, sessionId);
94-
}
95-
},
76+
// Yes, this could be one return
77+
// But typescript wants it to be split
78+
// Because of the discriminatory union
79+
if (exitCode === 0) {
80+
return c.json(
81+
{
82+
success: true,
83+
runalyzer_output: result,
84+
runalyzer_errors: stderr,
85+
exit_code: exitCode,
86+
},
87+
200,
88+
);
89+
}
90+
return c.json(
91+
{
92+
success: false,
93+
runalyzer_output: result,
94+
runalyzer_errors:
95+
stderr ||
96+
(exitCode === 124 ? "Time limit exceeded" : ""),
97+
exit_code: exitCode,
98+
},
99+
200,
100+
);
101+
} catch (error) {
102+
console.error(error);
103+
return c.json(
104+
{ success: false, error: "Internal server error" },
105+
500,
106+
);
107+
} finally {
108+
untrackContainer(containerName);
109+
await cleanupSessionDirectory(sessionPath, sessionId);
110+
}
111+
},
96112
);
97113

98114
export default app;

src/lib/docker.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ let activeContainers = new Set<string>();
88
export function buildDockerCommand(
99
sessionId: string,
1010
sessionPath: string,
11-
type: "unit-test",
1211
config?: Config,
1312
): string[] {
1413
const containerName = `code-runner-${sessionId}`;
@@ -56,17 +55,21 @@ export function untrackContainer(containerName: string): void {
5655

5756
export async function cleanupContainer(containerName: string): Promise<void> {
5857
try {
59-
Bun.spawn(["docker", "kill", containerName], {
58+
const killProc = Bun.spawn(["docker", "kill", containerName], {
6059
stdout: "ignore",
6160
stderr: "ignore",
6261
});
62+
// Wait for the kill command to complete before attempting to remove the container.
63+
// This prevents a race condition where `docker rm` is called before the container has stopped.
64+
await killProc.exited;
6365
Bun.spawn(["docker", "rm", "-f", containerName], {
6466
stdout: "ignore",
6567
stderr: "ignore",
6668
});
6769
activeContainers.delete(containerName);
6870
} catch (err) {
69-
// Ignore cleanup errors
71+
// This is non-crucial as containers have the --rm flag
72+
console.error(`Error cleaning up container ${containerName}:`, err);
7073
}
7174
}
7275

src/lib/openapi.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ export const metricsRoute = createRoute({
8888
responses: {
8989
200: {
9090
content: {
91-
"application/json": {
92-
schema: z.any(), // Managed by prometheus
91+
"text/plain": {
92+
schema: z.string(),
9393
},
9494
},
9595
description: "Server metrics as returned by printMetrics().",

src/lib/python/unit-test-runner.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import sys
55
import io
66
import time
7+
78
from unittests import test
89

910
try:
@@ -12,7 +13,7 @@
1213
sys.__stdout__.write(
1314
json.dumps(
1415
{
15-
"runalyzer_errors": "Nie znaleziono funkcji solution.",
16+
"runalyzer_errors": "Solution function not found.",
1617
"test_result": None,
1718
"stdout": None,
1819
"stderr": None,
@@ -22,6 +23,8 @@
2223
)
2324
exit(1)
2425

26+
MAX_LENGTH = 1024
27+
2528

2629
def truncate_output(output, max_chars=256):
2730
"""Truncate output to max_chars and add ... if needed"""
@@ -52,8 +55,8 @@ def truncate_output(output, max_chars=256):
5255
exit(1)
5356

5457
# Get captured output and truncate
55-
stdout_data = truncate_output(stdout_capture.getvalue(), 1024)
56-
stderr_data = truncate_output(stderr_capture.getvalue(), 1024)
58+
stdout_data = truncate_output(stdout_capture.getvalue(), MAX_LENGTH)
59+
stderr_data = truncate_output(stderr_capture.getvalue(), MAX_LENGTH)
5760

5861
# Redirect further output to /dev/null
5962
with open(os.devnull, "w") as devnull:
@@ -66,8 +69,8 @@ def truncate_output(output, max_chars=256):
6669
"stdout": stdout_data,
6770
"stderr": stderr_data,
6871
"duration": duration,
69-
"truncated": len(stdout_capture.getvalue()) > 1024
70-
or len(stderr_capture.getvalue()) > 1024,
72+
"truncated": len(stdout_capture.getvalue()) > MAX_LENGTH
73+
or len(stderr_capture.getvalue()) > MAX_LENGTH,
7174
}
7275
)
7376
)

0 commit comments

Comments
 (0)