|
| 1 | +import os |
| 2 | +import pathlib |
| 3 | +import tempfile |
| 4 | +import json |
| 5 | + |
| 6 | +from substrait.proto import Plan |
| 7 | +from substrait.json import load_json, parse_json, dump_json, write_json |
| 8 | + |
| 9 | +import pytest |
| 10 | + |
| 11 | + |
| 12 | +JSON_FIXTURES = ( |
| 13 | + pathlib.Path(os.path.dirname(__file__)) |
| 14 | + / ".." |
| 15 | + / "third_party" |
| 16 | + / "substrait-cpp" |
| 17 | + / "src" |
| 18 | + / "substrait" |
| 19 | + / "textplan" |
| 20 | + / "data" |
| 21 | +) |
| 22 | +JSON_TEST_FILE = sorted(JSON_FIXTURES.glob("*.json")) |
| 23 | +JSON_TEST_FILENAMES = [path.name for path in JSON_TEST_FILE] |
| 24 | + |
| 25 | + |
| 26 | +@pytest.mark.parametrize("jsonfile", JSON_TEST_FILE, ids=JSON_TEST_FILENAMES) |
| 27 | +def test_json_load(jsonfile): |
| 28 | + with open(jsonfile) as f: |
| 29 | + jsondata = _strip_json_comments(f) |
| 30 | + parsed_plan = parse_json(jsondata) |
| 31 | + |
| 32 | + # Save to a temporary file so we can test load_json |
| 33 | + # on content stripped of comments. |
| 34 | + with tempfile.TemporaryDirectory() as tmpdir: |
| 35 | + # We use a TemporaryDirectory as on Windows NamedTemporaryFile |
| 36 | + # doesn't allow for easy reopening of the file. |
| 37 | + with open(pathlib.Path(tmpdir) / "jsonfile.json", "w+") as stripped_file: |
| 38 | + stripped_file.write(jsondata) |
| 39 | + loaded_plan = load_json(stripped_file.name) |
| 40 | + |
| 41 | + # The Plan constructor itself will throw an exception |
| 42 | + # in case there is anything wrong in parsing the JSON |
| 43 | + # so we can take for granted that if the plan was created |
| 44 | + # it is a valid plan in terms of protobuf definition. |
| 45 | + assert type(loaded_plan) is Plan |
| 46 | + |
| 47 | + # Ensure that when loading from file or from string |
| 48 | + # the outcome is the same |
| 49 | + assert parsed_plan == loaded_plan |
| 50 | + |
| 51 | + |
| 52 | +@pytest.mark.parametrize("jsonfile", JSON_TEST_FILE, ids=JSON_TEST_FILENAMES) |
| 53 | +def test_json_roundtrip(jsonfile): |
| 54 | + with open(jsonfile) as f: |
| 55 | + jsondata = _strip_json_comments(f) |
| 56 | + |
| 57 | + parsed_plan = parse_json(jsondata) |
| 58 | + assert parse_json(dump_json(parsed_plan)) == parsed_plan |
| 59 | + |
| 60 | + # Test with write/load |
| 61 | + with tempfile.TemporaryDirectory() as tmpdir: |
| 62 | + filename = pathlib.Path(tmpdir) / "jsonfile.json" |
| 63 | + write_json(parsed_plan, filename) |
| 64 | + assert load_json(filename) == parsed_plan |
| 65 | + |
| 66 | + |
| 67 | +def _strip_json_comments(jsonfile): |
| 68 | + # The JSON files in the cpp testsuite are prefixed with |
| 69 | + # a comment containing the SQL that matches the json plan. |
| 70 | + # As Python JSON parser doesn't support comments, |
| 71 | + # we have to strip them to make the content readable |
| 72 | + return "\n".join(l for l in jsonfile.readlines() if l[0] != "#") |
0 commit comments