nooa-cli 0.0.7__py3-none-any.whl

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.
@@ -0,0 +1,394 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Tests for pysh library — method-chaining, async-native."""
4
+
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ from nooa_cli.tools.pyp.errors import PipeError, Result, make_pipe_error
10
+ from nooa_cli.tools.pyp.sources import cat, empty, find, glob, items, lines, run, seq
11
+ from nooa_cli.tools.pyp.stream import Stream
12
+
13
+ # ─── Stream basics ───────────────────────────────────────────────────
14
+
15
+
16
+ @pytest.mark.asyncio
17
+ async def test_stream_from_iterable():
18
+ s = Stream(iter(["a", "b", "c"]))
19
+ assert await s.collect() == ["a", "b", "c"]
20
+
21
+
22
+ @pytest.mark.asyncio
23
+ async def test_stream_chaining():
24
+ s = items(["hello", "world", "help"]).grep("hel")
25
+ assert await s.collect() == ["hello", "help"]
26
+
27
+
28
+ @pytest.mark.asyncio
29
+ async def test_stream_pipe_with_callable():
30
+ async def upper(ait):
31
+ async for x in ait:
32
+ yield x.upper()
33
+
34
+ s = items(["a", "b", "c"]) | upper
35
+ assert await s.collect() == ["A", "B", "C"]
36
+
37
+
38
+ @pytest.mark.asyncio
39
+ async def test_stream_repr():
40
+ s = items(["x"]).grep("x").head(1)
41
+ r = repr(s)
42
+ assert "grep" in r
43
+ assert "head" in r
44
+
45
+
46
+ # ─── Error types ─────────────────────────────────────────────────────
47
+
48
+
49
+ def test_result_ok():
50
+ r = Result(lines=["a", "b"], returncode=0)
51
+ assert r.ok
52
+ assert len(r) == 2
53
+ assert r.text == "a\nb"
54
+ assert bool(r) is True
55
+
56
+
57
+ def test_result_failure():
58
+ r = Result(lines=[], returncode=1, stderr="oops")
59
+ assert not r.ok
60
+ assert bool(r) is False
61
+
62
+
63
+ def test_pipe_error_fields():
64
+ e = make_pipe_error("failed", cmd="ls", returncode=2, stderr="no such file")
65
+ assert e.cmd == "ls"
66
+ assert e.returncode == 2
67
+ assert "no such file" in e.stderr
68
+
69
+
70
+ # ─── Sources ─────────────────────────────────────────────────────────
71
+
72
+
73
+ @pytest.mark.asyncio
74
+ async def test_cat(tmp_path):
75
+ f = tmp_path / "test.txt"
76
+ f.write_text("line1\nline2\nline3\n")
77
+ result = await cat(str(f)).collect()
78
+ assert result == ["line1", "line2", "line3"]
79
+
80
+
81
+ @pytest.mark.asyncio
82
+ async def test_cat_multiple(tmp_path):
83
+ f1 = tmp_path / "a.txt"
84
+ f2 = tmp_path / "b.txt"
85
+ f1.write_text("a\n")
86
+ f2.write_text("b\n")
87
+ result = await cat(str(f1), str(f2)).collect()
88
+ assert result == ["a", "b"]
89
+
90
+
91
+ @pytest.mark.asyncio
92
+ async def test_run_success():
93
+ result = await run("echo hello").collect()
94
+ assert result == ["hello"]
95
+
96
+
97
+ @pytest.mark.asyncio
98
+ async def test_run_failure():
99
+ with pytest.raises(PipeError):
100
+ await run("false").collect()
101
+
102
+
103
+ @pytest.mark.asyncio
104
+ async def test_run_check_false():
105
+ r = await run("false", check=False).result()
106
+ assert not r.ok
107
+
108
+
109
+ @pytest.mark.asyncio
110
+ async def test_find_files(tmp_path):
111
+ (tmp_path / "a.py").write_text("")
112
+ (tmp_path / "b.txt").write_text("")
113
+ sub = tmp_path / "sub"
114
+ sub.mkdir()
115
+ (sub / "c.py").write_text("")
116
+ result = await find(str(tmp_path), name="*.py").collect()
117
+ assert len(result) == 2
118
+ assert all(".py" in r for r in result)
119
+
120
+
121
+ @pytest.mark.asyncio
122
+ async def test_glob_files(tmp_path):
123
+ (tmp_path / "a.py").write_text("")
124
+ (tmp_path / "b.py").write_text("")
125
+ (tmp_path / "c.txt").write_text("")
126
+ result = await glob("*.py", root=str(tmp_path)).collect()
127
+ assert len(result) == 2
128
+
129
+
130
+ @pytest.mark.asyncio
131
+ async def test_lines_source():
132
+ text = "foo\nbar\nbaz"
133
+ assert await lines(text).collect() == ["foo", "bar", "baz"]
134
+
135
+
136
+ @pytest.mark.asyncio
137
+ async def test_items_source():
138
+ assert await items([1, 2, 3]).collect() == [1, 2, 3]
139
+
140
+
141
+ @pytest.mark.asyncio
142
+ async def test_empty_source():
143
+ assert await empty().collect() == []
144
+
145
+
146
+ @pytest.mark.asyncio
147
+ async def test_seq():
148
+ assert await seq(5).collect() == ["1", "2", "3", "4", "5"]
149
+ assert await seq(2, 10, step=3).collect() == ["2", "5", "8"]
150
+
151
+
152
+ # ─── Transforms (method chaining) ───────────────────────────────────
153
+
154
+
155
+ @pytest.mark.asyncio
156
+ async def test_grep_basic():
157
+ result = await items(["apple", "banana", "apricot"]).grep("ap").collect()
158
+ assert result == ["apple", "apricot"]
159
+
160
+
161
+ @pytest.mark.asyncio
162
+ async def test_grep_invert():
163
+ result = await items(["apple", "banana", "apricot"]).grep("ap", invert=True).collect()
164
+ assert result == ["banana"]
165
+
166
+
167
+ @pytest.mark.asyncio
168
+ async def test_grep_ignore_case():
169
+ result = await items(["Apple", "BANANA"]).grep("apple", ignore_case=True).collect()
170
+ assert result == ["Apple"]
171
+
172
+
173
+ @pytest.mark.asyncio
174
+ async def test_grep_fixed():
175
+ result = await items(["a.b", "axb", "a*b"]).grep("a.b", fixed=True).collect()
176
+ assert result == ["a.b"]
177
+
178
+
179
+ @pytest.mark.asyncio
180
+ async def test_head():
181
+ result = await items(range(100)).head(3).collect()
182
+ assert result == [0, 1, 2]
183
+
184
+
185
+ @pytest.mark.asyncio
186
+ async def test_tail():
187
+ result = await items(range(100)).tail(3).collect()
188
+ assert result == [97, 98, 99]
189
+
190
+
191
+ @pytest.mark.asyncio
192
+ async def test_sort_basic():
193
+ result = await items(["c", "a", "b"]).sort().collect()
194
+ assert result == ["a", "b", "c"]
195
+
196
+
197
+ @pytest.mark.asyncio
198
+ async def test_sort_reverse():
199
+ result = await items(["a", "b", "c"]).sort(reverse=True).collect()
200
+ assert result == ["c", "b", "a"]
201
+
202
+
203
+ @pytest.mark.asyncio
204
+ async def test_sort_numeric():
205
+ result = await items(["10", "2", "1", "20"]).sort(numeric=True).collect()
206
+ assert result == ["1", "2", "10", "20"]
207
+
208
+
209
+ @pytest.mark.asyncio
210
+ async def test_uniq_consecutive():
211
+ result = await items(["a", "a", "b", "b", "a"]).uniq().collect()
212
+ assert result == ["a", "b", "a"]
213
+
214
+
215
+ @pytest.mark.asyncio
216
+ async def test_uniq_all():
217
+ result = await items(["a", "b", "a", "c", "b"]).uniq(all_unique=True).collect()
218
+ assert result == ["a", "b", "c"]
219
+
220
+
221
+ @pytest.mark.asyncio
222
+ async def test_cut_basic():
223
+ data = ["Alice 30 NYC", "Bob 25 LA"]
224
+ result = await items(data).cut(fields=[0, 2]).collect()
225
+ assert result == ["Alice\tNYC", "Bob\tLA"]
226
+
227
+
228
+ @pytest.mark.asyncio
229
+ async def test_cut_with_sep():
230
+ data = ["a,b,c", "d,e,f"]
231
+ result = await items(data).cut(fields=[0, 2], sep=",", out_sep=",").collect()
232
+ assert result == ["a,c", "d,f"]
233
+
234
+
235
+ @pytest.mark.asyncio
236
+ async def test_sed_basic():
237
+ result = await items(["hello world", "hello python"]).sed("hello", "hi").collect()
238
+ assert result == ["hi world", "hi python"]
239
+
240
+
241
+ @pytest.mark.asyncio
242
+ async def test_sed_regex():
243
+ result = await items(["foo123bar"]).sed(r"(\d+)", "NUM").collect()
244
+ assert result == ["fooNUMbar"]
245
+
246
+
247
+ @pytest.mark.asyncio
248
+ async def test_map():
249
+ result = await items(["a", "b", "c"]).map(str.upper).collect()
250
+ assert result == ["A", "B", "C"]
251
+
252
+
253
+ @pytest.mark.asyncio
254
+ async def test_filter():
255
+ result = (
256
+ await items(["short", "a very long line indeed"]).filter(lambda x: len(x) > 10).collect()
257
+ )
258
+ assert result == ["a very long line indeed"]
259
+
260
+
261
+ @pytest.mark.asyncio
262
+ async def test_wc():
263
+ result = await items(["a", "b", "c"]).wc().collect()
264
+ assert result == ["3"]
265
+
266
+
267
+ @pytest.mark.asyncio
268
+ async def test_wc_full():
269
+ result = await items(["hello world", "foo"]).wc(lines_only=False).collect()
270
+ parts = result[0].split()
271
+ assert parts[0] == "2"
272
+
273
+
274
+ @pytest.mark.asyncio
275
+ async def test_tee(tmp_path):
276
+ outfile = str(tmp_path / "tee_out.txt")
277
+ result = await items(["a", "b", "c"]).tee(outfile).collect()
278
+ assert result == ["a", "b", "c"]
279
+ content = Path(outfile).read_text()
280
+ assert "a\n" in content
281
+ assert "c\n" in content
282
+
283
+
284
+ @pytest.mark.asyncio
285
+ async def test_skip():
286
+ result = await items(["header", "row1", "row2"]).skip(1).collect()
287
+ assert result == ["row1", "row2"]
288
+
289
+
290
+ @pytest.mark.asyncio
291
+ async def test_take_while():
292
+ result = await items([1, 2, 3, 10, 4]).take_while(lambda x: x < 5).collect()
293
+ assert result == [1, 2, 3]
294
+
295
+
296
+ @pytest.mark.asyncio
297
+ async def test_drop_while():
298
+ result = await items([1, 2, 3, 10, 4]).drop_while(lambda x: x < 5).collect()
299
+ assert result == [10, 4]
300
+
301
+
302
+ @pytest.mark.asyncio
303
+ async def test_flatten():
304
+ result = await items(["a,b,c", "d,e"]).flatten(sep=",").collect()
305
+ assert result == ["a", "b", "c", "d", "e"]
306
+
307
+
308
+ @pytest.mark.asyncio
309
+ async def test_strip():
310
+ result = await items([" hello ", " world "]).strip().collect()
311
+ assert result == ["hello", "world"]
312
+
313
+
314
+ # ─── Composition ─────────────────────────────────────────────────────
315
+
316
+
317
+ @pytest.mark.asyncio
318
+ async def test_chained_pipeline():
319
+ result = (
320
+ await items(["banana", "apple", "avocado", "cherry"]).grep("a").sort().head(2).collect()
321
+ )
322
+ assert result == ["apple", "avocado"]
323
+
324
+
325
+ @pytest.mark.asyncio
326
+ async def test_full_pipeline(tmp_path):
327
+ f = tmp_path / "data.txt"
328
+ f.write_text("ERROR: disk full\nINFO: ok\nERROR: timeout\nDEBUG: trace\n")
329
+ result = await cat(str(f)).grep("ERROR").sed("ERROR: ", "").sort().collect()
330
+ assert result == ["disk full", "timeout"]
331
+
332
+
333
+ @pytest.mark.asyncio
334
+ async def test_pipeline_with_run():
335
+ result = await run("echo -e 'foo\\nbar\\nbaz'").grep("ba").sort().collect()
336
+ assert "bar" in result
337
+ assert "baz" in result
338
+
339
+
340
+ # ─── Sinks ───────────────────────────────────────────────────────────
341
+
342
+
343
+ @pytest.mark.asyncio
344
+ async def test_first():
345
+ assert await items(["a", "b", "c"]).first() == "a"
346
+ assert await empty().first() is None
347
+
348
+
349
+ @pytest.mark.asyncio
350
+ async def test_last():
351
+ assert await items(["a", "b", "c"]).last() == "c"
352
+ assert await empty().last() is None
353
+
354
+
355
+ @pytest.mark.asyncio
356
+ async def test_count():
357
+ assert await items(range(42)).count() == 42
358
+ assert await empty().count() == 0
359
+
360
+
361
+ @pytest.mark.asyncio
362
+ async def test_write(tmp_path):
363
+ out = tmp_path / "out.txt"
364
+ n = await items(["x", "y", "z"]).write(str(out))
365
+ assert n == 3
366
+ assert out.read_text() == "x\ny\nz\n"
367
+
368
+
369
+ @pytest.mark.asyncio
370
+ async def test_to_set():
371
+ assert await items(["a", "b", "a"]).to_set() == {"a", "b"}
372
+
373
+
374
+ @pytest.mark.asyncio
375
+ async def test_to_dict():
376
+ assert await items(["k1=v1", "k2=v2"]).to_dict() == {"k1": "v1", "k2": "v2"}
377
+
378
+
379
+ @pytest.mark.asyncio
380
+ async def test_json():
381
+ data = await items(['{"a": 1, "b": 2}']).json()
382
+ assert data == {"a": 1, "b": 2}
383
+
384
+
385
+ @pytest.mark.asyncio
386
+ async def test_text():
387
+ assert await items(["hello", "world"]).text() == "hello\nworld"
388
+
389
+
390
+ @pytest.mark.asyncio
391
+ async def test_table():
392
+ table = await items(["Alice 30 NYC", "Bob 25 LA"]).table()
393
+ assert "Alice" in table
394
+ assert "Bob" in table