tui-td 0.2.2 → 0.2.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b74325984554c843a191d5affcf77472229000da6f14551c26ca2e6f1b0425cd
4
- data.tar.gz: 2ddeb1aaedfc23ca62e3e791bb254950ef4432a32f0031e0a144f7567e2a3785
3
+ metadata.gz: 739ccd98a6d9a5ee8f29eadfdaaaa86cde8ee67cebf7bd9c293a20e1785f4bdb
4
+ data.tar.gz: bbeb1b4fcc9def290a68ee73df31d63d4fb3cb5a7a9a55b8d8cf5b8a57394885
5
5
  SHA512:
6
- metadata.gz: b02944c10ed90a607ccfadd80fe80237a1bd5d50bba8e92320875c3cc71181f04057d2cad78648f5ddefee7a149b179c215c12b0ed61a19af7363b658090e36a
7
- data.tar.gz: fe0f2b747cc15fe39da0195c83371ef44c457194f1648aa5e4e03f525ac856a60f3561a482012009054158350d354ea7cf28a04ab75eb9d660c1712203364373
6
+ metadata.gz: 6bb830eee8c25605c0cc1ecf7e5692fe5db1144c5c7cc2a6144367139056c6a2aa8650390429647d51b263058c21e78b46ee01c085fd0662ef3879ca551184e3
7
+ data.tar.gz: cbefe0d9f7483787190763563d5a1919aa8c476c1b57cddd704b981974b652b3b1734c59c60031ffd12e59d940a4a07f1abe937ab35012249a88a7303ca29073
data/CHANGELOG.md CHANGED
@@ -1,8 +1,15 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.2.3
4
+
5
+ - `have_exit_status` RSpec matcher and `exitstatus` drive command — exit code testing on all three levels (JSON + RSpec + drive)
6
+ - `env` in start step — inject environment variables per test run
7
+ - Per-step `timeout` override
8
+ - `before_all` / `after_all` hooks for setup and teardown steps
9
+
3
10
  ## 0.2.2
4
11
 
5
- - `wait_for_exit` and `assert_exit` test steps — test process exit codes
12
+ - `wait_for_exit` and `assert_exit` JSON test steps — test process exit codes
6
13
 
7
14
  ## 0.2.1
8
15
 
data/README.md CHANGED
@@ -120,6 +120,7 @@ Interactive commands (drive mode):
120
120
  key <name> Send keystroke (enter, tab, escape, up, down, left, right,
121
121
  backspace, ctrl_c, ctrl_d)
122
122
  <text> Send text to the TUI
123
+ exitstatus Show process exit status (nil if running)
123
124
  exit Quit drive mode
124
125
 
125
126
  Global options:
data/lib/tui_td/cli.rb CHANGED
@@ -41,6 +41,7 @@ module TUITD
41
41
  opts.separator " key <name> Send keystroke (enter, tab, escape, up, down, left, right,"
42
42
  opts.separator " backspace, ctrl_c, ctrl_d)"
43
43
  opts.separator " <text> Send text to the TUI"
44
+ opts.separator " exitstatus Show process exit status (nil if running)"
44
45
  opts.separator " exit Quit drive mode"
45
46
  opts.separator ""
46
47
  opts.separator "Global options:"
@@ -188,6 +189,9 @@ module TUITD
188
189
  puts driver.state_json(pretty: true)
189
190
  elsif input == "raw"
190
191
  puts driver.raw_output[0..2000]
192
+ elsif input == "exitstatus"
193
+ status = driver.exitstatus
194
+ puts status ? "Exit status: #{status}" : "Process still running"
191
195
  elsif input.start_with?("key ")
192
196
  driver.send_keys(input.split(" ", 2).last.to_sym)
193
197
  else
@@ -327,12 +331,24 @@ module TUITD
327
331
  A test is a Hash or JSON string: {"name": "...", "steps": [...]}
328
332
 
329
333
  Top-level keys: name, steps, rows (default 40), cols (default 120),
330
- timeout (default 30), chdir
334
+ timeout (default 30), chdir, before_all, after_all
335
+
336
+ before_all / after_all are arrays of steps that run before and
337
+ after the main steps list. Useful for setup/teardown:
338
+
339
+ "before_all": [{"start": "my_tui"}, {"wait_for_text": "> "}],
340
+ "steps": [{"send": "hello\\n"}],
341
+ "after_all": [{"close": true}]
342
+
343
+ Each step can also set a per-step "timeout" (in seconds):
344
+
345
+ {"wait_for_text": "Slow", "timeout": 60}
331
346
 
332
347
  Each step is an object with a single action key:
333
348
 
334
349
  {"start": "<command>"}
335
- Start a TUI process in a PTY.
350
+ Start a TUI process in a PTY. Environment variables can be
351
+ passed via "env": {"FOO": "bar", "BAZ": "qux"}.
336
352
 
337
353
  {"send": "<text>"}
338
354
  Send text to the TUI. Use "\\n" for Enter.
@@ -428,6 +444,13 @@ module TUITD
428
444
  have_style.at(row, col).with(bold: true, italic: false, ...)
429
445
  Assert style attributes at [row, col] match the given hash.
430
446
  Usage: expect(state).to have_style.at(0, 0).with(bold: true)
447
+
448
+ Driver matchers (work on TUITD::Driver, not State)
449
+ --------------------------------------------------
450
+
451
+ have_exit_status(expected)
452
+ Assert the process exit status matches expected.
453
+ Usage: expect(driver).to have_exit_status(0)
431
454
  HELP
432
455
  exit 0
433
456
  end
data/lib/tui_td/driver.rb CHANGED
@@ -19,12 +19,13 @@ module TUITD
19
19
  class Driver
20
20
  attr_reader :command, :state
21
21
 
22
- def initialize(command, rows: 40, cols: 120, timeout: 30, chdir: nil)
22
+ def initialize(command, rows: 40, cols: 120, timeout: 30, chdir: nil, env: {})
23
23
  @command = command
24
24
  @rows = rows
25
25
  @cols = cols
26
26
  @timeout = timeout
27
27
  @chdir = chdir
28
+ @env = env
28
29
  @state = nil
29
30
  @stdin = nil
30
31
  @stdout = nil
@@ -37,7 +38,7 @@ module TUITD
37
38
 
38
39
  # Start the TUI application in a PTY
39
40
  def start
40
- env = { "TERM" => "xterm-256color", "COLUMNS" => @cols.to_s, "LINES" => @rows.to_s }
41
+ env = { "TERM" => "xterm-256color", "COLUMNS" => @cols.to_s, "LINES" => @rows.to_s }.merge(@env.transform_keys(&:to_s))
41
42
  spawn_opts = {}
42
43
  spawn_opts[:chdir] = @chdir if @chdir
43
44
 
@@ -68,5 +68,21 @@ module TUITD
68
68
  "expected style at [#{@row},#{@col}] to be #{@expected.inspect}, but was #{@actual.inspect}"
69
69
  end
70
70
  end
71
+
72
+ # Works on a Driver instance, not State
73
+ RSpec::Matchers.define :have_exit_status do |expected|
74
+ match do |driver|
75
+ @actual = driver.exitstatus
76
+ @actual == expected
77
+ end
78
+
79
+ description { "have exit status #{expected}" }
80
+ failure_message do |driver|
81
+ "expected exit status #{expected}, but was #{@actual}"
82
+ end
83
+ failure_message_when_negated do |driver|
84
+ "expected exit status not to be #{expected}"
85
+ end
86
+ end
71
87
  end
72
88
  end
@@ -13,16 +13,20 @@ module TUITD
13
13
  # {
14
14
  # "name": "My test",
15
15
  # "rows": 24, "cols": 80, "timeout": 10,
16
+ # "chdir": "/path/to/workdir",
17
+ # "before_all": [{"start": "my_tui", "env": {"FOO": "bar"}}],
16
18
  # "steps": [
17
- # {"start": "my_tui"},
18
19
  # {"wait_for_text": "> "},
19
20
  # {"send": "hello\n"},
20
21
  # {"assert_text": "hello"},
21
- # {"assert_fg": [0, 0], "is": "cyan"},
22
- # {"close": true}
23
- # ]
22
+ # {"assert_fg": [0, 0], "is": "cyan"}
23
+ # ],
24
+ # "after_all": [{"close": true}]
24
25
  # }
25
26
  #
27
+ # Per-step "timeout" overrides the top-level default:
28
+ # {"wait_for_text": "Slow", "timeout": 60}
29
+ #
26
30
  class TestRunner
27
31
  Result = Struct.new(:step, :passed, :message, keyword_init: true)
28
32
 
@@ -30,47 +34,60 @@ module TUITD
30
34
  raw = source.is_a?(String) ? JSON.parse(source) : source
31
35
  @plan = raw.transform_keys(&:to_sym)
32
36
  @plan[:steps] = @plan[:steps].map { |s| s.transform_keys(&:to_sym) }
37
+ @plan[:before_all] = @plan[:before_all]&.map { |s| s.transform_keys(&:to_sym) }
38
+ @plan[:after_all] = @plan[:after_all]&.map { |s| s.transform_keys(&:to_sym) }
33
39
  @on_step = on_step
34
40
  end
35
41
 
36
42
  def run
37
- results = []
38
- all_passed = true
39
43
  driver = nil
40
44
  rows = @plan[:rows] || 40
41
45
  cols = @plan[:cols] || 120
42
46
  timeout = @plan[:timeout] || 30
43
47
  chdir = @plan[:chdir]
44
48
 
45
- @plan[:steps].each do |step|
46
- action = step.keys.first.to_s
47
- value = step.values.first
48
- start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
49
-
50
- begin
51
- r = case action
52
- when "start"
53
- driver&.close
54
- driver = Driver.new(value.to_s, rows: rows, cols: cols, timeout: timeout, chdir: chdir)
55
- driver.start
56
- Result.new(step: action, passed: true, message: "Started: #{value}")
57
-
58
- when "send"
59
- ensure_driver!(driver)
60
- driver.send(value.to_s)
61
- Result.new(step: action, passed: true, message: "Sent #{value.to_s.length} characters")
49
+ hooks = [
50
+ { label: :before_all, steps: @plan[:before_all] || [] },
51
+ { label: :main, steps: @plan[:steps] },
52
+ { label: :after_all, steps: @plan[:after_all] || [] }
53
+ ]
62
54
 
63
- when "send_key"
64
- ensure_driver!(driver)
65
- driver.send_keys(value.to_s.to_sym)
66
- Result.new(step: action, passed: true, message: "Sent key: #{value}")
55
+ all_results = []
56
+ all_passed = true
57
+ total_steps = hooks.sum { |p| p[:steps].size }
67
58
 
68
- when "wait_for_text"
69
- ensure_driver!(driver)
70
- driver.wait_for_text(value.to_s)
71
- Result.new(step: action, passed: true, message: "Found: #{value}")
59
+ hooks.each do |phase|
60
+ phase[:steps].each do |step|
61
+ action = step.keys.first.to_s
62
+ value = step.values.first
72
63
 
73
- when "wait_for_stable"
64
+ begin
65
+ step_timeout = step[:timeout] || timeout
66
+ r = case action
67
+ when "start"
68
+ driver&.close
69
+ env = step[:env] || {}
70
+ env = env.transform_keys(&:to_sym).transform_values(&:to_s) if env.is_a?(Hash)
71
+ driver = Driver.new(value.to_s, rows: rows, cols: cols, timeout: step_timeout, chdir: chdir, env: env)
72
+ driver.start
73
+ Result.new(step: action, passed: true, message: "Started: #{value}")
74
+
75
+ when "send"
76
+ ensure_driver!(driver)
77
+ driver.send(value.to_s)
78
+ Result.new(step: action, passed: true, message: "Sent #{value.to_s.length} characters")
79
+
80
+ when "send_key"
81
+ ensure_driver!(driver)
82
+ driver.send_keys(value.to_s.to_sym)
83
+ Result.new(step: action, passed: true, message: "Sent key: #{value}")
84
+
85
+ when "wait_for_text"
86
+ ensure_driver!(driver)
87
+ driver.wait_for_text(value.to_s)
88
+ Result.new(step: action, passed: true, message: "Found: #{value}")
89
+
90
+ when "wait_for_stable"
74
91
  ensure_driver!(driver)
75
92
  driver.wait_for_stable
76
93
  Result.new(step: action, passed: true, message: "Stable")
@@ -165,7 +182,7 @@ module TUITD
165
182
  r = Result.new(step: action, passed: false, message: "#{e.class}: #{e.message}")
166
183
  end
167
184
 
168
- results << r
185
+ all_results << r
169
186
  all_passed &&= r.passed
170
187
 
171
188
  if @on_step
@@ -176,8 +193,8 @@ module TUITD
176
193
  # ignore — state retrieval is best-effort
177
194
  end
178
195
  @on_step.call(
179
- index: results.size - 1,
180
- total: @plan[:steps].size,
196
+ index: all_results.size - 1,
197
+ total: total_steps,
181
198
  action: action,
182
199
  value: value,
183
200
  result: r,
@@ -186,13 +203,14 @@ module TUITD
186
203
  )
187
204
  end
188
205
  end
206
+ end
189
207
 
190
208
  driver&.close
191
209
 
192
210
  {
193
211
  name: @plan[:name] || "(unnamed)",
194
212
  passed: all_passed,
195
- results: results.map(&:to_h)
213
+ results: all_results.map(&:to_h)
196
214
  }
197
215
  end
198
216
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TUITD
4
- VERSION = "0.2.2"
4
+ VERSION = "0.2.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tui-td
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Haluk Durmus