scriptcast 0.1.0__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.
- scriptcast/__init__.py +0 -0
- scriptcast/__main__.py +371 -0
- scriptcast/assets/__init__.py +0 -0
- scriptcast/assets/fonts/DMSans-Regular.ttf +0 -0
- scriptcast/assets/fonts/Pacifico.ttf +0 -0
- scriptcast/assets/themes/aurora.sh +20 -0
- scriptcast/assets/themes/dark.sh +19 -0
- scriptcast/assets/themes/light.sh +19 -0
- scriptcast/config.py +199 -0
- scriptcast/directives.py +444 -0
- scriptcast/export.py +595 -0
- scriptcast/generator.py +265 -0
- scriptcast/recorder.py +212 -0
- scriptcast/shell/__init__.py +20 -0
- scriptcast/shell/adapter.py +13 -0
- scriptcast/shell/bash.py +11 -0
- scriptcast/shell/zsh.py +11 -0
- scriptcast-0.1.0.dist-info/METADATA +21 -0
- scriptcast-0.1.0.dist-info/RECORD +33 -0
- scriptcast-0.1.0.dist-info/WHEEL +5 -0
- scriptcast-0.1.0.dist-info/entry_points.txt +2 -0
- scriptcast-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_cli.py +304 -0
- tests/test_config.py +400 -0
- tests/test_directives.py +606 -0
- tests/test_export.py +986 -0
- tests/test_generator.py +434 -0
- tests/test_integration.py +97 -0
- tests/test_recorder.py +462 -0
- tests/test_registry.py +57 -0
- tests/test_shell.py +34 -0
- tests/test_theme.py +204 -0
tests/test_directives.py
ADDED
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import json
|
|
3
|
+
from collections import deque
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from scriptcast.config import ScriptcastConfig
|
|
8
|
+
from scriptcast.directives import (
|
|
9
|
+
CommentDirective,
|
|
10
|
+
Directive,
|
|
11
|
+
ExpectDirective,
|
|
12
|
+
FilterDirective,
|
|
13
|
+
HelpersDirective,
|
|
14
|
+
MockDirective,
|
|
15
|
+
RecordDirective,
|
|
16
|
+
ScEvent,
|
|
17
|
+
SetDirective,
|
|
18
|
+
SleepDirective,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_sc_event_fields():
|
|
23
|
+
e = ScEvent(ts=1.0, type="cmd", text="echo hi")
|
|
24
|
+
assert e.ts == 1.0
|
|
25
|
+
assert e.type == "cmd"
|
|
26
|
+
assert e.text == "echo hi"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_sc_event_is_frozen():
|
|
30
|
+
e = ScEvent(ts=1.0, type="cmd", text="echo hi")
|
|
31
|
+
with pytest.raises(dataclasses.FrozenInstanceError):
|
|
32
|
+
e.ts = 2.0 # type: ignore[misc]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_directive_pre_passthrough():
|
|
36
|
+
d = Directive()
|
|
37
|
+
lines = ["echo hello\n", "exit 0\n"]
|
|
38
|
+
assert d.pre(lines) == lines
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_directive_post_passthrough():
|
|
42
|
+
d = Directive()
|
|
43
|
+
events = [ScEvent(1.0, "cmd", "echo hi"), ScEvent(1.1, "out", "hi")]
|
|
44
|
+
assert d.post(events) == events
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_directive_gen_noop():
|
|
48
|
+
d = Directive()
|
|
49
|
+
q = deque()
|
|
50
|
+
cfg = ScriptcastConfig()
|
|
51
|
+
cursor, cast_lines = d.gen((1.0, "dir", "hi"), q, cfg, 0.0)
|
|
52
|
+
assert cursor == 0.0
|
|
53
|
+
assert cast_lines == []
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_mock_directive_pre_passthrough():
|
|
57
|
+
d = MockDirective()
|
|
58
|
+
lines = ["echo hello\n", "exit 0\n"]
|
|
59
|
+
assert d.pre(lines) == lines
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_mock_directive_pre_rewrites():
|
|
63
|
+
d = MockDirective()
|
|
64
|
+
lines = [
|
|
65
|
+
": SC mock deploy <<'EOF'\n",
|
|
66
|
+
"Deploying...\n",
|
|
67
|
+
"OK\n",
|
|
68
|
+
"EOF\n",
|
|
69
|
+
"echo after\n",
|
|
70
|
+
]
|
|
71
|
+
result = d.pre(lines)
|
|
72
|
+
joined = "".join(result)
|
|
73
|
+
assert "(: SC mark mock; set +x; echo + deploy; cat) <<'EOF'\n" in joined
|
|
74
|
+
assert "Deploying...\n" in joined
|
|
75
|
+
assert "OK\n" in joined
|
|
76
|
+
assert "echo after\n" in joined
|
|
77
|
+
assert ": SC mock" not in joined
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_mock_directive_pre_custom_prefix():
|
|
81
|
+
d = MockDirective(dp="MY")
|
|
82
|
+
lines = [": MY mock cmd <<'DONE'\n", "output\n", "DONE\n"]
|
|
83
|
+
result = d.pre(lines)
|
|
84
|
+
assert "(: MY mark mock; set +x; echo + cmd; cat) <<'DONE'\n" in "".join(result)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_mock_directive_pre_unquoted_heredoc():
|
|
88
|
+
d = MockDirective()
|
|
89
|
+
lines = [": SC mock deploy <<EOF\n", "${GREEN}OK${RESET}\n", "EOF\n"]
|
|
90
|
+
result = d.pre(lines)
|
|
91
|
+
joined = "".join(result)
|
|
92
|
+
assert "(: SC mark mock; set +x; echo + deploy; cat) <<EOF\n" in joined
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_mock_directive_post_passthrough():
|
|
96
|
+
d = MockDirective()
|
|
97
|
+
events = [ScEvent(1.0, "cmd", "echo hi")]
|
|
98
|
+
assert d.post(events) == events
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_mock_directive_post_drops_mark_mock_and_set_x():
|
|
102
|
+
d = MockDirective()
|
|
103
|
+
events = [
|
|
104
|
+
ScEvent(1.0, "dir", "mark mock"),
|
|
105
|
+
ScEvent(1.001, "cmd", "set +x"),
|
|
106
|
+
ScEvent(1.002, "cmd", "deploy arg1"),
|
|
107
|
+
]
|
|
108
|
+
result = d.post(events)
|
|
109
|
+
assert result == [ScEvent(1.002, "cmd", "deploy arg1")]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_mock_directive_post_drops_mark_mock_without_set_x():
|
|
113
|
+
d = MockDirective()
|
|
114
|
+
events = [
|
|
115
|
+
ScEvent(1.0, "dir", "mark mock"),
|
|
116
|
+
ScEvent(1.001, "cmd", "deploy arg1"),
|
|
117
|
+
]
|
|
118
|
+
result = d.post(events)
|
|
119
|
+
assert result == [ScEvent(1.001, "cmd", "deploy arg1")]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_expect_directive_pre_passthrough():
|
|
123
|
+
d = ExpectDirective()
|
|
124
|
+
lines = ["echo hello\n", "exit 0\n"]
|
|
125
|
+
assert d.pre(lines) == lines
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_expect_directive_pre_rewrites():
|
|
129
|
+
d = ExpectDirective()
|
|
130
|
+
lines = [
|
|
131
|
+
": SC expect mysql <<'EOF'\n",
|
|
132
|
+
'expect "Password:"\n',
|
|
133
|
+
'send "secret\\r"\n',
|
|
134
|
+
"EOF\n",
|
|
135
|
+
"echo after\n",
|
|
136
|
+
]
|
|
137
|
+
result = d.pre(lines)
|
|
138
|
+
joined = "".join(result)
|
|
139
|
+
assert ": SC mark expect mysql" in joined
|
|
140
|
+
assert "expect <<'EOF'" in joined
|
|
141
|
+
assert "spawn mysql" in joined
|
|
142
|
+
assert 'send_user ": SC mark input secret\\n"' in joined
|
|
143
|
+
assert 'send "secret\\r"' in joined
|
|
144
|
+
assert "echo after\n" in joined
|
|
145
|
+
assert ": SC expect" not in joined
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_expect_directive_pre_multiple_sends():
|
|
149
|
+
d = ExpectDirective()
|
|
150
|
+
lines = [
|
|
151
|
+
": SC expect cmd <<'EOF'\n",
|
|
152
|
+
'expect "p1"\n',
|
|
153
|
+
'send "a\\r"\n',
|
|
154
|
+
'expect "p2"\n',
|
|
155
|
+
'send "b\\r"\n',
|
|
156
|
+
"EOF\n",
|
|
157
|
+
]
|
|
158
|
+
result = d.pre(lines)
|
|
159
|
+
joined = "".join(result)
|
|
160
|
+
assert joined.count('send_user ": SC mark input') == 2
|
|
161
|
+
assert 'send_user ": SC mark input a\\n"' in joined
|
|
162
|
+
assert 'send_user ": SC mark input b\\n"' in joined
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_expect_directive_pre_no_send():
|
|
166
|
+
d = ExpectDirective()
|
|
167
|
+
lines = [": SC expect app <<'EOF'\n", 'expect "done"\n', "EOF\n"]
|
|
168
|
+
result = d.pre(lines)
|
|
169
|
+
joined = "".join(result)
|
|
170
|
+
assert "spawn app" in joined
|
|
171
|
+
assert "mark input" not in joined
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_expect_directive_post_passthrough():
|
|
175
|
+
d = ExpectDirective()
|
|
176
|
+
events = [ScEvent(1.0, "cmd", "echo hi"), ScEvent(1.1, "out", "hi")]
|
|
177
|
+
assert d.post(events) == events
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_expect_directive_post_emits_cmd_from_mark_expect():
|
|
181
|
+
d = ExpectDirective()
|
|
182
|
+
events = [
|
|
183
|
+
ScEvent(1.0, "dir", "mark expect mysql"),
|
|
184
|
+
ScEvent(1.001, "cmd", "expect"),
|
|
185
|
+
ScEvent(1.002, "out", "spawn mysql"),
|
|
186
|
+
ScEvent(1.003, "out", "Password:"),
|
|
187
|
+
ScEvent(1.004, "cmd", "echo hi"),
|
|
188
|
+
]
|
|
189
|
+
result = d.post(events)
|
|
190
|
+
assert result[0] == ScEvent(1.0, "cmd", "mysql")
|
|
191
|
+
types = [e.type for e in result]
|
|
192
|
+
assert "out" in types
|
|
193
|
+
# Terminator stays in output
|
|
194
|
+
assert ScEvent(1.004, "cmd", "echo hi") in result
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_expect_directive_post_skips_spawn_line():
|
|
198
|
+
d = ExpectDirective()
|
|
199
|
+
events = [
|
|
200
|
+
ScEvent(1.0, "dir", "mark expect myapp"),
|
|
201
|
+
ScEvent(1.001, "cmd", "expect"),
|
|
202
|
+
ScEvent(1.002, "out", "spawn myapp"),
|
|
203
|
+
ScEvent(1.003, "cmd", "echo done"),
|
|
204
|
+
]
|
|
205
|
+
result = d.post(events)
|
|
206
|
+
assert not any("spawn" in e.text for e in result)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def test_expect_directive_post_emits_expect_input_for_mark_input():
|
|
210
|
+
d = ExpectDirective()
|
|
211
|
+
events = [
|
|
212
|
+
ScEvent(1.0, "dir", "mark expect app"),
|
|
213
|
+
ScEvent(1.001, "cmd", "expect"),
|
|
214
|
+
ScEvent(1.002, "out", "spawn app"),
|
|
215
|
+
ScEvent(1.003, "out", "Password: : SC mark input secret"),
|
|
216
|
+
ScEvent(1.004, "out", "Welcome"),
|
|
217
|
+
ScEvent(1.005, "cmd", "echo done"),
|
|
218
|
+
]
|
|
219
|
+
result = d.post(events)
|
|
220
|
+
dir_events = [e for e in result if e.type == "dir"]
|
|
221
|
+
assert any(e.text.startswith("expect-input") for e in dir_events)
|
|
222
|
+
out_events = [e for e in result if e.type == "out"]
|
|
223
|
+
assert any("Password:" in e.text for e in out_events)
|
|
224
|
+
assert any("Welcome" in e.text for e in out_events)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_expect_directive_post_strips_pty_echo():
|
|
228
|
+
d = ExpectDirective()
|
|
229
|
+
events = [
|
|
230
|
+
ScEvent(1.0, "dir", "mark expect app"),
|
|
231
|
+
ScEvent(1.001, "cmd", "expect"),
|
|
232
|
+
ScEvent(1.002, "out", "spawn app"),
|
|
233
|
+
ScEvent(1.003, "out", "prompt> : SC mark input show;"),
|
|
234
|
+
ScEvent(1.004, "out", "show;"), # pty echo — must be stripped
|
|
235
|
+
ScEvent(1.005, "out", "(0 rows)"),
|
|
236
|
+
ScEvent(1.006, "cmd", "echo done"),
|
|
237
|
+
]
|
|
238
|
+
result = d.post(events)
|
|
239
|
+
out_texts = [e.text for e in result if e.type == "out"]
|
|
240
|
+
assert "show;" not in out_texts
|
|
241
|
+
assert "(0 rows)" in out_texts
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def test_expect_directive_post_preserves_cr_lf_from_pty_echo():
|
|
245
|
+
# When the PTY echo includes a trailing \r\n (Enter echo), it must be kept
|
|
246
|
+
# as an out event so the cast shows a newline after the typed input.
|
|
247
|
+
# Bug scenario: "Email: dev@example.comPassword:" (no newline between them).
|
|
248
|
+
d = ExpectDirective()
|
|
249
|
+
events = [
|
|
250
|
+
ScEvent(1.0, "dir", "mark expect app"),
|
|
251
|
+
ScEvent(1.001, "cmd", "expect"),
|
|
252
|
+
ScEvent(1.002, "out", "spawn app"),
|
|
253
|
+
ScEvent(1.003, "out", "Email: : SC mark input dev@example.com"),
|
|
254
|
+
ScEvent(1.004, "out", "dev@example.com\r\n"), # PTY echo with Enter
|
|
255
|
+
ScEvent(1.005, "out", "Password: "),
|
|
256
|
+
ScEvent(1.006, "cmd", "echo done"),
|
|
257
|
+
]
|
|
258
|
+
result = d.post(events)
|
|
259
|
+
out_texts = [e.text for e in result if e.type == "out"]
|
|
260
|
+
# Characters stripped; \r\n retained so the cast shows a newline after typing
|
|
261
|
+
assert "dev@example.com\r\n" not in out_texts # full echo gone
|
|
262
|
+
assert "\r\n" in out_texts # but the Enter echo preserved
|
|
263
|
+
assert "Password: " in out_texts
|
|
264
|
+
# \r\n must appear before "Password: " in the output
|
|
265
|
+
rn_idx = next(i for i, t in enumerate(out_texts) if t == "\r\n")
|
|
266
|
+
pw_idx = next(i for i, t in enumerate(out_texts) if t == "Password: ")
|
|
267
|
+
assert rn_idx < pw_idx
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def test_expect_directive_post_handles_raw_expect_call():
|
|
271
|
+
"""Bare 'cmd: expect' event (no SC expect directive) is handled correctly."""
|
|
272
|
+
d = ExpectDirective()
|
|
273
|
+
events = [
|
|
274
|
+
ScEvent(1.0, "cmd", "expect"),
|
|
275
|
+
ScEvent(1.001, "out", "spawn ./fake-db"),
|
|
276
|
+
ScEvent(1.002, "out", "Password:"),
|
|
277
|
+
ScEvent(1.003, "cmd", "echo after"),
|
|
278
|
+
]
|
|
279
|
+
result = d.post(events)
|
|
280
|
+
assert result[0].type == "cmd"
|
|
281
|
+
assert result[0].text == "./fake-db"
|
|
282
|
+
assert any(e.type == "out" and "Password:" in e.text for e in result)
|
|
283
|
+
assert ScEvent(1.003, "cmd", "echo after") in result
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def test_expect_directive_post_terminates_on_dir_event():
|
|
287
|
+
d = ExpectDirective()
|
|
288
|
+
events = [
|
|
289
|
+
ScEvent(1.0, "dir", "mark expect app"),
|
|
290
|
+
ScEvent(1.001, "cmd", "expect"),
|
|
291
|
+
ScEvent(1.002, "out", "spawn app"),
|
|
292
|
+
ScEvent(1.003, "out", "some output"),
|
|
293
|
+
ScEvent(1.004, "dir", "filter sed 's/x/y/g'"),
|
|
294
|
+
]
|
|
295
|
+
result = d.post(events)
|
|
296
|
+
assert ScEvent(1.004, "dir", "filter sed 's/x/y/g'") in result
|
|
297
|
+
assert any(e.type == "out" and "some output" in e.text for e in result)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_filter_directive_post_passthrough_when_no_filter_set():
|
|
301
|
+
d = FilterDirective()
|
|
302
|
+
events = [ScEvent(1.0, "cmd", "echo hi"), ScEvent(1.1, "out", "hi")]
|
|
303
|
+
assert d.post(events) == events
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def test_filter_directive_post_registers_filter():
|
|
307
|
+
d = FilterDirective()
|
|
308
|
+
events = [ScEvent(1.0, "dir", "filter sed 's/foo/bar/g'")]
|
|
309
|
+
result = d.post(events)
|
|
310
|
+
assert result == []
|
|
311
|
+
assert d.apply("foo baz") == "bar baz"
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def test_filter_directive_post_registers_filter_add():
|
|
315
|
+
d = FilterDirective()
|
|
316
|
+
d.post([ScEvent(1.0, "dir", "filter sed 's/foo/bar/g'")])
|
|
317
|
+
d.post([ScEvent(1.1, "dir", "filter-add sed 's/baz/qux/g'")])
|
|
318
|
+
assert d.apply("foo baz") == "bar qux"
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def test_filter_directive_post_replaces_filter():
|
|
322
|
+
d = FilterDirective()
|
|
323
|
+
d.post([ScEvent(1.0, "dir", "filter sed 's/a/b/g'")])
|
|
324
|
+
d.post([ScEvent(1.1, "dir", "filter sed 's/x/y/g'")])
|
|
325
|
+
assert d.apply("aaa xxx") == "aaa yyy"
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def test_filter_directive_post_transforms_out_events():
|
|
329
|
+
d = FilterDirective()
|
|
330
|
+
events = [
|
|
331
|
+
ScEvent(1.0, "dir", "filter sed 's/foo/bar/g'"),
|
|
332
|
+
ScEvent(1.1, "out", "foo baz"),
|
|
333
|
+
]
|
|
334
|
+
result = d.post(events)
|
|
335
|
+
assert ScEvent(1.1, "out", "bar baz") in result
|
|
336
|
+
assert not any(e.text == "foo baz" for e in result)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def test_filter_directive_post_transforms_cmd_events():
|
|
340
|
+
d = FilterDirective()
|
|
341
|
+
events = [
|
|
342
|
+
ScEvent(1.0, "dir", "filter sed 's/foo/bar/g'"),
|
|
343
|
+
ScEvent(1.1, "cmd", "foo command"),
|
|
344
|
+
]
|
|
345
|
+
result = d.post(events)
|
|
346
|
+
assert ScEvent(1.1, "cmd", "bar command") in result
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def test_filter_directive_post_does_not_transform_dir_events():
|
|
350
|
+
d = FilterDirective()
|
|
351
|
+
events = [
|
|
352
|
+
ScEvent(1.0, "dir", "filter sed 's/foo/bar/g'"),
|
|
353
|
+
ScEvent(1.1, "dir", "scene intro"),
|
|
354
|
+
]
|
|
355
|
+
result = d.post(events)
|
|
356
|
+
assert ScEvent(1.1, "dir", "scene intro") in result
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def test_filter_directive_apply_identity_when_empty():
|
|
360
|
+
d = FilterDirective()
|
|
361
|
+
assert d.apply("unchanged") == "unchanged"
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def test_filter_directive_apply_real_command():
|
|
365
|
+
d = FilterDirective()
|
|
366
|
+
d.post([ScEvent(1.0, "dir", "filter tr 'a-z' 'A-Z'")])
|
|
367
|
+
assert d.apply("hello") == "HELLO"
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def test_filter_directive_apply_preserves_cr_in_content():
|
|
371
|
+
# text=True in subprocess would convert \r → \n via universal newlines;
|
|
372
|
+
# binary mode must be used so progress-bar \r overwrite chars survive the filter.
|
|
373
|
+
d = FilterDirective()
|
|
374
|
+
d.post([ScEvent(1.0, "dir", "filter sed 's/x/x/g'")]) # identity filter
|
|
375
|
+
assert d.apply("frame1\rframe2\rframe3\r\n") == "frame1\rframe2\rframe3\r\n"
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def test_filter_directive_failed_command_returns_empty():
|
|
379
|
+
d = FilterDirective()
|
|
380
|
+
d.post([ScEvent(1.0, "dir", "filter false")])
|
|
381
|
+
assert d.apply("anything") == ""
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def test_filter_directive_command_not_found_returns_empty():
|
|
385
|
+
d = FilterDirective()
|
|
386
|
+
d.post([ScEvent(1.0, "dir", "filter __nonexistent_command_xyz__")])
|
|
387
|
+
assert d.apply("anything") == ""
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def test_record_directive_post_passthrough():
|
|
391
|
+
d = RecordDirective()
|
|
392
|
+
events = [ScEvent(1.0, "cmd", "echo hi"), ScEvent(1.1, "out", "hi")]
|
|
393
|
+
assert d.post(events) == events
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def test_record_directive_drops_pause_resume_block():
|
|
397
|
+
d = RecordDirective()
|
|
398
|
+
events = [
|
|
399
|
+
ScEvent(1.0, "dir", "record pause"),
|
|
400
|
+
ScEvent(1.1, "cmd", "PS1=>"),
|
|
401
|
+
ScEvent(1.2, "cmd", "echo ignored"),
|
|
402
|
+
ScEvent(1.3, "dir", "record resume"),
|
|
403
|
+
ScEvent(1.4, "cmd", "echo after"),
|
|
404
|
+
]
|
|
405
|
+
result = d.post(events)
|
|
406
|
+
assert result == [ScEvent(1.4, "cmd", "echo after")]
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def test_record_directive_drops_to_end_if_no_resume():
|
|
410
|
+
d = RecordDirective()
|
|
411
|
+
events = [
|
|
412
|
+
ScEvent(1.0, "dir", "record pause"),
|
|
413
|
+
ScEvent(1.1, "cmd", "echo something"),
|
|
414
|
+
]
|
|
415
|
+
result = d.post(events)
|
|
416
|
+
assert result == []
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def test_record_directive_ignores_resume_without_pause():
|
|
420
|
+
d = RecordDirective()
|
|
421
|
+
events = [ScEvent(1.0, "dir", "record resume"), ScEvent(1.1, "cmd", "echo hi")]
|
|
422
|
+
result = d.post(events)
|
|
423
|
+
assert result == [ScEvent(1.0, "dir", "record resume"), ScEvent(1.1, "cmd", "echo hi")]
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def test_set_directive_gen_mutates_active_config():
|
|
427
|
+
d = SetDirective()
|
|
428
|
+
active = ScriptcastConfig()
|
|
429
|
+
active.type_speed = 40
|
|
430
|
+
cursor, events = d.gen((1.0, "dir", "set type_speed 10"), deque(), active, 0.0)
|
|
431
|
+
assert active.type_speed == 10
|
|
432
|
+
assert cursor == 0.0
|
|
433
|
+
assert events == []
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def test_set_directive_gen_quoted_value():
|
|
437
|
+
# bash traces `"$ "` as `'$ '`; shlex.split must handle shell quoting
|
|
438
|
+
d = SetDirective()
|
|
439
|
+
active = ScriptcastConfig()
|
|
440
|
+
cursor, events = d.gen((1.0, "dir", "set prompt '$ '"), deque(), active, 0.0)
|
|
441
|
+
assert active.prompt == "$ "
|
|
442
|
+
assert cursor == 0.0
|
|
443
|
+
assert events == []
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def test_sleep_directive_gen_advances_cursor():
|
|
447
|
+
d = SleepDirective()
|
|
448
|
+
active = ScriptcastConfig()
|
|
449
|
+
cursor, events = d.gen((1.0, "dir", "sleep 500"), deque(), active, 0.0)
|
|
450
|
+
assert abs(cursor - 0.5) < 1e-9
|
|
451
|
+
assert events == []
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def test_sleep_directive_gen_zero():
|
|
455
|
+
d = SleepDirective()
|
|
456
|
+
active = ScriptcastConfig()
|
|
457
|
+
cursor, events = d.gen((1.0, "dir", "sleep 0"), deque(), active, 1.5)
|
|
458
|
+
assert cursor == 1.5
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def test_comment_directive_post_passthrough():
|
|
462
|
+
d = CommentDirective()
|
|
463
|
+
events = [ScEvent(1.0, "cmd", "echo hi"), ScEvent(1.1, "out", "hi")]
|
|
464
|
+
assert d.post(events) == events
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def test_comment_directive_converts_comment_with_text():
|
|
468
|
+
d = CommentDirective()
|
|
469
|
+
events = [ScEvent(1.0, "dir", "'\\\' This is a comment")]
|
|
470
|
+
result = d.post(events)
|
|
471
|
+
assert result == [ScEvent(1.0, "cmd", "# This is a comment")]
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def test_comment_directive_converts_empty_comment():
|
|
475
|
+
d = CommentDirective()
|
|
476
|
+
events = [ScEvent(1.0, "dir", "'\\\'")]
|
|
477
|
+
result = d.post(events)
|
|
478
|
+
assert result == [ScEvent(1.0, "cmd", "#")]
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def test_comment_directive_does_not_touch_other_dir_events():
|
|
482
|
+
d = CommentDirective()
|
|
483
|
+
events = [ScEvent(1.0, "dir", "scene intro")]
|
|
484
|
+
assert d.post(events) == events
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def test_directive_has_priority():
|
|
488
|
+
d = Directive()
|
|
489
|
+
assert isinstance(d.priority, int)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def test_directive_has_handles_none_by_default():
|
|
493
|
+
d = Directive()
|
|
494
|
+
assert d.handles is None
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def test_set_directive_handles_set():
|
|
498
|
+
d = SetDirective()
|
|
499
|
+
assert d.handles == "set"
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def test_sleep_directive_handles_sleep():
|
|
503
|
+
d = SleepDirective()
|
|
504
|
+
assert d.handles == "sleep"
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def test_expect_directive_priority_lower_than_filter():
|
|
508
|
+
assert ExpectDirective().priority < FilterDirective().priority
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def test_expect_directive_handles_expect_input():
|
|
512
|
+
d = ExpectDirective()
|
|
513
|
+
assert d.handles == "expect-input"
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def test_expect_directive_gen_advances_cursor_by_input_wait():
|
|
517
|
+
from collections import deque
|
|
518
|
+
|
|
519
|
+
from scriptcast.config import ScriptcastConfig
|
|
520
|
+
d = ExpectDirective()
|
|
521
|
+
active = ScriptcastConfig()
|
|
522
|
+
active.input_wait = 300
|
|
523
|
+
active.type_speed = 0
|
|
524
|
+
cursor, lines = d.gen((1.0, "dir", "expect-input secret"), deque(), active, 0.0)
|
|
525
|
+
assert cursor == pytest.approx(0.3)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def test_expect_directive_gen_emits_chars():
|
|
529
|
+
from collections import deque
|
|
530
|
+
|
|
531
|
+
from scriptcast.config import ScriptcastConfig
|
|
532
|
+
d = ExpectDirective()
|
|
533
|
+
active = ScriptcastConfig()
|
|
534
|
+
active.input_wait = 0
|
|
535
|
+
active.type_speed = 0
|
|
536
|
+
_, lines = d.gen((1.0, "dir", "expect-input hi"), deque(), active, 0.0)
|
|
537
|
+
cast_texts = [json.loads(ln)[2] for ln in lines]
|
|
538
|
+
assert "h" in cast_texts
|
|
539
|
+
assert "i" in cast_texts
|
|
540
|
+
assert "\r\n" not in cast_texts
|
|
541
|
+
event_types = {json.loads(ln)[1] for ln in lines}
|
|
542
|
+
assert event_types == {"o"}
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def test_expect_directive_gen_empty_input():
|
|
546
|
+
from collections import deque
|
|
547
|
+
|
|
548
|
+
from scriptcast.config import ScriptcastConfig
|
|
549
|
+
d = ExpectDirective()
|
|
550
|
+
active = ScriptcastConfig()
|
|
551
|
+
active.input_wait = 0
|
|
552
|
+
active.type_speed = 0
|
|
553
|
+
cursor, lines = d.gen((1.0, "dir", "expect-input"), deque(), active, 0.0)
|
|
554
|
+
assert lines == []
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def test_helpers_directive_priority_is_lowest():
|
|
558
|
+
from scriptcast.directives import build_directives
|
|
559
|
+
all_directives = build_directives()
|
|
560
|
+
helpers = next(d for d in all_directives if isinstance(d, HelpersDirective))
|
|
561
|
+
others = [d for d in all_directives if not isinstance(d, HelpersDirective)]
|
|
562
|
+
assert all(helpers.priority < d.priority for d in others)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def test_helpers_directive_pre_passthrough():
|
|
566
|
+
d = HelpersDirective()
|
|
567
|
+
lines = ["echo hello\n", "exit 0\n"]
|
|
568
|
+
assert d.pre(lines) == lines
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def test_helpers_directive_pre_expands_helpers_line():
|
|
572
|
+
d = HelpersDirective()
|
|
573
|
+
lines = [": SC helpers\n", "echo hi\n"]
|
|
574
|
+
result = d.pre(lines)
|
|
575
|
+
joined = "".join(result)
|
|
576
|
+
assert ": SC record pause\n" in joined
|
|
577
|
+
assert "RED=$'\\033[31m'\n" in joined
|
|
578
|
+
assert "YELLOW=$'\\033[33m'\n" in joined
|
|
579
|
+
assert "GREEN=$'\\033[32m'\n" in joined
|
|
580
|
+
assert "CYAN=$'\\033[36m'\n" in joined
|
|
581
|
+
assert "BOLD=$'\\033[1m'\n" in joined
|
|
582
|
+
assert "RESET=$'\\033[0m'\n" in joined
|
|
583
|
+
assert ": SC record resume\n" in joined
|
|
584
|
+
assert "echo hi\n" in joined
|
|
585
|
+
assert ": SC helpers\n" not in joined
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def test_helpers_directive_pre_custom_prefix():
|
|
589
|
+
d = HelpersDirective(dp="MY")
|
|
590
|
+
lines = [": MY helpers\n"]
|
|
591
|
+
result = d.pre(lines)
|
|
592
|
+
joined = "".join(result)
|
|
593
|
+
assert ": MY record pause\n" in joined
|
|
594
|
+
assert ": MY record resume\n" in joined
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def test_helpers_directive_pre_ignores_partial_match():
|
|
598
|
+
d = HelpersDirective()
|
|
599
|
+
lines = [": SC helpers extra\n"]
|
|
600
|
+
assert d.pre(lines) == lines
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def test_helpers_directive_post_passthrough():
|
|
604
|
+
d = HelpersDirective()
|
|
605
|
+
events = [ScEvent(1.0, "cmd", "echo hi")]
|
|
606
|
+
assert d.post(events) == events
|