File size: 3,255 Bytes
ceea884
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
"""์ŠคํŠธ๋ฆฌ๋ฐ JSON ๋ฆฌ๋” โ€” ๊ฒฝ๊ณ„ ์กฐ๊ฑด ์œ„์ฃผ.

์ด ํŒŒ์„œ๋Š” 1.35 GB ํŒŒ์ผ์„ ๋‹ค๋ฃจ๋ฏ€๋กœ '๋ฒ„ํผ ๊ฒฝ๊ณ„์— ์›์†Œ๊ฐ€ ๊ฑธ์น˜๋Š”' ๊ฒฝ์šฐ๊ฐ€ ๋ฐ˜๋“œ์‹œ ์ƒ๊ธด๋‹ค.
์ฒญํฌ ํฌ๊ธฐ๋ฅผ ์•„์ฃผ ์ž‘๊ฒŒ ๊ฐ•์ œํ•ด ๊ทธ ๊ฒฝ๋กœ๋ฅผ ์ผ๋ถ€๋Ÿฌ ๋ฐŸ๋Š”๋‹ค.
"""

import json

import pytest

from nusim.io.jsonstream import index_table, iter_json_array, load_table


def _write(tmp_path, obj, name="t.json"):
    p = tmp_path / name
    p.write_text(json.dumps(obj, ensure_ascii=False))
    return p


def test_roundtrip_simple(tmp_path):
    data = [{"token": f"t{i}", "v": i} for i in range(100)]
    assert list(iter_json_array(_write(tmp_path, data))) == data


def test_empty_array(tmp_path):
    assert list(iter_json_array(_write(tmp_path, []))) == []


def test_single_element(tmp_path):
    assert list(iter_json_array(_write(tmp_path, [{"a": 1}]))) == [{"a": 1}]


@pytest.mark.parametrize("chunk", [1, 2, 3, 7, 16, 64, 4096])
def test_tiny_chunks_force_boundary_splits(tmp_path, chunk):
    """์ฒญํฌ๊ฐ€ 1๋ฐ”์ดํŠธ์—ฌ๋„ ๊ฒฐ๊ณผ๊ฐ€ ๊ฐ™์•„์•ผ ํ•œ๋‹ค. ๊ฒฝ๊ณ„ ์žฌ์‹œ๋„ ๋กœ์ง์˜ ํ•ต์‹ฌ ๊ฒ€์ฆ."""
    data = [{"token": f"tok{i}", "nested": {"x": [1, 2, 3]}, "s": "a" * i} for i in range(40)]
    p = _write(tmp_path, data)
    assert list(iter_json_array(p, chunk_size=chunk)) == data


def test_unicode_and_escapes(tmp_path):
    data = [
        {"desc": "์‹ฑ๊ฐ€ํฌ๋ฅด onenorth, ์šฐ์ฒœ"},
        {"desc": 'quote " and backslash \\ and bracket ] inside'},
        {"desc": "์ƒˆ\n์ค„\tํƒญ"},
    ]
    p = _write(tmp_path, data)
    # ']' ๊ฐ€ ๋ฌธ์ž์—ด ์•ˆ์— ์žˆ์–ด๋„ ๋ฐฐ์—ด ๋์œผ๋กœ ์˜ค์ธํ•˜๋ฉด ์•ˆ ๋œ๋‹ค
    assert list(iter_json_array(p, chunk_size=3)) == data


def test_whitespace_and_pretty_printed(tmp_path):
    data = [{"a": 1}, {"b": 2}]
    p = tmp_path / "pretty.json"
    p.write_text("\n\n  " + json.dumps(data, indent=4) + "\n\n")
    assert list(iter_json_array(p, chunk_size=5)) == data


def test_not_an_array(tmp_path):
    p = tmp_path / "obj.json"
    p.write_text('{"a": 1}')
    with pytest.raises(ValueError, match="๋ฐฐ์—ด"):
        list(iter_json_array(p))


def test_truncated_file_raises(tmp_path):
    p = tmp_path / "cut.json"
    p.write_text('[{"a": 1}, {"b": ')
    with pytest.raises(ValueError, match="์ž˜๋ ธ"):
        list(iter_json_array(p, chunk_size=4))


def test_load_table_filters_rows_and_fields(tmp_path):
    data = [{"token": str(i), "keep": i % 2 == 0, "big": "x" * 50} for i in range(10)]
    p = _write(tmp_path, data)
    got = load_table(p, keep=lambda r: r["keep"], fields=("token",))
    assert got == [{"token": str(i)} for i in range(0, 10, 2)]


def test_index_table(tmp_path):
    data = [{"token": "a", "v": 1}, {"token": "b", "v": 2}]
    got = index_table(_write(tmp_path, data), fields=("v",))
    assert got == {"a": {"v": 1}, "b": {"v": 2}}


def test_memory_stays_bounded(tmp_path):
    """์›์†Œ๋ฅผ ๋‹ค ๋ชจ์œผ์ง€ ์•Š๊ณ  ํ˜๋ ค๋ณด๋‚ด๋Š”์ง€. ์ œ๋„ˆ๋ ˆ์ดํ„ฐ๊ฐ€ ๋ฆฌ์ŠคํŠธ๋กœ ํ‡ดํ™”ํ•˜๋ฉด ์‹คํŒจํ•œ๋‹ค."""
    import sys

    data = [{"i": i, "pad": "y" * 200} for i in range(5000)]
    p = _write(tmp_path, data)
    it = iter_json_array(p, chunk_size=1024)
    first = next(it)
    assert first["i"] == 0
    # ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ์ž์ฒด๋Š” ์ž‘์•„์•ผ ํ•œ๋‹ค
    assert sys.getsizeof(it) < 1000