diff --git a/jsonpatch_cli.py b/jsonpatch_cli.py index 8ef787d..e3697e3 100644 --- a/jsonpatch_cli.py +++ b/jsonpatch_cli.py @@ -8,6 +8,7 @@ import jsonpatch import tempfile import argparse +from contextlib import ExitStack jsondiff_parser = argparse.ArgumentParser( @@ -35,8 +36,12 @@ def jsondiff_main(): def diff_files(): """ Diffs two JSON files and prints a patch """ args = jsondiff_parser.parse_args() - doc1 = json.load(args.FILE1) - doc2 = json.load(args.FILE2) + with ExitStack() as inputs: + for stream in (args.FILE1, args.FILE2): + if stream is not sys.stdin: + inputs.enter_context(stream) + doc1 = json.load(args.FILE1) + doc2 = json.load(args.FILE2) patch = jsonpatch.make_patch(doc1, doc2, generate_test_ops=args.test_ops) if patch.patch: print(json.dumps(patch.patch, indent=args.indent, ensure_ascii=not(args.preserve_unicode))) diff --git a/tests.py b/tests.py index 5cd2ca2..ec52d5b 100755 --- a/tests.py +++ b/tests.py @@ -2711,6 +2711,63 @@ def test_jsondiff_equal_files(self): res = self.run_script('jsondiff', src, dst) self.assertEqual((res.returncode, res.stdout), (0, ''), res.stderr) + def test_jsondiff_closes_inputs(self): + for target in (1, 2): + with self.subTest(target=target): + src = self.write('a.json', {'a': 1}) + dst = self.write('b.json', {'a': target}) + parser = jsonpatch_cli.jsondiff_parser + args = parser.parse_args([src, dst]) + self.addCleanup(args.FILE1.close) + self.addCleanup(args.FILE2.close) + output = io.StringIO() + with mock.patch.object(parser, 'parse_args', return_value=args), \ + contextlib.redirect_stdout(output): + if target == 1: + jsonpatch_cli.diff_files() + self.assertEqual(output.getvalue(), '') + else: + with self.assertRaises(SystemExit) as caught: + jsonpatch_cli.diff_files() + self.assertEqual(caught.exception.code, 1) + self.assertEqual(jsonpatch.apply_patch({'a': 1}, json.loads(output.getvalue())), + {'a': target}) + self.assertTrue(args.FILE1.closed) + self.assertTrue(args.FILE2.closed) + + def test_jsondiff_closes_both_inputs_after_parse_error(self): + for invalid in (0, 1): + with self.subTest(invalid=invalid): + paths = [self.write('a.json', {}), self.write('b.json', {})] + with open(paths[invalid], 'w', encoding='utf-8') as f: + f.write('{') + parser = jsonpatch_cli.jsondiff_parser + args = parser.parse_args(paths) + self.addCleanup(args.FILE1.close) + self.addCleanup(args.FILE2.close) + with mock.patch.object(parser, 'parse_args', return_value=args): + with self.assertRaises(json.JSONDecodeError): + jsonpatch_cli.diff_files() + self.assertTrue(args.FILE1.closed) + self.assertTrue(args.FILE2.closed) + + def test_jsondiff_keeps_stdin_open(self): + for stdin_first in (False, True): + with self.subTest(stdin_first=stdin_first): + path = self.write('a.json', {'a': 1}) + stdin = io.StringIO('{"a": 1}') + self.addCleanup(stdin.close) + parser = jsonpatch_cli.jsondiff_parser + with mock.patch.object(sys, 'stdin', stdin): + args = parser.parse_args(['-', path] if stdin_first else [path, '-']) + owned = args.FILE2 if stdin_first else args.FILE1 + self.addCleanup(owned.close) + with mock.patch.object(parser, 'parse_args', return_value=args), \ + contextlib.redirect_stdout(io.StringIO()): + jsonpatch_cli.diff_files() + self.assertFalse(stdin.closed) + self.assertTrue(owned.closed) + def test_jsondiff_indent(self): src = self.write('a.json', {'a': 1}) dst = self.write('b.json', {'b': 1})