From 5218b72c75dd3b9068eb30906f2a539443ac333e Mon Sep 17 00:00:00 2001 From: juchechu Date: Mon, 17 Aug 2026 10:15:20 +0100 Subject: [PATCH] fix: Python expected-value JSON crash + analysis modal scroll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - formatPythonExpected: validates expected as JSON, wraps bare strings via json.Marshal - Python template: try/except JSONDecodeError fallback around json.loads(expected) - AnalysisModal: right pane overflow-hidden → overflow-y-auto for full scroll - ExplainPanel + AnalysisHydration: remove h-full constraint, natural content flow - 10 new test cases for formatPythonExpected + template assertion update --- .../best-practices/AnalysisHydration.tsx | 2 +- .../best-practices/AnalysisModal.tsx | 2 +- .../best-practices/ExplainPanel.tsx | 4 +-- internal/executor/executor.go | 19 +++++++++++- internal/executor/executor_test.go | 31 +++++++++++++++++++ internal/executor/templates.go | 5 ++- 6 files changed, 57 insertions(+), 6 deletions(-) diff --git a/frontend/components/best-practices/AnalysisHydration.tsx b/frontend/components/best-practices/AnalysisHydration.tsx index 004df2ee..a09786e0 100644 --- a/frontend/components/best-practices/AnalysisHydration.tsx +++ b/frontend/components/best-practices/AnalysisHydration.tsx @@ -24,7 +24,7 @@ export function AnalysisHydration({ partial.best_practices_score != null; return ( -
+
Generating analysis diff --git a/frontend/components/best-practices/AnalysisModal.tsx b/frontend/components/best-practices/AnalysisModal.tsx index bafa945b..6a678add 100644 --- a/frontend/components/best-practices/AnalysisModal.tsx +++ b/frontend/components/best-practices/AnalysisModal.tsx @@ -342,7 +342,7 @@ export function AnalysisModal({
{/* RIGHT: Telemetry & Tabbed Analysis */} -
+
+
{/* Top Telemetry: Quality Gauge + Score Radar */}
@@ -80,7 +80,7 @@ export function ExplainPanel({
{/* Scrollable Tab Content */} -
+
{activeTab === "overview" && ( <> diff --git a/internal/executor/executor.go b/internal/executor/executor.go index b18e3e65..edf8e299 100644 --- a/internal/executor/executor.go +++ b/internal/executor/executor.go @@ -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) { @@ -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), } } diff --git a/internal/executor/executor_test.go b/internal/executor/executor_test.go index 5e261812..844ed9e9 100644 --- a/internal/executor/executor_test.go +++ b/internal/executor/executor_test.go @@ -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) { diff --git a/internal/executor/templates.go b/internal/executor/templates.go index 68439e1c..5550926c 100644 --- a/internal/executor/templates.go +++ b/internal/executor/templates.go @@ -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}")