flow.record 3.22.dev3__tar.gz → 3.22.dev5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/PKG-INFO +1 -1
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/base.py +2 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/jsonpacker.py +14 -7
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/stream.py +7 -2
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/version.py +3 -3
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow.record.egg-info/PKG-INFO +1 -1
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow.record.egg-info/SOURCES.txt +1 -0
- flow_record-3.22.dev5/tests/fieldtypes/test_boolean.py +35 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/fieldtypes/test_fieldtypes.py +0 -30
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/packer/test_json_packer.py +10 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/record/test_adapter.py +8 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/tools/test_rdump.py +73 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/.git-blame-ignore-revs +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/.gitattributes +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/COPYRIGHT +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/LICENSE +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/MANIFEST.in +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/README.md +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/examples/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/examples/filesystem.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/examples/passivedns.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/examples/records.json +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/examples/selectors.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/examples/tcpconn.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/archive.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/avro.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/broker.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/csvfile.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/duckdb.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/elastic.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/jsonfile.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/line.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/mongo.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/split.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/splunk.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/sqlite.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/stream.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/text.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/adapter/xlsx.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/context.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/exceptions.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/fieldtypes/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/fieldtypes/credential.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/fieldtypes/net/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/fieldtypes/net/ip.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/fieldtypes/net/ipv4.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/fieldtypes/net/tcp.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/fieldtypes/net/udp.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/packer.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/selector.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/tools/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/tools/geoip.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/tools/rdump.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/utils.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow/record/whitelist.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow.record.egg-info/dependency_links.txt +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow.record.egg-info/entry_points.txt +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow.record.egg-info/requires.txt +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/flow.record.egg-info/top_level.txt +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/pyproject.toml +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/setup.cfg +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/_data/.gitkeep +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/_docs/Makefile +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/_docs/conf.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/_docs/index.rst +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/_utils.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/adapter/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/adapter/test_avro.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/adapter/test_csv.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/adapter/test_elastic.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/adapter/test_json.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/adapter/test_line.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/adapter/test_splunk.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/adapter/test_sqlite_duckdb.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/adapter/test_text.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/adapter/test_xlsx.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/conftest.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/fieldtypes/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/fieldtypes/test_ip.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/packer/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/packer/test_packer.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/record/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/record/test_context.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/record/test_descriptor.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/record/test_multi_timestamp.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/record/test_record.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/selector/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/selector/test_compiled.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/selector/test_selectors.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/test_deprecations.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/test_regressions.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/test_utils.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tests/tools/__init__.py +0 -0
- {flow_record-3.22.dev3 → flow_record-3.22.dev5}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flow.record
|
|
3
|
-
Version: 3.22.
|
|
3
|
+
Version: 3.22.dev5
|
|
4
4
|
Summary: A library for defining and creating structured data (called records) that can be streamed to disk or piped to other tools that use flow.record
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -893,6 +893,8 @@ def RecordAdapter(
|
|
|
893
893
|
"entering record text, rather than a record stream? This can be fixed by using "
|
|
894
894
|
"'rdump -w -' to write a record stream to stdout."
|
|
895
895
|
)
|
|
896
|
+
if not peek_data:
|
|
897
|
+
raise EOFError("Empty input stream")
|
|
896
898
|
raise RecordAdapterNotFound("Could not find adapter for file-like object")
|
|
897
899
|
|
|
898
900
|
# Now that we found an adapter, we will fall back into the same code path as when a URL is given. As the url
|
|
@@ -49,12 +49,7 @@ class JsonRecordPacker:
|
|
|
49
49
|
serial["_type"] = "record"
|
|
50
50
|
serial["_recorddescriptor"] = obj._desc.identifier
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
# Boolean field types should be cast to a bool instead of staying ints
|
|
54
|
-
if field_type == "boolean" and isinstance(serial[field_name], int):
|
|
55
|
-
serial[field_name] = bool(serial[field_name])
|
|
56
|
-
|
|
57
|
-
return serial
|
|
52
|
+
return self.convert_basic_types(serial)
|
|
58
53
|
if isinstance(obj, RecordDescriptor):
|
|
59
54
|
return {
|
|
60
55
|
"_type": "recorddescriptor",
|
|
@@ -102,7 +97,19 @@ class JsonRecordPacker:
|
|
|
102
97
|
return RecordDescriptor._unpack(*data)
|
|
103
98
|
return obj
|
|
104
99
|
|
|
105
|
-
def
|
|
100
|
+
def convert_basic_types(self, obj: Any) -> Any:
|
|
101
|
+
"""Explicitly convert some basic types when packing to JSON."""
|
|
102
|
+
if isinstance(obj, fieldtypes.boolean):
|
|
103
|
+
return bool(obj)
|
|
104
|
+
if isinstance(obj, dict):
|
|
105
|
+
return {k: self.convert_basic_types(v) for k, v in obj.items()}
|
|
106
|
+
if isinstance(obj, list):
|
|
107
|
+
return [self.convert_basic_types(item) for item in obj]
|
|
108
|
+
return obj
|
|
109
|
+
|
|
110
|
+
def pack(self, obj: Record | RecordDescriptor | dict) -> str:
|
|
111
|
+
if isinstance(obj, dict):
|
|
112
|
+
obj = self.convert_basic_types(obj)
|
|
106
113
|
return json.dumps(obj, default=self.pack_obj, indent=self.indent)
|
|
107
114
|
|
|
108
115
|
def unpack(self, d: str) -> RecordDescriptor | Record:
|
|
@@ -164,11 +164,13 @@ def record_stream(sources: list[str], selector: str | None = None) -> Iterator[R
|
|
|
164
164
|
print("[reading from stdin]", file=sys.stderr)
|
|
165
165
|
|
|
166
166
|
# Initial value for reader, in case of exception message
|
|
167
|
-
reader = "RecordReader"
|
|
167
|
+
reader: str | AbstractReader = "RecordReader"
|
|
168
168
|
try:
|
|
169
169
|
reader = RecordReader(src, selector=selector)
|
|
170
170
|
yield from reader
|
|
171
|
-
|
|
171
|
+
except EOFError as e:
|
|
172
|
+
# End of file reached, likely no records in source
|
|
173
|
+
log.warning("%s(%r): %s", reader, src, e)
|
|
172
174
|
except IOError as e:
|
|
173
175
|
if len(sources) == 1:
|
|
174
176
|
raise
|
|
@@ -184,6 +186,9 @@ def record_stream(sources: list[str], selector: str | None = None) -> Iterator[R
|
|
|
184
186
|
else:
|
|
185
187
|
log.warning("Exception in %r for %r: %s -- skipping to next reader", reader, src, aRepr.repr(e))
|
|
186
188
|
continue
|
|
189
|
+
finally:
|
|
190
|
+
if isinstance(reader, AbstractReader):
|
|
191
|
+
reader.close()
|
|
187
192
|
|
|
188
193
|
|
|
189
194
|
class PathTemplateWriter:
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '3.22.
|
|
32
|
-
__version_tuple__ = version_tuple = (3, 22, '
|
|
31
|
+
__version__ = version = '3.22.dev5'
|
|
32
|
+
__version_tuple__ = version_tuple = (3, 22, 'dev5')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'gc3f8cd8c6'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flow.record
|
|
3
|
-
Version: 3.22.
|
|
3
|
+
Version: 3.22.dev5
|
|
4
4
|
Summary: A library for defining and creating structured data (called records) that can be streamed to disk or piped to other tools that use flow.record
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from flow.record.base import RecordDescriptor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_boolean() -> None:
|
|
9
|
+
TestRecord = RecordDescriptor(
|
|
10
|
+
"test/boolean",
|
|
11
|
+
[
|
|
12
|
+
("boolean", "booltrue"),
|
|
13
|
+
("boolean", "boolfalse"),
|
|
14
|
+
],
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
r = TestRecord(True, False)
|
|
18
|
+
assert bool(r.booltrue) is True
|
|
19
|
+
assert bool(r.boolfalse) is False
|
|
20
|
+
|
|
21
|
+
r = TestRecord(1, 0)
|
|
22
|
+
assert bool(r.booltrue) is True
|
|
23
|
+
assert bool(r.boolfalse) is False
|
|
24
|
+
|
|
25
|
+
assert str(r.booltrue) == "True"
|
|
26
|
+
assert str(r.boolfalse) == "False"
|
|
27
|
+
|
|
28
|
+
assert repr(r.booltrue) == "True"
|
|
29
|
+
assert repr(r.boolfalse) == "False"
|
|
30
|
+
|
|
31
|
+
with pytest.raises(ValueError, match="Value not a valid boolean value"):
|
|
32
|
+
TestRecord(2, -1)
|
|
33
|
+
|
|
34
|
+
with pytest.raises(ValueError, match="invalid literal for int"):
|
|
35
|
+
TestRecord("True", "False")
|
|
@@ -293,36 +293,6 @@ def test_dictlist() -> None:
|
|
|
293
293
|
assert r.hits[1]["b"] == 4
|
|
294
294
|
|
|
295
295
|
|
|
296
|
-
def test_boolean() -> None:
|
|
297
|
-
TestRecord = RecordDescriptor(
|
|
298
|
-
"test/boolean",
|
|
299
|
-
[
|
|
300
|
-
("boolean", "booltrue"),
|
|
301
|
-
("boolean", "boolfalse"),
|
|
302
|
-
],
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
r = TestRecord(True, False)
|
|
306
|
-
assert bool(r.booltrue) is True
|
|
307
|
-
assert bool(r.boolfalse) is False
|
|
308
|
-
|
|
309
|
-
r = TestRecord(1, 0)
|
|
310
|
-
assert bool(r.booltrue) is True
|
|
311
|
-
assert bool(r.boolfalse) is False
|
|
312
|
-
|
|
313
|
-
assert str(r.booltrue) == "True"
|
|
314
|
-
assert str(r.boolfalse) == "False"
|
|
315
|
-
|
|
316
|
-
assert repr(r.booltrue) == "True"
|
|
317
|
-
assert repr(r.boolfalse) == "False"
|
|
318
|
-
|
|
319
|
-
with pytest.raises(ValueError, match="Value not a valid boolean value"):
|
|
320
|
-
r = TestRecord(2, -1)
|
|
321
|
-
|
|
322
|
-
with pytest.raises(ValueError, match="invalid literal for int"):
|
|
323
|
-
r = TestRecord("True", "False")
|
|
324
|
-
|
|
325
|
-
|
|
326
296
|
def test_float() -> None:
|
|
327
297
|
TestRecord = RecordDescriptor(
|
|
328
298
|
"test/float",
|
|
@@ -93,6 +93,16 @@ def test_record_pack_bool_regression() -> None:
|
|
|
93
93
|
# pack the json string back to a record and make sure it is the same as before
|
|
94
94
|
assert packer.unpack(data) == record
|
|
95
95
|
|
|
96
|
+
# Make sure the same applies to an OrderedDict, which is how JsonRecordPacker is invoked for
|
|
97
|
+
# the Elastic adapter.
|
|
98
|
+
rdict = record._asdict()
|
|
99
|
+
data = packer.pack(rdict)
|
|
100
|
+
assert data.startswith('{"some_varint": 1, "some_uint": 0, "some_boolean": false, ')
|
|
101
|
+
|
|
102
|
+
# test that packer.pack has no side effects on rdict
|
|
103
|
+
assert rdict == record._asdict()
|
|
104
|
+
assert isinstance(rdict["some_boolean"], fieldtypes.boolean)
|
|
105
|
+
|
|
96
106
|
|
|
97
107
|
def test_record_pack_surrogateescape() -> None:
|
|
98
108
|
TestRecord = RecordDescriptor(
|
|
@@ -499,3 +499,11 @@ def test_file_like_writer_reader() -> None:
|
|
|
499
499
|
assert len(read_records) == 10
|
|
500
500
|
for idx, record in enumerate(read_records):
|
|
501
501
|
assert record == test_records[idx]
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def test_empty_stdin(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
505
|
+
# Mock stdin to be empty
|
|
506
|
+
monkeypatch.setattr(sys, "stdin", BytesIO(b""))
|
|
507
|
+
|
|
508
|
+
with pytest.raises(EOFError, match="Empty input stream"):
|
|
509
|
+
RecordAdapter()
|
|
@@ -797,3 +797,76 @@ def test_rdump_catch_sigpipe(tmp_path: Path) -> None:
|
|
|
797
797
|
assert "test/record count=0" in stdout
|
|
798
798
|
assert "test/record count=1" in stdout
|
|
799
799
|
assert len(stdout.splitlines()) == 2
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def test_rdump_empty_records_pipe(tmp_path: Path) -> None:
|
|
803
|
+
"""Test that rdump handles empty records as input gracefully."""
|
|
804
|
+
|
|
805
|
+
# create an empty records file
|
|
806
|
+
path = tmp_path / "empty.records"
|
|
807
|
+
with RecordWriter(path):
|
|
808
|
+
pass
|
|
809
|
+
|
|
810
|
+
# although the records file is empty, it should exist and have a RECORDSTREAM header
|
|
811
|
+
assert path.exists()
|
|
812
|
+
assert b"RECORDSTREAM" in path.read_bytes()
|
|
813
|
+
|
|
814
|
+
# rdump empty.records | rdump -l
|
|
815
|
+
p1 = subprocess.Popen(["rdump", str(path)], stdout=subprocess.PIPE)
|
|
816
|
+
p2 = subprocess.Popen(
|
|
817
|
+
["rdump", "-l"],
|
|
818
|
+
stdin=p1.stdout,
|
|
819
|
+
stdout=subprocess.PIPE,
|
|
820
|
+
stderr=subprocess.PIPE,
|
|
821
|
+
)
|
|
822
|
+
stdout, stderr = p2.communicate()
|
|
823
|
+
assert p2.returncode == 0
|
|
824
|
+
assert b"RecordReader('-'): Empty input stream" in stderr
|
|
825
|
+
assert b"Processed 0 records (matched=0, unmatched=0)" in stdout
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
@pytest.mark.parametrize(
|
|
829
|
+
"stdin_bytes",
|
|
830
|
+
[
|
|
831
|
+
b"",
|
|
832
|
+
None,
|
|
833
|
+
],
|
|
834
|
+
)
|
|
835
|
+
def test_rdump_empty_stdin_pipe(stdin_bytes: bytes | None) -> None:
|
|
836
|
+
"""Test that rdump handles empty stdin as input gracefully."""
|
|
837
|
+
|
|
838
|
+
# rdump -l (with empty stdin)
|
|
839
|
+
pipe = subprocess.Popen(
|
|
840
|
+
["rdump", "-l"],
|
|
841
|
+
stdin=subprocess.PIPE,
|
|
842
|
+
stdout=subprocess.PIPE,
|
|
843
|
+
stderr=subprocess.PIPE,
|
|
844
|
+
)
|
|
845
|
+
stdout, stderr = pipe.communicate(input=None)
|
|
846
|
+
assert pipe.returncode == 0
|
|
847
|
+
assert b"RecordReader('-'): Empty input stream" in stderr
|
|
848
|
+
assert b"Processed 0 records (matched=0, unmatched=0)" in stdout
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
@pytest.mark.parametrize(
|
|
852
|
+
"stdin_bytes",
|
|
853
|
+
[
|
|
854
|
+
b"\n",
|
|
855
|
+
b"this is not a valid record stream",
|
|
856
|
+
b"RANDOMDATA",
|
|
857
|
+
],
|
|
858
|
+
)
|
|
859
|
+
def test_rdump_invalid_stdin_pipe(stdin_bytes: bytes) -> None:
|
|
860
|
+
"""Test that rdump handles invalid stdin as an error"""
|
|
861
|
+
|
|
862
|
+
# rdump -l (with invalid stdin)
|
|
863
|
+
pipe = subprocess.Popen(
|
|
864
|
+
["rdump", "-l"],
|
|
865
|
+
stdin=subprocess.PIPE,
|
|
866
|
+
stdout=subprocess.PIPE,
|
|
867
|
+
stderr=subprocess.PIPE,
|
|
868
|
+
)
|
|
869
|
+
stdout, stderr = pipe.communicate(input=stdin_bytes)
|
|
870
|
+
assert pipe.returncode == 1, "rdump should exit with error code 1 on invalid input"
|
|
871
|
+
assert b"rdump encountered a fatal error: Could not find adapter for file-like object" in stderr
|
|
872
|
+
assert b"Processed 0 records (matched=0, unmatched=0)" in stdout
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|