|
| 1 | +# BSD 3-Clause License; see https://github.com/scikit-hep/awkward/blob/main/LICENSE |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import sys |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +import pytest |
| 9 | + |
| 10 | +import awkward as ak |
| 11 | + |
| 12 | +forth_only = pytest.mark.skipif( |
| 13 | + sys.byteorder == "big", |
| 14 | + reason="AwkwardForth not yet supported on big-endian systems", |
| 15 | +) |
| 16 | + |
| 17 | + |
| 18 | +def test_from_json_nullable_record_missing_option_key(): |
| 19 | + # A nullable record (type ["object", "null"]) with a missing option-type |
| 20 | + # key used to raise a spurious "JSON schema mismatch" because the |
| 21 | + # instruction stack was left unbalanced (switch fall-through in |
| 22 | + # nulls_for_optiontype). It should null-fill the missing key, exactly like |
| 23 | + # the non-nullable record does. |
| 24 | + schema = { |
| 25 | + "type": "array", |
| 26 | + "items": { |
| 27 | + "type": ["object", "null"], |
| 28 | + "properties": { |
| 29 | + "x": {"type": "integer"}, |
| 30 | + "y": {"type": ["integer", "null"]}, |
| 31 | + }, |
| 32 | + }, |
| 33 | + } |
| 34 | + out = ak.from_json('[{"x": 1}, null, {"x": 2, "y": 3}]', schema=schema) |
| 35 | + assert out.tolist() == [{"x": 1, "y": None}, None, {"x": 2, "y": 3}] |
| 36 | + |
| 37 | + |
| 38 | +def test_from_json_nonnullable_record_missing_option_key(): |
| 39 | + # The non-nullable counterpart already worked; keep it as a guard. |
| 40 | + schema = { |
| 41 | + "type": "array", |
| 42 | + "items": { |
| 43 | + "type": "object", |
| 44 | + "properties": { |
| 45 | + "x": {"type": "integer"}, |
| 46 | + "y": {"type": ["integer", "null"]}, |
| 47 | + }, |
| 48 | + }, |
| 49 | + } |
| 50 | + out = ak.from_json('[{"x": 1}, {"x": 2, "y": 3}]', schema=schema) |
| 51 | + assert out.tolist() == [{"x": 1, "y": None}, {"x": 2, "y": 3}] |
| 52 | + |
| 53 | + |
| 54 | +def test_arraybuilder_clear_then_reuse(): |
| 55 | + # RecordBuilder::clear() used to clear keys_/pointers_ while keeping |
| 56 | + # contents_, leaving the parallel arrays out of sync (out-of-bounds reads) |
| 57 | + # and length_ == -1 (ValueError from __len__). clear() must leave a |
| 58 | + # consistent, empty-but-structured state that can be reused. |
| 59 | + builder = ak.ArrayBuilder() |
| 60 | + with builder.record(): |
| 61 | + builder.field("x").integer(1) |
| 62 | + builder.field("y").real(2.5) |
| 63 | + |
| 64 | + ext = builder._layout |
| 65 | + assert len(ext) == 1 |
| 66 | + |
| 67 | + ext.clear() |
| 68 | + # No ValueError, length resets cleanly, form stays intact. |
| 69 | + assert len(ext) == 0 |
| 70 | + form_after_clear = ext.form() |
| 71 | + assert "RecordArray" in form_after_clear |
| 72 | + |
| 73 | + # Reuse after clear must succeed and produce a consistent array. |
| 74 | + with builder.record(): |
| 75 | + builder.field("x").integer(7) |
| 76 | + builder.field("y").real(8.5) |
| 77 | + assert len(ext) == 1 |
| 78 | + assert builder.snapshot().tolist() == [{"x": 7, "y": 8.5}] |
| 79 | + |
| 80 | + |
| 81 | +@forth_only |
| 82 | +def test_forthmachine_output_initial_size_zero(): |
| 83 | + # maybe_resize() used to loop forever when output_initial_size == 0 |
| 84 | + # (reservation stayed 0). It must grow and run to completion. |
| 85 | + vm = ak.forth.ForthMachine64( |
| 86 | + "output out int64 5 0 do i out <- stack loop", |
| 87 | + output_initial_size=0, |
| 88 | + ) |
| 89 | + vm.run({}) |
| 90 | + assert vm.output("out").tolist() == [0, 1, 2, 3, 4] |
| 91 | + |
| 92 | + |
| 93 | +@forth_only |
| 94 | +def test_forthmachine_output_resize_factor_one(): |
| 95 | + # A resize factor of exactly 1.0 also failed to grow geometrically. |
| 96 | + vm = ak.forth.ForthMachine64( |
| 97 | + "output out int64 5 0 do i out <- stack loop", |
| 98 | + output_initial_size=1, |
| 99 | + output_resize_factor=1.0, |
| 100 | + ) |
| 101 | + vm.run({}) |
| 102 | + assert vm.output("out").tolist() == [0, 1, 2, 3, 4] |
| 103 | + |
| 104 | + |
| 105 | +@forth_only |
| 106 | +@pytest.mark.parametrize( |
| 107 | + ("nbits", "nbytes"), |
| 108 | + [(32, 4), (33, 5), (40, 5), (64, 8)], |
| 109 | +) |
| 110 | +def test_forthmachine_nbit_mask_large_widths(nbits, nbytes): |
| 111 | + # mask = (1 << bit_width) - 1 shifted an int literal -> UB/wrong for |
| 112 | + # bit_width >= 31. Reading all-ones bytes must yield exactly nbits set. |
| 113 | + expected = (1 << nbits) - 1 |
| 114 | + vm = ak.forth.ForthMachine64( |
| 115 | + f"input data output out uint64 1 data #{nbits}bit-> out", |
| 116 | + output_initial_size=64, |
| 117 | + ) |
| 118 | + data = np.frombuffer(b"\xff" * nbytes, dtype=np.uint8) |
| 119 | + vm.run({"data": data}) |
| 120 | + (result,) = vm.output("out").tolist() |
| 121 | + assert (result & 0xFFFFFFFFFFFFFFFF) == (expected & 0xFFFFFFFFFFFFFFFF) |
0 commit comments