Skip to content

Commit c9ed51e

Browse files
authored
Merge pull request #375 from cs50/fix/issue-374-json-serialization
Serialize expected and actual values correctly
2 parents cf10651 + 2250164 commit c9ed51e

File tree

2 files changed

+105
-1
lines changed

2 files changed

+105
-1
lines changed

check50/_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ def _safe_truncate(x, y):
468468

469469
super().__init__(rationale=rationale, help=help)
470470

471-
self.payload.update({"expected": expected, "actual": actual})
471+
self.payload.update({"expected": _raw(expected), "actual": _raw(actual)})
472472

473473

474474
def hidden(failure_rationale):

tests/api_tests.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,5 +294,109 @@ def test_no_reject(self):
294294
with self.assertRaises(check50.Failure):
295295
self.process.reject()
296296

297+
class TestMismatch(unittest.TestCase):
298+
"""Test Mismatch exception class for proper JSON serialization."""
299+
300+
def test_json_serialization_with_strings(self):
301+
"""Test that regular strings are properly escaped for JSON."""
302+
import json
303+
304+
test_cases = [
305+
# Regular strings
306+
("hello", "world"),
307+
# Strings with quotes
308+
('Hello "World"', 'Goodbye "World"'),
309+
# Strings with newlines
310+
("First\nSecond", "First\nDifferent"),
311+
# Strings with backslashes
312+
("Path\\to\\file", "Path\\to\\other"),
313+
# JSON-like strings
314+
('{"key": "value"}', '{"key": "different"}'),
315+
# Mixed special characters
316+
('Line with \\ and " and \n', 'Another \\ line " with \n'),
317+
]
318+
319+
for expected, actual in test_cases:
320+
with self.subTest(expected=expected, actual=actual):
321+
mismatch = check50.Mismatch(expected, actual)
322+
323+
# Ensure payload can be serialized to JSON
324+
json_str = json.dumps(mismatch.payload)
325+
326+
# Ensure it can be parsed back
327+
parsed = json.loads(json_str)
328+
329+
# Verify expected fields are present
330+
self.assertIn('rationale', parsed)
331+
self.assertIn('expected', parsed)
332+
self.assertIn('actual', parsed)
333+
self.assertIsNone(parsed.get('help'))
334+
335+
def test_json_serialization_with_special_values(self):
336+
"""Test that special values like EOF and class types are handled."""
337+
import json
338+
from pexpect.exceptions import EOF, TIMEOUT
339+
340+
test_cases = [
341+
# EOF and TIMEOUT constants
342+
(check50.EOF, "some output"),
343+
("some input", check50.EOF),
344+
(check50.EOF, check50.EOF),
345+
# Class types (simulating the error case)
346+
(EOF, "output"),
347+
("input", EOF),
348+
(EOF, TIMEOUT),
349+
]
350+
351+
for expected, actual in test_cases:
352+
with self.subTest(expected=expected, actual=actual):
353+
mismatch = check50.Mismatch(expected, actual)
354+
355+
# Ensure payload can be serialized to JSON
356+
json_str = json.dumps(mismatch.payload)
357+
358+
# Ensure it can be parsed back
359+
parsed = json.loads(json_str)
360+
361+
# Verify expected fields are present and are strings
362+
self.assertIn('rationale', parsed)
363+
self.assertIn('expected', parsed)
364+
self.assertIn('actual', parsed)
365+
366+
# Ensure values in payload are strings, not class types
367+
self.assertIsInstance(parsed['expected'], str)
368+
self.assertIsInstance(parsed['actual'], str)
369+
370+
def test_mismatch_with_help(self):
371+
"""Test that help messages are included in the payload."""
372+
import json
373+
374+
mismatch = check50.Mismatch("expected", "actual", help="Did you forget something?")
375+
376+
# Ensure payload can be serialized to JSON
377+
json_str = json.dumps(mismatch.payload)
378+
parsed = json.loads(json_str)
379+
380+
# Verify help is in the payload
381+
self.assertEqual(parsed['help'], "Did you forget something?")
382+
383+
def test_mismatch_with_truncation(self):
384+
"""Test that long strings are truncated properly."""
385+
import json
386+
387+
# Create very long strings that will be truncated
388+
long_expected = "a" * 1000
389+
long_actual = "b" * 1000
390+
391+
mismatch = check50.Mismatch(long_expected, long_actual)
392+
393+
# Ensure payload can be serialized to JSON
394+
json_str = json.dumps(mismatch.payload)
395+
parsed = json.loads(json_str)
396+
397+
# Verify truncation occurred (should have ellipsis)
398+
self.assertIn("...", parsed['expected'])
399+
self.assertIn("...", parsed['actual'])
400+
297401
if __name__ == '__main__':
298402
unittest.main()

0 commit comments

Comments
 (0)