Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/components/best-practices/AnalysisHydration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function AnalysisHydration({
partial.best_practices_score != null;

return (
<div className="flex h-full flex-col space-y-4">
<div className="flex flex-col space-y-4">
<div className="flex items-center gap-2 text-sm font-semibold text-foreground">
<Loader2 size={15} className="animate-spin text-purple-300" />
Generating analysis
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/best-practices/AnalysisModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ export function AnalysisModal({
</div>

{/* RIGHT: Telemetry & Tabbed Analysis */}
<div className="flex min-h-0 flex-col overflow-hidden bg-[#141414] p-5">
<div className="flex min-h-0 flex-col overflow-y-auto bg-[#141414] p-5">
<ExplainPanel
explanation={explanation}
loading={loading}
Expand Down
4 changes: 2 additions & 2 deletions frontend/components/best-practices/ExplainPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function ExplainPanel({
if (!explanation) return null;

return (
<div className="flex h-full flex-col space-y-4">
<div className="flex flex-col space-y-4">
{/* Top Telemetry: Quality Gauge + Score Radar */}
<div className="grid shrink-0 grid-cols-2 gap-3">
<QualityGauge score={explanation.quality_score} />
Expand Down Expand Up @@ -80,7 +80,7 @@ export function ExplainPanel({
</div>

{/* Scrollable Tab Content */}
<div className="min-h-0 flex-1 space-y-4 overflow-y-auto pr-1">
<div className="space-y-4 pr-1">
{activeTab === "overview" && (
<>
<AnalysisSummary summary={explanation.summary} />
Expand Down
19 changes: 18 additions & 1 deletion internal/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,23 @@ func formatPythonLiteral(paramType string, data []byte) (string, error) {
return "", fmt.Errorf("unsupported type %q for Python literal", paramType)
}

// formatPythonExpected ensures a test case expected value is valid JSON
// before it is embedded in the Python test template. Bare strings (e.g.
// fish) are wrapped as JSON strings ("fish") so json.loads in the template
// does not fail with Expecting value.
func formatPythonExpected(raw string) string {
var js json.RawMessage
if err := json.Unmarshal([]byte(raw), &js); err == nil {
return raw // already valid JSON
}
// Not valid JSON — treat as a bare string and wrap it as a JSON string.
b, err := json.Marshal(raw)
if err != nil {
return raw // should never happen for a Go string
}
return string(b)
}

// executePython runs Python code execution: prepares sandbox files,
// executes, parses output, optionally records submission + progress, and returns the result.
func (e *Executor) executePython(ctx context.Context, req ExecutionRequest, problem *store.Problem, testCases []store.TestCase, recordSubmission bool) (*ExecutionResult, error) {
Expand Down Expand Up @@ -1019,7 +1036,7 @@ func (e *Executor) executePython(ctx context.Context, req ExecutionRequest, prob
pyCases[i] = PyTestCaseRenderData{
Ordinal: tc.Ordinal,
PyInputs: pyInputs,
Expected: tc.Expected,
Expected: formatPythonExpected(tc.Expected),
}
}

Expand Down
31 changes: 31 additions & 0 deletions internal/executor/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,37 @@ func TestPythonTemplate_ComparisonLogic(t *testing.T) {
if !strings.Contains(content, "result == expected_val") {
t.Error("template missing result == expected_val")
}
if !strings.Contains(content, "json.JSONDecodeError") {
t.Error("template missing JSONDecodeError fallback for non-JSON expected values")
}
}

func TestFormatPythonExpected(t *testing.T) {
tests := []struct {
raw string
want string
}{
// Valid JSON passthrough
{"42", "42"},
{"true", "true"},
{"null", "null"},
{"[1,2,3]", "[1,2,3]"},
{`"hello"`, `"hello"`},
// Bare strings wrapped as JSON strings
{"fish", `"fish"`},
{"hello world", `"hello world"`},
{"", `""`},
// Strings with special chars
{"it's", `"it's"`},
{`he said "hi"`, `"he said \"hi\""`},
}

for _, tc := range tests {
got := formatPythonExpected(tc.raw)
if got != tc.want {
t.Errorf("formatPythonExpected(%q) = %q, want %q", tc.raw, got, tc.want)
}
}
}

func TestResolveProblemLanguageMeta(t *testing.T) {
Expand Down
5 changes: 4 additions & 1 deletion internal/executor/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ for tc in test_cases:
expected = tc["expected"]
try:
result = {{.FuncName}}(*inputs)
expected_val = json.loads(expected)
try:
expected_val = json.loads(expected)
except json.JSONDecodeError:
expected_val = expected
if result == expected_val:
passed += 1
print(f"--- PASS: TestSolution/case_{ordinal}")
Expand Down
Loading