yap-shell 0.5.2 → 0.6.0

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +11 -0
  5. data/Gemfile.travis +8 -0
  6. data/README.md +9 -11
  7. data/addons/history/history.rb +1 -0
  8. data/addons/history_search/history_search.rb +21 -17
  9. data/bin/yap +47 -55
  10. data/bin/yap-dev +0 -1
  11. data/lib/yap/configuration.rb +25 -0
  12. data/lib/yap/shell/builtins/alias.rb +16 -0
  13. data/lib/yap/shell/commands.rb +15 -3
  14. data/lib/yap/shell/evaluation/shell_expansions.rb +10 -10
  15. data/lib/yap/shell/evaluation.rb +29 -4
  16. data/lib/yap/shell/execution/context.rb +3 -2
  17. data/lib/yap/shell/execution/file_system_command_execution.rb +9 -17
  18. data/lib/yap/shell/repl.rb +2 -5
  19. data/lib/yap/shell/version.rb +1 -1
  20. data/lib/yap/shell.rb +8 -2
  21. data/lib/yap/world.rb +23 -18
  22. data/lib/yap.rb +52 -7
  23. data/spec/features/aliases_spec.rb +78 -0
  24. data/spec/features/environment_variables_spec.rb +69 -0
  25. data/spec/features/filesystem_commands_spec.rb +61 -0
  26. data/spec/features/first_time_spec.rb +45 -0
  27. data/spec/features/grouping_spec.rb +81 -0
  28. data/spec/features/line_editing_spec.rb +166 -0
  29. data/spec/features/range_spec.rb +35 -0
  30. data/spec/features/redirection_spec.rb +225 -0
  31. data/spec/features/repetition_spec.rb +118 -0
  32. data/spec/features/shell_expansions_spec.rb +127 -0
  33. data/spec/spec_helper.rb +162 -0
  34. data/spec/support/matchers/have_not_printed.rb +30 -0
  35. data/spec/support/matchers/have_printed.rb +30 -0
  36. data/spec/support/very_soon.rb +9 -0
  37. data/spec/support/yap_spec_dsl.rb +240 -0
  38. data/yap-shell.gemspec +4 -2
  39. metadata +69 -8
@@ -0,0 +1,225 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'I/O Redirection', type: :feature do
4
+ before do
5
+ write_executable_script 'fail', <<-SCRIPT.strip_heredoc
6
+ |#!/bin/sh
7
+ |>&2 echo fail $1
8
+ |exit 1
9
+ SCRIPT
10
+ end
11
+
12
+ describe 'STDOUT' do
13
+ it 'redirects with: >' do
14
+ type 'echo foo > output.txt'
15
+ enter
16
+ clear_all_output
17
+
18
+ type 'cat output.txt'
19
+ enter
20
+ expect { output }.to have_printed('foo')
21
+ end
22
+
23
+ it 'redirects with: 1>' do
24
+ type 'echo hrm 1> output.txt'
25
+ enter
26
+ clear_all_output
27
+
28
+ type 'cat output.txt'
29
+ enter
30
+ expect { output }.to have_printed('hrm')
31
+ end
32
+
33
+ it 'overwrites the file' do
34
+ type 'echo foo > output.txt'
35
+ enter
36
+ type 'echo bar > output.txt'
37
+ enter
38
+ clear_all_output
39
+
40
+ type 'cat output.txt'
41
+ enter
42
+ expect { output }.to have_printed('bar')
43
+ expect { output }.to have_not_printed('foo')
44
+ end
45
+
46
+ describe 'appending' do
47
+ before do
48
+ type 'echo foo > output.txt'
49
+ enter
50
+ type 'cat output.txt'
51
+ enter
52
+ expect { output }.to have_printed(/foo/)
53
+ clear_all_output
54
+ end
55
+
56
+ it 'appends with: >>' do
57
+ type 'echo bar >> output.txt'
58
+ enter
59
+ type 'cat output.txt'
60
+ enter
61
+ expect { output }.to have_printed(/foo.*\n.*bar/)
62
+ end
63
+
64
+ it 'appends with: 1>>' do
65
+ type 'echo bar 1>> output.txt'
66
+ enter
67
+ type 'cat output.txt'
68
+ enter
69
+ expect { output }.to have_printed(/foo.*\n.*bar/)
70
+ end
71
+ end
72
+ end
73
+
74
+ describe 'STDERR' do
75
+ it 'redirects with: 2>' do
76
+ type './fail foo 2> error.txt'
77
+ enter
78
+ clear_all_output
79
+
80
+ type 'cat error.txt'
81
+ enter
82
+ expect { output }.to have_printed('fail foo')
83
+ end
84
+
85
+ it 'overwrites the file' do
86
+ type './fail foo 2> error.txt'
87
+ enter
88
+ type './fail bar 2> error.txt'
89
+ enter
90
+ clear_all_output
91
+
92
+ type 'cat error.txt'
93
+ enter
94
+ expect { output }.to have_printed('fail bar')
95
+ expect { output }.to have_not_printed('fail foo')
96
+ clear_all_output
97
+ end
98
+
99
+ describe 'appending' do
100
+ before do
101
+ type './fail caz 2> error.txt'
102
+ enter
103
+ type 'cat error.txt'
104
+ enter
105
+ expect { output }.to have_printed(/fail caz/)
106
+ clear_all_output
107
+ end
108
+
109
+ it 'appends with: 2>>' do
110
+ type './fail box 2>> error.txt'
111
+ enter
112
+ type 'cat error.txt'
113
+ enter
114
+ expect { output }.to have_printed(/fail caz.*\n.*fail box/)
115
+ end
116
+ end
117
+ end
118
+
119
+ describe 'STDOUT and STDERR separately for a command' do
120
+ before do
121
+ write_executable_script 'print-stuff', <<-SCRIPT.strip_heredoc
122
+ |#!/bin/sh
123
+ |echo 'normal foo'
124
+ |(>&2 echo 'error foo')
125
+ SCRIPT
126
+ end
127
+
128
+ it 'redirects with: > and 2>' do
129
+ type './print-stuff > stdout.txt 2> stderr.txt'
130
+ enter
131
+ clear_all_output
132
+
133
+ type 'cat stdout.txt'
134
+ enter
135
+ expect { output }.to have_printed('normal foo')
136
+ clear_all_output
137
+
138
+ type 'cat stdout.txt'
139
+ enter
140
+ expect { output }.to have_not_printed('error foo')
141
+ clear_all_output
142
+
143
+ type 'cat stderr.txt'
144
+ enter
145
+ expect { output }.to have_printed('error foo')
146
+ clear_all_output
147
+
148
+ type 'cat stderr.txt'
149
+ enter
150
+ expect { output }.to have_not_printed('normal foo')
151
+ end
152
+
153
+ it 'redirects with: 1> and 2>' do
154
+ type './print-stuff 1> stdout.txt 2> stderr.txt'
155
+ enter
156
+ clear_all_output
157
+
158
+ type 'cat stdout.txt'
159
+ enter
160
+ expect { output }.to have_printed('normal foo')
161
+ clear_all_output
162
+
163
+ type 'cat stdout.txt'
164
+ enter
165
+ expect { output }.to have_not_printed('error foo')
166
+ clear_all_output
167
+
168
+ type 'cat stderr.txt'
169
+ enter
170
+ expect { output }.to have_printed('error foo')
171
+ clear_all_output
172
+
173
+ type 'cat stderr.txt'
174
+ enter
175
+ expect { output }.to have_not_printed('normal foo')
176
+ end
177
+ end
178
+
179
+ describe 'STDOUT and STDERR pointing to the same file' do
180
+ before do
181
+ write_executable_script 'echo-stdout-and-stderr', <<-SCRIPT.strip_heredoc
182
+ |#!/bin/sh
183
+ |echo $1
184
+ |>&2 echo $2
185
+ SCRIPT
186
+ end
187
+
188
+ it 'redirects and overwrites with: &>' do
189
+ type './echo-stdout-and-stderr foo bar &> output-and-error.txt'
190
+ enter
191
+ type 'cat output-and-error.txt'
192
+ enter
193
+ expect { output }.to have_printed(/foo.*\n.*bar/m)
194
+ end
195
+
196
+ it 'redirects and overwrites with: 2>&1' do
197
+ type './echo-stdout-and-stderr bar foo 2>&1 output-and-error.txt'
198
+ enter
199
+ type 'cat output-and-error.txt'
200
+ enter
201
+ expect { output }.to have_printed(/bar.*\n.*foo/m)
202
+ end
203
+
204
+ it 'redirects and appends with: &>>' do
205
+ type './echo-stdout-and-stderr howdy hey &>> output-and-error.txt'
206
+ enter
207
+ type 'cat output-and-error.txt'
208
+ enter
209
+ expect { output }.to have_printed(/howdy.*\n.*hey/m)
210
+ end
211
+ end
212
+
213
+ describe 'STDIN' do
214
+ it 'redirect with: <' do
215
+ type 'echo foo > stdin.txt'
216
+ enter
217
+ clear_all_output
218
+
219
+ type 'cat < stdin.txt'
220
+ enter
221
+ expect { output }.to have_printed('foo')
222
+ end
223
+ end
224
+
225
+ end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Repetition', type: :feature do
4
+ describe 'N.times: statement' do
5
+ it 'runs the line N times' do
6
+ type '5.times : echo foo'
7
+ enter
8
+ expect { output }.to have_printed(/(.*foo){5}/m)
9
+ end
10
+
11
+ it 'is not white-space sensitive' do
12
+ type '5.times : echo foo'
13
+ enter
14
+ expect { output }.to have_printed(/(.*foo){5}/m)
15
+ end
16
+ end
17
+
18
+ describe 'N.times: statement1 ; statement2' do
19
+ it 'runs all the statements on the line N times' do
20
+ type '3.times : echo foo ; echo bar'
21
+ enter
22
+ expect { output }.to have_printed(/(.*foo.*bar){3}/m)
23
+ end
24
+
25
+ it 'is not white-space sensitive' do
26
+ type '3.times:echo foo ; echo bar'
27
+ enter
28
+ expect { output }.to have_printed(/(.*foo.*bar){3}/m)
29
+ end
30
+ end
31
+
32
+ describe 'parameters' do
33
+ it 'supplies an index for each execution' do
34
+ type '3.times as n: echo foo$n ; echo bar$n '
35
+ enter
36
+ expect { output }.to have_printed(/foo1.*bar1.*foo2.*bar2.*foo3.*bar3/m)
37
+ end
38
+
39
+ it 'does not overwrite existing variables with the same name' do
40
+ type "n='hello world'"
41
+ enter
42
+ type '3.times as n: echo $n'
43
+ enter
44
+ expect { output }.to have_printed(/1.*2.*3/m)
45
+ clear_all_output
46
+
47
+ type 'echo $n'
48
+ enter
49
+ expect { output }.to have_printed('hello world')
50
+ end
51
+ end
52
+
53
+ describe 'repetition with blocks' do
54
+ it 'runs the code inside the block N times' do
55
+ type '4.times { echo foo }'
56
+ enter
57
+ expect { output }.to have_printed(/(.*foo){4}/m)
58
+ end
59
+
60
+ xit 'requires white-space before the opening curly brace' do
61
+ # this breaks everything :(
62
+ type '4.times{ echo foo }'
63
+ enter
64
+ expect { output }.to have_printed(/Infinite loop detected/m)
65
+ skip 'Provide a better error message for the user.'
66
+ end
67
+
68
+ it 'requires white-space before the closing curly brace' do
69
+ type '4.times { echo foo}'
70
+ enter
71
+ expect { output }.to have_printed(/Parse error/m)
72
+ # should this be an error? Really? We can't tell that we're in a block
73
+ # and we've got a closing curly brace right there?
74
+ skip 'Provide a better error message for the user.'
75
+ end
76
+
77
+ it 'does not require white-space after the opening curly brace' do
78
+ type '4.times {echo foo }'
79
+ enter
80
+ expect { output }.to have_printed(/(.*foo){4}/m)
81
+ end
82
+
83
+ it 'does not require white-space after the closing curly brace' do
84
+ type '4.times { echo foo };'
85
+ enter
86
+ expect { output }.to have_printed(/Parse error/m)
87
+ skip "This shouldn't be an error"
88
+ end
89
+
90
+ describe 'block parameters' do
91
+ it 'supplies an index for each execution' do
92
+ type 'echo beg ; 4.times { |n| echo $n } ; echo end'
93
+ enter
94
+ expect { output }.to have_printed(/beg.*\n.*1.*2.*3.*4.*\n.*end/m)
95
+ end
96
+
97
+ it 'does not overwrite existing variables with the same name' do
98
+ type "n='hello world'"
99
+ enter
100
+ type 'echo beg ; 3.times { |n| echo $n } ; echo end'
101
+ enter
102
+ expect { output }.to have_printed(/beg.*\n.*1.*2.*3.*\n.*end/m)
103
+ clear_all_output
104
+
105
+ type 'echo $n'
106
+ enter
107
+ expect { output }.to have_printed('hello world')
108
+ end
109
+
110
+ it 'can step thru the repetition in groups based on how many block params are provided' do
111
+ type 'echo beg ; 5.times { |a, b| echo $a $b } ; echo end'
112
+ enter
113
+ expect { output }.to have_printed(/beg.*\n.*1 2.*\n.*3 4.*\n.*5.*\n.*end/m)
114
+ end
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1,127 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Shell expansions', type: :feature do
4
+ describe 'aliases' do
5
+ before do
6
+ type 'alias foo="echo bar"'
7
+ enter
8
+ expect { output }.to have_printed('Setting alias foo')
9
+ end
10
+
11
+ it 'expands aliases found in command position' do
12
+ type 'foo'
13
+ enter
14
+ expect { output }.to have_printed('bar')
15
+ end
16
+
17
+ it 'does not expand aliases found in argument position' do
18
+ type 'echo foo'
19
+ enter
20
+ expect { output }.to have_printed('foo')
21
+ expect { output }.to have_not_printed('bar')
22
+ end
23
+ end
24
+
25
+ describe 'environment variable expansion' do
26
+ it 'expands upper-case env vars with a preceding $' do
27
+ type 'FOO=bar echo $FOO'
28
+ enter
29
+ expect { output }.to have_printed('bar')
30
+ end
31
+
32
+ it 'expands lower-case vars with a preceding $' do
33
+ type 'foo=bar echo $foo'
34
+ enter
35
+ expect { output }.to have_printed('bar')
36
+ end
37
+
38
+ it 'is case sensitive' do
39
+ type 'foo=bar echo $foo'
40
+ enter
41
+ expect { output }.to have_printed('bar')
42
+
43
+ type 'echo $FOO'
44
+ enter
45
+ expect { output }.to have_printed('$FOO')
46
+ end
47
+
48
+ it 'does not expand escaped env vars' do
49
+ type 'FOO=bar echo \\$FOO'
50
+ enter
51
+ expect { output }.to have_printed('$FOO')
52
+ end
53
+ end
54
+
55
+ describe 'exit status for last command: $?' do
56
+ it 'prints whatever the exit status of the last command was' do
57
+ write_executable_script 'exit-with-status-code', <<-SCRIPT.strip_heredoc
58
+ |#!/bin/sh
59
+ |exit $1
60
+ SCRIPT
61
+
62
+ type './exit-with-status-code 0 ; echo $?'
63
+ enter
64
+ expect { output }.to have_printed('0')
65
+
66
+ type './exit-with-status-code 1 ; echo $?'
67
+ enter
68
+ expect { output }.to have_printed('1')
69
+
70
+ type './exit-with-status-code 99 ; echo $?'
71
+ enter
72
+ expect { output }.to have_printed('99')
73
+ end
74
+ end
75
+
76
+ describe 'word expansions using curly braces' do
77
+ it 'expands in command position' do
78
+ type 'foo_{}'
79
+ enter
80
+ expect { error_output }.to have_printed('yap: command not found: foo_')
81
+ end
82
+
83
+ describe 'arguments' do
84
+ it 'expands no words' do
85
+ type 'echo foo_{}'
86
+ enter
87
+ expect { output }.to have_printed('foo_')
88
+ end
89
+
90
+ it 'expands single words' do
91
+ type 'echo foo_{bar}'
92
+ enter
93
+ expect { output }.to have_printed('foo_bar')
94
+ end
95
+
96
+ it 'multiple words in comma-separated list' do
97
+ type 'echo foo_{bar,bay,baz}'
98
+ enter
99
+ expect { output }.to have_printed('foo_bar foo_bay')
100
+ end
101
+
102
+ it 'expands environment variables' do
103
+ type 'FOO=huzzah echo foo_{$FOO}'
104
+ enter
105
+ expect { output }.to have_printed('foo_huzzah')
106
+ end
107
+
108
+ it 'does not expand environment variables in double quotes' do
109
+ type 'FOO=huzzah echo foo_{"$FOO"}'
110
+ enter
111
+ expect { output }.to have_printed('foo_{$FOO}')
112
+ end
113
+
114
+ it 'does not expand environment variables in single quotes' do
115
+ type %|FOO=huzzah echo foo_{'$FOO'}|
116
+ enter
117
+ expect { output }.to have_printed('foo_{$FOO}')
118
+ end
119
+
120
+ it 'treats ~ as any a normal character, not home directory expansion' do
121
+ type 'echo foo_{~}'
122
+ enter
123
+ expect { output }.to have_printed('foo_~')
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,162 @@
1
+ require 'ansi_string'
2
+ require 'childprocess'
3
+ require 'pry'
4
+
5
+ Dir[File.dirname(__FILE__) + '/support/**/**/*.rb'].each do |file|
6
+ require file
7
+ end
8
+
9
+ class String
10
+ def strip_heredoc
11
+ gsub(/^\s*\|/, '')
12
+ end
13
+ end
14
+
15
+ # This file was generated by the `rspec --init` command. Conventionally, all
16
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
17
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
18
+ # this file to always be loaded, without a need to explicitly require it in any
19
+ # files.
20
+ #
21
+ # Given that it is always loaded, you are encouraged to keep this file as
22
+ # light-weight as possible. Requiring heavyweight dependencies from this file
23
+ # will add to the boot time of your test suite on EVERY test run, even for an
24
+ # individual file that may not need all of that loaded. Instead, consider making
25
+ # a separate helper file that requires the additional dependencies and performs
26
+ # the additional setup, and require it from the spec files that actually need
27
+ # it.
28
+ #
29
+ # The `.rspec` file also contains a few flags that are not defaults but that
30
+ # users commonly want.
31
+ #
32
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
33
+ RSpec.configure do |config|
34
+ config.include Yap::Spec::DSL
35
+
36
+ config.before(:all, type: :feature) do
37
+ ENV['HOME'] = tmp_dir.to_s
38
+
39
+ set_yap_command_line_arguments \
40
+ '--no-history', '--no-addons', '--no-rcfiles', '--skip-first-time'
41
+
42
+ turn_on_debug_log(debug: 'editor')
43
+
44
+ initialize_shell
45
+
46
+ mkdir tmp_dir
47
+ Dir.chdir tmp_dir
48
+
49
+ type "cd #{tmp_dir}"
50
+ enter
51
+
52
+ type "pwd"
53
+ enter
54
+ expect { output }.to have_printed(File.expand_path(tmp_dir))
55
+ clear_all_output
56
+ end
57
+
58
+ config.after(:all, type: :feature) do
59
+ shell.stop if shell
60
+ end
61
+
62
+ config.after(:each, type: :feature) do
63
+ begin
64
+ if self.class.metadata[:forks]
65
+ # no-op because the example will cause yap to fork inside the child
66
+ # childprocess and will break the IO pipes we know about in the test
67
+ else
68
+ enter if typed_content_awaiting_enter?
69
+
70
+ # Use yap to tell us when it's done doing whatever is was doing.
71
+ # This ensures we don'try to clean up before yap is done!
72
+ type 'echo done with this test'
73
+ enter
74
+ expect { output }.to have_printed('done with this test')
75
+ end
76
+ clear_all_output
77
+ ensure
78
+ Dir.chdir(tmp_dir) do
79
+ # remove all files including hidden but not current directory and
80
+ # parent directory
81
+ FileUtils.rm_rf Dir.glob('{.,}*') - %w(. ..)
82
+ end
83
+ end
84
+ end
85
+
86
+ # rspec-expectations config goes here. You can use an alternate
87
+ # assertion/expectation library such as wrong or the stdlib/minitest
88
+ # assertions if you prefer.
89
+ config.expect_with :rspec do |expectations|
90
+ # This option will default to `true` in RSpec 4. It makes the `description`
91
+ # and `failure_message` of custom matchers include text for helper methods
92
+ # defined using `chain`, e.g.:
93
+ # be_bigger_than(2).and_smaller_than(4).description
94
+ # # => "be bigger than 2 and smaller than 4"
95
+ # ...rather than:
96
+ # # => "be bigger than 2"
97
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
98
+ end
99
+
100
+ # rspec-mocks config goes here. You can use an alternate test double
101
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
102
+ config.mock_with :rspec do |mocks|
103
+ # Prevents you from mocking or stubbing a method that does not exist on
104
+ # a real object. This is generally recommended, and will default to
105
+ # `true` in RSpec 4.
106
+ mocks.verify_partial_doubles = true
107
+ end
108
+
109
+ # The settings below are suggested to provide a good initial experience
110
+ # with RSpec, but feel free to customize to your heart's content.
111
+ =begin
112
+ # These two settings work together to allow you to limit a spec run
113
+ # to individual examples or groups you care about by tagging them with
114
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
115
+ # get run.
116
+ config.filter_run :focus
117
+ config.run_all_when_everything_filtered = true
118
+
119
+ # Allows RSpec to persist some state between runs in order to support
120
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
121
+ # you configure your source control system to ignore this file.
122
+ config.example_status_persistence_file_path = "spec/examples.txt"
123
+
124
+ # Limits the available syntax to the non-monkey patched syntax that is
125
+ # recommended. For more details, see:
126
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
127
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
128
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
129
+ config.disable_monkey_patching!
130
+
131
+ # This setting enables warnings. It's recommended, but in some cases may
132
+ # be too noisy due to issues in dependencies.
133
+ config.warnings = true
134
+
135
+ # Many RSpec users commonly either run the entire suite or an individual
136
+ # file, and it's useful to allow more verbose output when running an
137
+ # individual spec file.
138
+ if config.files_to_run.one?
139
+ # Use the documentation formatter for detailed output,
140
+ # unless a formatter has already been configured
141
+ # (e.g. via a command-line flag).
142
+ config.default_formatter = 'doc'
143
+ end
144
+
145
+ # Print the 10 slowest examples and example groups at the
146
+ # end of the spec run, to help surface which specs are running
147
+ # particularly slow.
148
+ config.profile_examples = 10
149
+
150
+ # Run specs in random order to surface order dependencies. If you find an
151
+ # order dependency and want to debug it, you can fix the order by providing
152
+ # the seed, which is printed after each run.
153
+ # --seed 1234
154
+ config.order = :random
155
+
156
+ # Seed global randomization in this process using the `--seed` CLI option.
157
+ # Setting this allows you to use `--seed` to deterministically reproduce
158
+ # test failures related to randomization by passing the same `--seed` value
159
+ # as the one that triggered the failure.
160
+ Kernel.srand config.seed
161
+ =end
162
+ end
@@ -0,0 +1,30 @@
1
+ RSpec::Matchers.define :have_not_printed do |expected|
2
+ output_seen_so_far = ""
3
+
4
+ match do |block|
5
+ regex = expected
6
+ regex = /#{Regexp.escape(regex)}/m unless regex.is_a?(Regexp)
7
+ begin
8
+ very_soon do
9
+ current_output = block.call
10
+ output_seen_so_far << current_output
11
+ current_output.length > 0
12
+ end
13
+ rescue Timeout::Error
14
+ return true # we didn't see it, make the assumption we're good
15
+ end
16
+ !regex.match(output_seen_so_far)
17
+ end
18
+
19
+ failure_message do |actual|
20
+ if expected.is_a?(Regexp)
21
+ "expected that #{expected.inspect} would not match #{output_seen_so_far.inspect}"
22
+ else
23
+ "expected that #{expected.inspect} would not appear in #{output_seen_so_far.inspect}"
24
+ end
25
+ end
26
+
27
+ def supports_block_expectations?
28
+ true
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ RSpec::Matchers.define :have_printed do |expected|
2
+ output_seen_so_far = ""
3
+
4
+ match do |block|
5
+ begin
6
+ regex = expected
7
+ regex = /#{Regexp.escape(regex)}/m unless regex.is_a?(Regexp)
8
+ very_soon do
9
+ current_output = block.call
10
+ output_seen_so_far << current_output
11
+ regex.match(current_output)
12
+ end
13
+ true
14
+ rescue Timeout::Error
15
+ false
16
+ end
17
+ end
18
+
19
+ failure_message do |actual|
20
+ if expected.is_a?(Regexp)
21
+ "expected that #{expected.inspect} would match #{output_seen_so_far.inspect}"
22
+ else
23
+ "expected that #{expected.inspect} would appear in #{output_seen_so_far.inspect}"
24
+ end
25
+ end
26
+
27
+ def supports_block_expectations?
28
+ true
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ def very_soon(timeout: 5, &block)
2
+ Timeout.timeout(timeout) do
3
+ loop do
4
+ result = block.call
5
+ break result if result
6
+ sleep 0.1
7
+ end
8
+ end
9
+ end