greenletters 0.0.1
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.
- data/.gitignore +19 -0
- data/History.txt +4 -0
- data/README.org +107 -0
- data/Rakefile +30 -0
- data/bin/greenletters +7 -0
- data/examples/adventure.rb +30 -0
- data/examples/cucumber/adventure.feature +67 -0
- data/examples/cucumber/greenletters.log +251 -0
- data/examples/cucumber/support/env.rb +4 -0
- data/lib/greenletters/cucumber_steps.rb +68 -0
- data/lib/greenletters.rb +566 -0
- data/script/console +5 -0
- data/spec/greenletters_spec.rb +6 -0
- data/spec/spec_helper.rb +15 -0
- data/test/test_greenletters.rb +0 -0
- data/version.txt +1 -0
- metadata +93 -0
data/.gitignore
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# The list of files that should be ignored by Mr Bones.
|
2
|
+
# Lines that start with '#' are comments.
|
3
|
+
#
|
4
|
+
# A .gitignore file can be used instead by setting it as the ignore
|
5
|
+
# file in your Rakefile:
|
6
|
+
#
|
7
|
+
# Bones {
|
8
|
+
# ignore_file '.gitignore'
|
9
|
+
# }
|
10
|
+
#
|
11
|
+
# For a project with a C extension, the following would be a good set of
|
12
|
+
# exclude patterns (uncomment them if you want to use them):
|
13
|
+
# *.[oa]
|
14
|
+
# *~
|
15
|
+
announcement.txt
|
16
|
+
coverage
|
17
|
+
doc
|
18
|
+
pkg
|
19
|
+
/examples/cucumber/greenletters.log
|
data/History.txt
ADDED
data/README.org
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
#+Title: Greenletters README
|
2
|
+
#+AUTHOR: Avdi Grimm
|
3
|
+
#+EMAIL: avdi@avdi.org
|
4
|
+
|
5
|
+
# Configuration:
|
6
|
+
#+STARTUP: odd
|
7
|
+
#+STARTUP: hi
|
8
|
+
#+STARTUP: hidestars
|
9
|
+
|
10
|
+
|
11
|
+
* Synopsis
|
12
|
+
|
13
|
+
#+begin_src ruby
|
14
|
+
require 'greenletters'
|
15
|
+
adv = Greenletters::Process.new("adventure", :transcript => $stdout)
|
16
|
+
adv.on(:output, /welcome to adventure/i) do |process, match_data|
|
17
|
+
adv << "no\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "Starting adventure..."
|
21
|
+
adv.start!
|
22
|
+
adv.wait_for(:output, /you are standing at the end of a road/i)
|
23
|
+
adv << "east\n"
|
24
|
+
adv.wait_for(:output, /inside a building/i)
|
25
|
+
adv << "quit\n"
|
26
|
+
adv.wait_for(:output, /really want to quit/i)
|
27
|
+
adv << "yes\n"
|
28
|
+
adv.wait_for(:exit)
|
29
|
+
puts "Adventure has exited."
|
30
|
+
#+end_src
|
31
|
+
|
32
|
+
Or, in Cucumber format:
|
33
|
+
|
34
|
+
#+BEGIN_SRC
|
35
|
+
Given process activity is logged to "greenletters.log"
|
36
|
+
Given a process "adventure" from command "adventure"
|
37
|
+
Given I reply "no" to output "Would you like instructions?" from process "adventure"
|
38
|
+
Given I reply "yes" to output "Do you really want to quit" from process "adventure"
|
39
|
+
When I execute the process "adventure"
|
40
|
+
Then I should see the following output from process "adventure":
|
41
|
+
"""
|
42
|
+
You are standing at the end of a road before a small brick building.
|
43
|
+
Around you is a forest. A small stream flows out of the building and
|
44
|
+
down a gully.
|
45
|
+
"""
|
46
|
+
When I enter "east" into process "adventure"
|
47
|
+
Then I should see the following output from process "adventure":
|
48
|
+
"""
|
49
|
+
You are inside a building, a well house for a large spring.
|
50
|
+
"""
|
51
|
+
#+END_SRC
|
52
|
+
|
53
|
+
* What
|
54
|
+
|
55
|
+
Greenletters is a console interaction automation library similar to [[http://directory.fsf.org/project/expect/][GNU
|
56
|
+
Expect]]. You can use it to script interactions with command-line programs.
|
57
|
+
|
58
|
+
* Why
|
59
|
+
Because Ruby's built-in expect.rb is pretty underpowered and I wanted to drive
|
60
|
+
command-line applications from Ruby, not TCL.
|
61
|
+
|
62
|
+
* Who
|
63
|
+
Greenletters is by [[mailto:avdi@avdi.org][Avdi Grimm]].
|
64
|
+
|
65
|
+
* Where
|
66
|
+
http://github.com/avdi/greenletters
|
67
|
+
|
68
|
+
* How
|
69
|
+
Greenletters uses the pty.rb library under the covers to create a UNIX
|
70
|
+
pseudoterminal under Ruby's control. Of course, this means that it is
|
71
|
+
*NIX-only; Windows users need not apply.
|
72
|
+
|
73
|
+
The advantage of using a PTY is that *any* output - inclding output written to
|
74
|
+
the console instead of STDOUT/STDERR - will be captured by Greenletters.
|
75
|
+
|
76
|
+
* Cucumber
|
77
|
+
To use the Cucumber steps in your own feature files, put the following in your env.rb:
|
78
|
+
|
79
|
+
#+BEGIN_SRC ruby
|
80
|
+
require 'greenletters'
|
81
|
+
require 'greenletters/cucumber_steps'
|
82
|
+
#+END_SRC
|
83
|
+
|
84
|
+
* LICENSE
|
85
|
+
|
86
|
+
(The MIT License)
|
87
|
+
|
88
|
+
Copyright (c) 2010 Avdi Grimm
|
89
|
+
|
90
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
91
|
+
a copy of this software and associated documentation files (the
|
92
|
+
'Software'), to deal in the Software without restriction, including
|
93
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
94
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
95
|
+
permit persons to whom the Software is furnished to do so, subject to
|
96
|
+
the following conditions:
|
97
|
+
|
98
|
+
The above copyright notice and this permission notice shall be
|
99
|
+
included in all copies or substantial portions of the Software.
|
100
|
+
|
101
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
102
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
103
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
104
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
105
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
106
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
107
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require 'bones'
|
4
|
+
rescue LoadError
|
5
|
+
abort '### Please install the "bones" gem ###'
|
6
|
+
end
|
7
|
+
|
8
|
+
task :default => 'test:run'
|
9
|
+
task 'gem:release' => 'test:run'
|
10
|
+
|
11
|
+
Bones {
|
12
|
+
name 'greenletters'
|
13
|
+
authors 'Avdi Grimm'
|
14
|
+
email 'avdi@avdi.org'
|
15
|
+
url 'http://github.com/avdi/greenletters'
|
16
|
+
ignore_file '.gitignore'
|
17
|
+
readme_file 'README.org'
|
18
|
+
|
19
|
+
summary 'A Ruby console automation framework a la Expect'
|
20
|
+
|
21
|
+
description <<-END
|
22
|
+
Greenletterrs is a console automation framework, similar to the classic
|
23
|
+
utility Expect. You give it a command to execute, and tell it which outputs
|
24
|
+
or events to expect and how to respond to them.
|
25
|
+
|
26
|
+
Greenletters also includes a set of Cucumber steps which simplify the task
|
27
|
+
of spcifying interactive command-line applications.
|
28
|
+
END
|
29
|
+
}
|
30
|
+
|
data/bin/greenletters
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# This demo interacts with the classic Collossal Cave Adventure game. To install
|
2
|
+
# the game on Debian-based systems (Ubuntu, etc), execute:
|
3
|
+
#
|
4
|
+
# sudo apt-get install bsdgames
|
5
|
+
#
|
6
|
+
$:.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
|
7
|
+
require 'greenletters'
|
8
|
+
require 'logger'
|
9
|
+
|
10
|
+
logger = ::Logger.new($stdout)
|
11
|
+
logger.level = ::Logger::INFO
|
12
|
+
# logger.level = ::Logger::DEBUG
|
13
|
+
adv = Greenletters::Process.new("adventure",
|
14
|
+
:logger => logger,
|
15
|
+
:transcript => $stdout)
|
16
|
+
adv.on(:output, /welcome to adventure/i) do |process, match_data|
|
17
|
+
adv << "no\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "Starting aadventure..."
|
21
|
+
adv.start!
|
22
|
+
adv.wait_for(:output, /you are standing at the end of a road/i)
|
23
|
+
adv << "east\n"
|
24
|
+
adv.wait_for(:output, /inside a building/i)
|
25
|
+
adv << "quit\n"
|
26
|
+
adv.wait_for(:output, /really want to quit/i)
|
27
|
+
adv << "yes\n"
|
28
|
+
adv.wait_for(:exit)
|
29
|
+
puts "Adventure has exited."
|
30
|
+
|
@@ -0,0 +1,67 @@
|
|
1
|
+
Feature: play adventure
|
2
|
+
As a nerd
|
3
|
+
I want to play a text adventure game
|
4
|
+
Because I'm old-skool
|
5
|
+
|
6
|
+
Scenario: play first few rooms (named process)
|
7
|
+
Given process activity is logged to "greenletters.log"
|
8
|
+
Given a process "adventure" from command "adventure"
|
9
|
+
Given I reply "no" to output "Would you like instructions?" from process "adventure"
|
10
|
+
Given I reply "yes" to output "Do you really want to quit" from process "adventure"
|
11
|
+
When I execute the process "adventure"
|
12
|
+
Then I should see the following output from process "adventure":
|
13
|
+
"""
|
14
|
+
You are standing at the end of a road before a small brick building.
|
15
|
+
Around you is a forest. A small stream flows out of the building and
|
16
|
+
down a gully.
|
17
|
+
"""
|
18
|
+
When I enter "east" into process "adventure"
|
19
|
+
Then I should see the following output from process "adventure":
|
20
|
+
"""
|
21
|
+
You are inside a building, a well house for a large spring.
|
22
|
+
"""
|
23
|
+
When I enter "west" into process "adventure"
|
24
|
+
Then I should see the following output from process "adventure":
|
25
|
+
"""
|
26
|
+
You're at end of road again.
|
27
|
+
"""
|
28
|
+
When I enter "south" into process "adventure"
|
29
|
+
Then I should see the following output from process "adventure":
|
30
|
+
"""
|
31
|
+
You are in a valley in the forest beside a stream tumbling along a
|
32
|
+
rocky bed.
|
33
|
+
"""
|
34
|
+
When I enter "quit" into process "adventure"
|
35
|
+
Then the process "adventure" should exit succesfully
|
36
|
+
|
37
|
+
Scenario: play first few rooms (default process)
|
38
|
+
Given process activity is logged to "greenletters.log"
|
39
|
+
Given a process from command "adventure"
|
40
|
+
Given I reply "no" to output "Would you like instructions?"
|
41
|
+
Given I reply "yes" to output "Do you really want to quit"
|
42
|
+
When I execute the process
|
43
|
+
Then I should see the following output:
|
44
|
+
"""
|
45
|
+
You are standing at the end of a road before a small brick building.
|
46
|
+
Around you is a forest. A small stream flows out of the building and
|
47
|
+
down a gully.
|
48
|
+
"""
|
49
|
+
When I enter "east"
|
50
|
+
Then I should see the following output:
|
51
|
+
"""
|
52
|
+
You are inside a building, a well house for a large spring.
|
53
|
+
"""
|
54
|
+
When I enter "west"
|
55
|
+
Then I should see the following output:
|
56
|
+
"""
|
57
|
+
You're at end of road again.
|
58
|
+
"""
|
59
|
+
When I enter "south"
|
60
|
+
Then I should see the following output:
|
61
|
+
"""
|
62
|
+
You are in a valley in the forest beside a stream tumbling along a
|
63
|
+
rocky bed.
|
64
|
+
"""
|
65
|
+
When I enter "quit"
|
66
|
+
Then the process should exit succesfully
|
67
|
+
|
@@ -0,0 +1,251 @@
|
|
1
|
+
D, [2010-07-18T09:07:00.322981 #4796] DEBUG -- : added trigger on output matching /Would\s+you\s+like\s+instructions\?/
|
2
|
+
D, [2010-07-18T09:07:00.324028 #4796] DEBUG -- : added trigger on output matching /Do\s+you\s+really\s+want\s+to\s+quit/
|
3
|
+
D, [2010-07-18T09:07:00.324701 #4796] DEBUG -- : installing end marker handler for __GREENLETTERS_PROCESS_ENDED__
|
4
|
+
D, [2010-07-18T09:07:00.324808 #4796] DEBUG -- : prepended trigger on output matching /__GREENLETTERS_PROCESS_ENDED__/
|
5
|
+
D, [2010-07-18T09:07:00.324875 #4796] DEBUG -- : executing /usr/bin/ruby1.8 -C /home/avdi/dev/greenletters/examples/cucumber -e system(*["adventure"]) -e puts("__GREENLETTERS_PROCESS_ENDED__") -e gets -e exit $?.exitstatus
|
6
|
+
D, [2010-07-18T09:07:00.325021 #4796] DEBUG -- : command environment:
|
7
|
+
{"LIBGL_DRIVERS_PATH"=>"/usr/lib/fglrx/dri:/usr/lib32/fglrx/dri", "GDM_LANG"=>"en_US.utf8", "LANGUAGE"=>"", "KONSOLE_DBUS_SERVICE"=>":1.10", "LOGNAME"=>"avdi", "GTK_MODULES"=>"canberra-gtk-module", "DBUS_SESSION_BUS_ADDRESS"=>"unix:abstract=/tmp/dbus-ysd8AY9I4g,guid=d9670f4428aeffbcad7a4cf94c40af03", "SPEECHD_PORT"=>"7560", "PATH"=>"/home/avdi/bin:/home/avdi/share/bin:/home/avdi/.gem/ruby/1.8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games", "USER"=>"avdi", "GNOME_KEYRING_PID"=>"2330", "LANG"=>"en_US.utf8", "DEFAULTS_PATH"=>"/usr/share/gconf/gnome.default.path", "WINDOWID"=>"23068726", "XDG_CONFIG_DIRS"=>"/etc/xdg/xdg-gnome:/etc/xdg", "ORBIT_SOCKETDIR"=>"/tmp/orbit-avdi", "XDG_DATA_DIRS"=>"/usr/share/gnome:/usr/local/share/:/usr/share/", "GNOME_DESKTOP_SESSION_ID"=>"this-is-deprecated", "DISPLAY"=>":0.0", "PWD"=>"/home/avdi/dev/greenletters/examples/cucumber", "GDMSESSION"=>"gnome", "GDM_KEYBOARD_LAYOUT"=>"us", "COLORFGBG"=>"15;0", "XAUTHORITY"=>"/var/run/gdm/auth-for-avdi-GRGq1x/database", "SSH_AGENT_PID"=>"2389", "XDG_SESSION_COOKIE"=>"95da948d870c084a39cf95e74bcf1d12-1279307523.573384-2005890633", "TERM"=>"xterm", "PROFILEHOME"=>"", "SESSION_MANAGER"=>"local/petronius:@/tmp/.ICE-unix/2348,unix/petronius:/tmp/.ICE-unix/2348", "DESKTOP_AUTOSTART_ID"=>"10957e6313bf75a5e0127930752431450800000023480004", "SSH_AUTH_SOCK"=>"/tmp/keyring-5DVyII/ssh", "KONSOLE_DBUS_SESSION"=>"/Sessions/4", "GNOME_KEYRING_CONTROL"=>"/tmp/keyring-5DVyII", "DESKTOP_SESSION"=>"gnome", "USERNAME"=>"avdi", "MANDATORY_PATH"=>"/usr/share/gconf/gnome.mandatory.path", "HOME"=>"/home/avdi", "GPG_AGENT_INFO"=>"/tmp/gpg-5xymDi/S.gpg-agent:2390:1", "SHELL"=>"/bin/zsh", "SHLVL"=>"1", "OLDPWD"=>"/home/avdi/dev/greenletters/examples", "EDITOR"=>"/home/avdi/share/bin/emacs-newwindow", "GEM_EDITOR"=>"/home/avdi/share/bin/emacs-newwindow", "ZSH"=>"/home/avdi/.oh-my-zsh", "ZSH_THEME"=>"robbyrussell", "LSCOLORS"=>"Gxfxcxdxbxegedabagacad", "GREP_OPTIONS"=>"--color=auto", "GREP_COLOR"=>"1;32", "PAGER"=>"less", "LC_CTYPE"=>"en_US.UTF-8", "rvm_path"=>"/home/avdi/.rvm", "rvm_rubies_path"=>"/home/avdi/.rvm/rubies", "rvm_scripts_path"=>"/home/avdi/.rvm/scripts", "rvm_archives_path"=>"/home/avdi/.rvm/archives", "rvm_src_path"=>"/home/avdi/.rvm/src", "rvm_log_path"=>"/home/avdi/.rvm/log", "rvm_bin_path"=>"/home/avdi/.rvm/bin", "rvm_gems_path"=>"/home/avdi/.rvm/gems", "rvm_config_path"=>"/home/avdi/.rvm/config", "rvm_tmp_path"=>"/home/avdi/.rvm/tmp", "rvm_hooks_path"=>"/home/avdi/.rvm/hooks", "rvm_gems_cache_path"=>"/home/avdi/.rvm/gems/cache", "rvm_gemset_separator"=>"@", "rvm_version"=>"0.1.28", "_"=>"/home/avdi/.gem/ruby/1.8/bin/cucumber"}
|
8
|
+
D, [2010-07-18T09:07:00.337560 #4796] DEBUG -- : spawned pid 4801
|
9
|
+
D, [2010-07-18T09:07:00.338511 #4796] DEBUG -- : added trigger on output matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./
|
10
|
+
D, [2010-07-18T09:07:00.338569 #4796] DEBUG -- : waiting for output matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./
|
11
|
+
D, [2010-07-18T09:07:00.338608 #4796] DEBUG -- : select()
|
12
|
+
D, [2010-07-18T09:07:00.338693 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
13
|
+
D, [2010-07-18T09:07:00.338747 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
|
14
|
+
D, [2010-07-18T09:07:00.338825 #4796] DEBUG -- :
|
15
|
+
<<
|
16
|
+
<< Welcome to Adventure!! Would you like instructions?
|
17
|
+
D, [2010-07-18T09:07:00.338863 #4796] DEBUG -- : read 56 bytes
|
18
|
+
D, [2010-07-18T09:07:00.338910 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
|
19
|
+
D, [2010-07-18T09:07:00.338966 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against "\r\nWelcome to Adventure!! Would you like instructions?\r\n"
|
20
|
+
D, [2010-07-18T09:07:00.339007 #4796] DEBUG -- : no match
|
21
|
+
D, [2010-07-18T09:07:00.339043 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
|
22
|
+
D, [2010-07-18T09:07:00.339077 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against "\r\nWelcome to Adventure!! Would you like instructions?\r\n"
|
23
|
+
D, [2010-07-18T09:07:00.339117 #4796] DEBUG -- : matched /Would\s+you\s+like\s+instructions\?/
|
24
|
+
D, [2010-07-18T09:07:00.339168 #4796] DEBUG -- : match trigger output matching /Would\s+you\s+like\s+instructions\?/
|
25
|
+
D, [2010-07-18T09:07:00.339209 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
|
26
|
+
D, [2010-07-18T09:07:00.339247 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against "\r\n"
|
27
|
+
D, [2010-07-18T09:07:00.339281 #4796] DEBUG -- : no match
|
28
|
+
D, [2010-07-18T09:07:00.339316 #4796] DEBUG -- : checking output against output matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./
|
29
|
+
D, [2010-07-18T09:07:00.339350 #4796] DEBUG -- : matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./ against "\r\n"
|
30
|
+
D, [2010-07-18T09:07:00.339380 #4796] DEBUG -- : no match
|
31
|
+
D, [2010-07-18T09:07:00.339414 #4796] DEBUG -- : select()
|
32
|
+
D, [2010-07-18T09:07:00.339455 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
33
|
+
D, [2010-07-18T09:07:00.339504 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
|
34
|
+
D, [2010-07-18T09:07:00.339563 #4796] DEBUG -- :
|
35
|
+
>> no
|
36
|
+
D, [2010-07-18T09:07:00.339599 #4796] DEBUG -- : wrote 3 bytes
|
37
|
+
D, [2010-07-18T09:07:00.339631 #4796] DEBUG -- : select()
|
38
|
+
D, [2010-07-18T09:07:00.339673 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
39
|
+
D, [2010-07-18T09:07:00.359024 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
|
40
|
+
D, [2010-07-18T09:07:00.359161 #4796] DEBUG -- :
|
41
|
+
<< no
|
42
|
+
<<
|
43
|
+
<< You are standing at the end of a road before a small brick building.
|
44
|
+
<< Around you is a forest. A small stream flows out of the building and
|
45
|
+
<< down a gully.
|
46
|
+
D, [2010-07-18T09:07:00.359200 #4796] DEBUG -- : read 162 bytes
|
47
|
+
D, [2010-07-18T09:07:00.359251 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
|
48
|
+
D, [2010-07-18T09:07:00.359299 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against "\r\nno\r\n\r\nYou are standing at the end of a road before a small brick building.\r\nAround you is a forest. A small stream flows out of the building and\r\ndown a gully.\r\n"
|
49
|
+
D, [2010-07-18T09:07:00.359334 #4796] DEBUG -- : no match
|
50
|
+
D, [2010-07-18T09:07:00.359368 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
|
51
|
+
D, [2010-07-18T09:07:00.359406 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against "\r\nno\r\n\r\nYou are standing at the end of a road before a small brick building.\r\nAround you is a forest. A small stream flows out of the building and\r\ndown a gully.\r\n"
|
52
|
+
D, [2010-07-18T09:07:00.359437 #4796] DEBUG -- : no match
|
53
|
+
D, [2010-07-18T09:07:00.359507 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
|
54
|
+
D, [2010-07-18T09:07:00.359548 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against "\r\nno\r\n\r\nYou are standing at the end of a road before a small brick building.\r\nAround you is a forest. A small stream flows out of the building and\r\ndown a gully.\r\n"
|
55
|
+
D, [2010-07-18T09:07:00.359585 #4796] DEBUG -- : no match
|
56
|
+
D, [2010-07-18T09:07:00.359621 #4796] DEBUG -- : checking output against output matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./
|
57
|
+
D, [2010-07-18T09:07:00.359664 #4796] DEBUG -- : matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./ against "\r\nno\r\n\r\nYou are standing at the end of a road before a small brick building.\r\nAround you is a forest. A small stream flows out of the building and\r\ndown a gully.\r\n"
|
58
|
+
D, [2010-07-18T09:07:00.359714 #4796] DEBUG -- : matched /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./
|
59
|
+
D, [2010-07-18T09:07:00.359754 #4796] DEBUG -- : match trigger output matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./
|
60
|
+
D, [2010-07-18T09:07:00.359790 #4796] DEBUG -- : unblocked
|
61
|
+
D, [2010-07-18T09:07:00.359829 #4796] DEBUG -- : trigger removed
|
62
|
+
D, [2010-07-18T09:07:00.361484 #4796] DEBUG -- : added trigger on output matching /You\s+are\s+inside\s+a\s+building,\s+a\s+well\s+house\s+for\s+a\s+large\s+spring\./
|
63
|
+
D, [2010-07-18T09:07:00.361546 #4796] DEBUG -- : waiting for output matching /You\s+are\s+inside\s+a\s+building,\s+a\s+well\s+house\s+for\s+a\s+large\s+spring\./
|
64
|
+
D, [2010-07-18T09:07:00.361588 #4796] DEBUG -- : select()
|
65
|
+
D, [2010-07-18T09:07:00.361658 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
66
|
+
D, [2010-07-18T09:07:00.361728 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
|
67
|
+
D, [2010-07-18T09:07:00.361820 #4796] DEBUG -- :
|
68
|
+
>> east
|
69
|
+
D, [2010-07-18T09:07:00.361878 #4796] DEBUG -- : wrote 5 bytes
|
70
|
+
D, [2010-07-18T09:07:00.361942 #4796] DEBUG -- : select()
|
71
|
+
D, [2010-07-18T09:07:00.362026 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
72
|
+
D, [2010-07-18T09:07:00.379006 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
|
73
|
+
D, [2010-07-18T09:07:00.379142 #4796] DEBUG -- :
|
74
|
+
<< east
|
75
|
+
<<
|
76
|
+
<< You are inside a building, a well house for a large spring.
|
77
|
+
<<
|
78
|
+
<< There are some keys on the ground here.
|
79
|
+
<<
|
80
|
+
<< There is a shiny brass lamp nearby.
|
81
|
+
<<
|
82
|
+
<< There is food here.
|
83
|
+
<<
|
84
|
+
<< There is a bottle of water here.
|
85
|
+
D, [2010-07-18T09:07:00.379182 #4796] DEBUG -- : read 210 bytes
|
86
|
+
D, [2010-07-18T09:07:00.379240 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
|
87
|
+
D, [2010-07-18T09:07:00.379288 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against "\r\neast\r\n\r\nYou are inside a building, a well house for a large spring.\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\n"
|
88
|
+
D, [2010-07-18T09:07:00.379321 #4796] DEBUG -- : no match
|
89
|
+
D, [2010-07-18T09:07:00.379355 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
|
90
|
+
D, [2010-07-18T09:07:00.379394 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against "\r\neast\r\n\r\nYou are inside a building, a well house for a large spring.\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\n"
|
91
|
+
D, [2010-07-18T09:07:00.379455 #4796] DEBUG -- : no match
|
92
|
+
D, [2010-07-18T09:07:00.379497 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
|
93
|
+
D, [2010-07-18T09:07:00.379549 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against "\r\neast\r\n\r\nYou are inside a building, a well house for a large spring.\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\n"
|
94
|
+
D, [2010-07-18T09:07:00.379594 #4796] DEBUG -- : no match
|
95
|
+
D, [2010-07-18T09:07:00.379640 #4796] DEBUG -- : checking output against output matching /You\s+are\s+inside\s+a\s+building,\s+a\s+well\s+house\s+for\s+a\s+large\s+spring\./
|
96
|
+
D, [2010-07-18T09:07:00.379699 #4796] DEBUG -- : matching /You\s+are\s+inside\s+a\s+building,\s+a\s+well\s+house\s+for\s+a\s+large\s+spring\./ against "\r\neast\r\n\r\nYou are inside a building, a well house for a large spring.\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\n"
|
97
|
+
D, [2010-07-18T09:07:00.379758 #4796] DEBUG -- : matched /You\s+are\s+inside\s+a\s+building,\s+a\s+well\s+house\s+for\s+a\s+large\s+spring\./
|
98
|
+
D, [2010-07-18T09:07:00.379810 #4796] DEBUG -- : match trigger output matching /You\s+are\s+inside\s+a\s+building,\s+a\s+well\s+house\s+for\s+a\s+large\s+spring\./
|
99
|
+
D, [2010-07-18T09:07:00.379858 #4796] DEBUG -- : unblocked
|
100
|
+
D, [2010-07-18T09:07:00.379910 #4796] DEBUG -- : trigger removed
|
101
|
+
D, [2010-07-18T09:07:00.381659 #4796] DEBUG -- : added trigger on output matching /You're\s+at\s+end\s+of\s+road\s+again\./
|
102
|
+
D, [2010-07-18T09:07:00.381736 #4796] DEBUG -- : waiting for output matching /You're\s+at\s+end\s+of\s+road\s+again\./
|
103
|
+
D, [2010-07-18T09:07:00.381794 #4796] DEBUG -- : select()
|
104
|
+
D, [2010-07-18T09:07:00.381854 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
105
|
+
D, [2010-07-18T09:07:00.381920 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
|
106
|
+
D, [2010-07-18T09:07:00.382004 #4796] DEBUG -- :
|
107
|
+
>> west
|
108
|
+
D, [2010-07-18T09:07:00.382053 #4796] DEBUG -- : wrote 5 bytes
|
109
|
+
D, [2010-07-18T09:07:00.382096 #4796] DEBUG -- : select()
|
110
|
+
D, [2010-07-18T09:07:00.382157 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
111
|
+
D, [2010-07-18T09:07:00.397861 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
|
112
|
+
D, [2010-07-18T09:07:00.398226 #4796] DEBUG -- :
|
113
|
+
<< west
|
114
|
+
<<
|
115
|
+
<< You're at end of road again.
|
116
|
+
D, [2010-07-18T09:07:00.398280 #4796] DEBUG -- : read 38 bytes
|
117
|
+
D, [2010-07-18T09:07:00.398344 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
|
118
|
+
D, [2010-07-18T09:07:00.398412 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against "\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\nwest\r\n\r\nYou're at end of road again.\r\n"
|
119
|
+
D, [2010-07-18T09:07:00.398458 #4796] DEBUG -- : no match
|
120
|
+
D, [2010-07-18T09:07:00.398504 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
|
121
|
+
D, [2010-07-18T09:07:00.398556 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against "\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\nwest\r\n\r\nYou're at end of road again.\r\n"
|
122
|
+
D, [2010-07-18T09:07:00.398598 #4796] DEBUG -- : no match
|
123
|
+
D, [2010-07-18T09:07:00.398643 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
|
124
|
+
D, [2010-07-18T09:07:00.398695 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against "\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\nwest\r\n\r\nYou're at end of road again.\r\n"
|
125
|
+
D, [2010-07-18T09:07:00.398777 #4796] DEBUG -- : no match
|
126
|
+
D, [2010-07-18T09:07:00.398828 #4796] DEBUG -- : checking output against output matching /You're\s+at\s+end\s+of\s+road\s+again\./
|
127
|
+
D, [2010-07-18T09:07:00.398885 #4796] DEBUG -- : matching /You're\s+at\s+end\s+of\s+road\s+again\./ against "\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\nwest\r\n\r\nYou're at end of road again.\r\n"
|
128
|
+
D, [2010-07-18T09:07:00.398959 #4796] DEBUG -- : matched /You're\s+at\s+end\s+of\s+road\s+again\./
|
129
|
+
D, [2010-07-18T09:07:00.399010 #4796] DEBUG -- : match trigger output matching /You're\s+at\s+end\s+of\s+road\s+again\./
|
130
|
+
D, [2010-07-18T09:07:00.399053 #4796] DEBUG -- : unblocked
|
131
|
+
D, [2010-07-18T09:07:00.399099 #4796] DEBUG -- : trigger removed
|
132
|
+
D, [2010-07-18T09:07:00.401301 #4796] DEBUG -- : added trigger on output matching /You\s+are\s+in\s+a\s+valley\s+in\s+the\s+forest\s+beside\s+a\s+stream\s+tumbling\s+along\s+a\s+rocky\s+bed\./
|
133
|
+
D, [2010-07-18T09:07:00.401391 #4796] DEBUG -- : waiting for output matching /You\s+are\s+in\s+a\s+valley\s+in\s+the\s+forest\s+beside\s+a\s+stream\s+tumbling\s+along\s+a\s+rocky\s+bed\./
|
134
|
+
D, [2010-07-18T09:07:00.401444 #4796] DEBUG -- : select()
|
135
|
+
D, [2010-07-18T09:07:00.401514 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
136
|
+
D, [2010-07-18T09:07:00.401581 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
|
137
|
+
D, [2010-07-18T09:07:00.401661 #4796] DEBUG -- :
|
138
|
+
>> south
|
139
|
+
D, [2010-07-18T09:07:00.401710 #4796] DEBUG -- : wrote 6 bytes
|
140
|
+
D, [2010-07-18T09:07:00.401754 #4796] DEBUG -- : select()
|
141
|
+
D, [2010-07-18T09:07:00.401810 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
142
|
+
D, [2010-07-18T09:07:00.418999 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
|
143
|
+
D, [2010-07-18T09:07:00.419139 #4796] DEBUG -- :
|
144
|
+
<< south
|
145
|
+
<<
|
146
|
+
<< You are in a valley in the forest beside a stream tumbling along a
|
147
|
+
<< rocky bed.
|
148
|
+
D, [2010-07-18T09:07:00.419189 #4796] DEBUG -- : read 89 bytes
|
149
|
+
D, [2010-07-18T09:07:00.419253 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
|
150
|
+
D, [2010-07-18T09:07:00.419311 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against "\r\nsouth\r\n\r\nYou are in a valley in the forest beside a stream tumbling along a\r\nrocky bed.\r\n"
|
151
|
+
D, [2010-07-18T09:07:00.419356 #4796] DEBUG -- : no match
|
152
|
+
D, [2010-07-18T09:07:00.419402 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
|
153
|
+
D, [2010-07-18T09:07:00.419449 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against "\r\nsouth\r\n\r\nYou are in a valley in the forest beside a stream tumbling along a\r\nrocky bed.\r\n"
|
154
|
+
D, [2010-07-18T09:07:00.419490 #4796] DEBUG -- : no match
|
155
|
+
D, [2010-07-18T09:07:00.419535 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
|
156
|
+
D, [2010-07-18T09:07:00.419575 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against "\r\nsouth\r\n\r\nYou are in a valley in the forest beside a stream tumbling along a\r\nrocky bed.\r\n"
|
157
|
+
D, [2010-07-18T09:07:00.419607 #4796] DEBUG -- : no match
|
158
|
+
D, [2010-07-18T09:07:00.419641 #4796] DEBUG -- : checking output against output matching /You\s+are\s+in\s+a\s+valley\s+in\s+the\s+forest\s+beside\s+a\s+stream\s+tumbling\s+along\s+a\s+rocky\s+bed\./
|
159
|
+
D, [2010-07-18T09:07:00.419680 #4796] DEBUG -- : matching /You\s+are\s+in\s+a\s+valley\s+in\s+the\s+forest\s+beside\s+a\s+stream\s+tumbling\s+along\s+a\s+rocky\s+bed\./ against "\r\nsouth\r\n\r\nYou are in a valley in the forest beside a stream tumbling along a\r\nrocky bed.\r\n"
|
160
|
+
D, [2010-07-18T09:07:00.419725 #4796] DEBUG -- : matched /You\s+are\s+in\s+a\s+valley\s+in\s+the\s+forest\s+beside\s+a\s+stream\s+tumbling\s+along\s+a\s+rocky\s+bed\./
|
161
|
+
D, [2010-07-18T09:07:00.419792 #4796] DEBUG -- : match trigger output matching /You\s+are\s+in\s+a\s+valley\s+in\s+the\s+forest\s+beside\s+a\s+stream\s+tumbling\s+along\s+a\s+rocky\s+bed\./
|
162
|
+
D, [2010-07-18T09:07:00.419828 #4796] DEBUG -- : unblocked
|
163
|
+
D, [2010-07-18T09:07:00.419862 #4796] DEBUG -- : trigger removed
|
164
|
+
D, [2010-07-18T09:07:00.462688 #4796] DEBUG -- : added trigger on exit with status 0
|
165
|
+
D, [2010-07-18T09:07:00.462995 #4796] DEBUG -- : waiting for exit with status 0
|
166
|
+
D, [2010-07-18T09:07:00.463056 #4796] DEBUG -- : select()
|
167
|
+
D, [2010-07-18T09:07:00.463119 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
168
|
+
D, [2010-07-18T09:07:00.463187 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
|
169
|
+
D, [2010-07-18T09:07:00.463264 #4796] DEBUG -- :
|
170
|
+
>> quit
|
171
|
+
D, [2010-07-18T09:07:00.463314 #4796] DEBUG -- : wrote 5 bytes
|
172
|
+
D, [2010-07-18T09:07:00.463357 #4796] DEBUG -- : select()
|
173
|
+
D, [2010-07-18T09:07:00.463412 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
174
|
+
D, [2010-07-18T09:07:00.479013 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
|
175
|
+
D, [2010-07-18T09:07:00.479151 #4796] DEBUG -- :
|
176
|
+
<< quit
|
177
|
+
<<
|
178
|
+
<< Do you really want to quit now?
|
179
|
+
D, [2010-07-18T09:07:00.479201 #4796] DEBUG -- : read 41 bytes
|
180
|
+
D, [2010-07-18T09:07:00.479267 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
|
181
|
+
D, [2010-07-18T09:07:00.479321 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against "\r\nquit\r\n\r\nDo you really want to quit now?\r\n"
|
182
|
+
D, [2010-07-18T09:07:00.479365 #4796] DEBUG -- : no match
|
183
|
+
D, [2010-07-18T09:07:00.479412 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
|
184
|
+
D, [2010-07-18T09:07:00.479458 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against "\r\nquit\r\n\r\nDo you really want to quit now?\r\n"
|
185
|
+
D, [2010-07-18T09:07:00.479500 #4796] DEBUG -- : no match
|
186
|
+
D, [2010-07-18T09:07:00.479546 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
|
187
|
+
D, [2010-07-18T09:07:00.479592 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against "\r\nquit\r\n\r\nDo you really want to quit now?\r\n"
|
188
|
+
D, [2010-07-18T09:07:00.479642 #4796] DEBUG -- : matched /Do\s+you\s+really\s+want\s+to\s+quit/
|
189
|
+
D, [2010-07-18T09:07:00.479712 #4796] DEBUG -- : match trigger output matching /Do\s+you\s+really\s+want\s+to\s+quit/
|
190
|
+
D, [2010-07-18T09:07:00.479767 #4796] DEBUG -- : select()
|
191
|
+
D, [2010-07-18T09:07:00.479831 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
192
|
+
D, [2010-07-18T09:07:00.479895 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
|
193
|
+
D, [2010-07-18T09:07:00.479968 #4796] DEBUG -- :
|
194
|
+
>> yes
|
195
|
+
D, [2010-07-18T09:07:00.480017 #4796] DEBUG -- : wrote 4 bytes
|
196
|
+
D, [2010-07-18T09:07:00.480060 #4796] DEBUG -- : select()
|
197
|
+
D, [2010-07-18T09:07:00.480115 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
198
|
+
D, [2010-07-18T09:07:00.499047 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
|
199
|
+
D, [2010-07-18T09:07:00.499209 #4796] DEBUG -- :
|
200
|
+
<< yes
|
201
|
+
<<
|
202
|
+
<< OK
|
203
|
+
<<
|
204
|
+
<<
|
205
|
+
<<
|
206
|
+
<< You scored 32 out of a possible 350 using 4 turns.
|
207
|
+
<<
|
208
|
+
<< You are obviously a rank amateur. Better luck next time.
|
209
|
+
<< To achieve the next higher rating, you need 4 more points.
|
210
|
+
<< __GREENLETTERS_PROCESS_ENDED__
|
211
|
+
D, [2010-07-18T09:07:00.499249 #4796] DEBUG -- : read 222 bytes
|
212
|
+
D, [2010-07-18T09:07:00.499298 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
|
213
|
+
D, [2010-07-18T09:07:00.499345 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against " now?\r\nyes\r\n\r\nOK\r\n\r\n\r\n\r\nYou scored 32 out of a possible 350 using 4 turns.\r\n\r\nYou are obviously a rank amateur. Better luck next time.\r\nTo achieve the next higher rating, you need 4 more points.\r\n__GREENLETTERS_PROCESS_ENDED__\r\n"
|
214
|
+
D, [2010-07-18T09:07:00.499382 #4796] DEBUG -- : matched /__GREENLETTERS_PROCESS_ENDED__/
|
215
|
+
D, [2010-07-18T09:07:00.499444 #4796] DEBUG -- : end marker found
|
216
|
+
D, [2010-07-18T09:07:00.499506 #4796] DEBUG -- : end marker expunged from output buffer
|
217
|
+
D, [2010-07-18T09:07:00.499540 #4796] DEBUG -- : acknowledging end marker
|
218
|
+
D, [2010-07-18T09:07:00.499582 #4796] DEBUG -- : match trigger output matching /__GREENLETTERS_PROCESS_ENDED__/
|
219
|
+
D, [2010-07-18T09:07:00.499622 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
|
220
|
+
D, [2010-07-18T09:07:00.499663 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against " now?\r\nyes\r\n\r\nOK\r\n\r\n\r\n\r\nYou scored 32 out of a possible 350 using 4 turns.\r\n\r\nYou are obviously a rank amateur. Better luck next time.\r\nTo achieve the next higher rating, you need 4 more points.\r\n"
|
221
|
+
D, [2010-07-18T09:07:00.499695 #4796] DEBUG -- : no match
|
222
|
+
D, [2010-07-18T09:07:00.499729 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
|
223
|
+
D, [2010-07-18T09:07:00.499783 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against " now?\r\nyes\r\n\r\nOK\r\n\r\n\r\n\r\nYou scored 32 out of a possible 350 using 4 turns.\r\n\r\nYou are obviously a rank amateur. Better luck next time.\r\nTo achieve the next higher rating, you need 4 more points.\r\n"
|
224
|
+
D, [2010-07-18T09:07:00.499828 #4796] DEBUG -- : no match
|
225
|
+
D, [2010-07-18T09:07:00.499865 #4796] DEBUG -- : flushing triggers matching Greenletters::OutputTrigger
|
226
|
+
D, [2010-07-18T09:07:00.499903 #4796] DEBUG -- : select()
|
227
|
+
D, [2010-07-18T09:07:00.499949 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
228
|
+
D, [2010-07-18T09:07:00.499996 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
|
229
|
+
D, [2010-07-18T09:07:00.500048 #4796] DEBUG -- :
|
230
|
+
|
231
|
+
D, [2010-07-18T09:07:00.500084 #4796] DEBUG -- : wrote 1 bytes
|
232
|
+
D, [2010-07-18T09:07:00.500116 #4796] DEBUG -- : select()
|
233
|
+
D, [2010-07-18T09:07:00.500175 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
234
|
+
D, [2010-07-18T09:07:00.509491 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
|
235
|
+
D, [2010-07-18T09:07:00.509618 #4796] DEBUG -- :
|
236
|
+
<<
|
237
|
+
D, [2010-07-18T09:07:00.509668 #4796] DEBUG -- : read 2 bytes
|
238
|
+
D, [2010-07-18T09:07:00.509729 #4796] DEBUG -- : flushing triggers matching Greenletters::OutputTrigger
|
239
|
+
D, [2010-07-18T09:07:00.509778 #4796] DEBUG -- : select()
|
240
|
+
D, [2010-07-18T09:07:00.509838 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
|
241
|
+
D, [2010-07-18T09:07:00.509898 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
|
242
|
+
D, [2010-07-18T09:07:00.510051 #4796] DEBUG -- : Errno::EIO caught
|
243
|
+
D, [2010-07-18T09:07:00.510106 #4796] DEBUG -- : waiting for child 4801 to die
|
244
|
+
D, [2010-07-18T09:07:00.523528 #4796] DEBUG -- : caught PTY::ChildExited
|
245
|
+
D, [2010-07-18T09:07:00.523800 #4796] DEBUG -- : collecting remaining output
|
246
|
+
D, [2010-07-18T09:07:00.523953 #4796] DEBUG -- : Input/output error - /dev/pts/7
|
247
|
+
D, [2010-07-18T09:07:00.524009 #4796] DEBUG -- : handling exit of process 4801
|
248
|
+
D, [2010-07-18T09:07:00.524070 #4796] DEBUG -- : checking exit against exit with status 0
|
249
|
+
D, [2010-07-18T09:07:00.524129 #4796] DEBUG -- : match trigger exit with status 0
|
250
|
+
D, [2010-07-18T09:07:00.524172 #4796] DEBUG -- : unblocked
|
251
|
+
D, [2010-07-18T09:07:00.524216 #4796] DEBUG -- : trigger removed
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'cucumber'
|
2
|
+
|
3
|
+
module Greenletters
|
4
|
+
module CucumberHelpers
|
5
|
+
def greenletters_prepare_entry(text)
|
6
|
+
text.chomp + "\n"
|
7
|
+
end
|
8
|
+
def greenletters_massage_pattern(text)
|
9
|
+
Regexp.new(Regexp.escape(text.strip.tr_s(" \r\n\t", " ")).gsub('\ ', '\s+'))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
World(Greenletters::CucumberHelpers)
|
15
|
+
|
16
|
+
Before do
|
17
|
+
@greenletters_process_table = Hash.new {|h,k|
|
18
|
+
raise "No such process defined: #{k}"
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
Given /^process activity is logged to "([^\"]*)"$/ do |filename|
|
23
|
+
logger = ::Logger.new(open(filename, 'w+'))
|
24
|
+
#logger.level = ::Logger::INFO
|
25
|
+
logger.level = ::Logger::DEBUG
|
26
|
+
@greenletters_process_log = logger
|
27
|
+
end
|
28
|
+
|
29
|
+
Given /^a process (?:"([^\"]*)" )?from command "([^\"]*)"$/ do |name, command|
|
30
|
+
name ||= "default"
|
31
|
+
options = {
|
32
|
+
}
|
33
|
+
options[:logger] = @greenletters_process_log if @greenletters_process_log
|
34
|
+
@greenletters_process_table[name] = Greenletters::Process.new(command, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
Given /^I reply "([^\"]*)" to output "([^\"]*)"(?: from process "([^\"]*)")?$/ do
|
38
|
+
|reply, pattern, name|
|
39
|
+
name ||= "default"
|
40
|
+
pattern = greenletters_massage_pattern(pattern)
|
41
|
+
@greenletters_process_table[name].on(:output, pattern) do |process, match_data|
|
42
|
+
process << greenletters_prepare_entry(reply)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
When /^I execute the process(?: "([^\"]*)")?$/ do |name|
|
47
|
+
name ||= "default"
|
48
|
+
@greenletters_process_table[name].start!
|
49
|
+
end
|
50
|
+
|
51
|
+
Then /^I should see the following output(?: from process "([^\"]*)")?:$/ do
|
52
|
+
|name, pattern|
|
53
|
+
name ||= "default"
|
54
|
+
pattern = greenletters_massage_pattern(pattern)
|
55
|
+
@greenletters_process_table[name].wait_for(:output, pattern)
|
56
|
+
end
|
57
|
+
|
58
|
+
When /^I enter "([^\"]*)"(?: into process "([^\"]*)")?$/ do
|
59
|
+
|input, name|
|
60
|
+
name ||= "default"
|
61
|
+
@greenletters_process_table[name] << greenletters_prepare_entry(input)
|
62
|
+
end
|
63
|
+
|
64
|
+
Then /^the process(?: "([^\"]*)")? should exit succesfully$/ do |name|
|
65
|
+
name ||= "default"
|
66
|
+
@greenletters_process_table[name].wait_for(:exit, 0)
|
67
|
+
end
|
68
|
+
|
data/lib/greenletters.rb
ADDED
@@ -0,0 +1,566 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'pty'
|
3
|
+
require 'forwardable'
|
4
|
+
require 'stringio'
|
5
|
+
require 'shellwords'
|
6
|
+
require 'rbconfig'
|
7
|
+
require 'strscan'
|
8
|
+
|
9
|
+
# A better expect.rb
|
10
|
+
#
|
11
|
+
# Implementation note: because of the way PTY is implemented in Ruby, it is
|
12
|
+
# possible when executing a quick non-interactive command for PTY::ChildExited
|
13
|
+
# to be raised before ever getting input/output handles to the child
|
14
|
+
# process. Without an output handle, it's not possible to read any output the
|
15
|
+
# process produced. This is obviously undesirable, especially since when a
|
16
|
+
# command is unexpectedly quick and noninteractive it's usually because there
|
17
|
+
# was an error and you really want to be able to see what the problem was.
|
18
|
+
#
|
19
|
+
# Greenletters' solution to this problem is to wrap every command in a short
|
20
|
+
# script. The script executes the passed command and on termination, outputs an
|
21
|
+
# easily recognizable marker string. Then it waits for acknowledgment (a
|
22
|
+
# newline) before exiting. When Greenletters sees the marker string in the
|
23
|
+
# output, it automatically performs the acknowledgement and allows the child
|
24
|
+
# process to finish. By forcing the child process to wait for acknowledgement,
|
25
|
+
# we guarantee that the child will never exit before we have a chance to look at
|
26
|
+
# the output.
|
27
|
+
module Greenletters
|
28
|
+
|
29
|
+
# :stopdoc:
|
30
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
31
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
32
|
+
# :startdoc:
|
33
|
+
|
34
|
+
# Returns the version string for the library.
|
35
|
+
#
|
36
|
+
def self.version
|
37
|
+
@version ||= File.read(path('version.txt')).strip
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the library path for the module. If any arguments are given,
|
41
|
+
# they will be joined to the end of the libray path using
|
42
|
+
# <tt>File.join</tt>.
|
43
|
+
#
|
44
|
+
def self.libpath( *args, &block )
|
45
|
+
rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
46
|
+
if block
|
47
|
+
begin
|
48
|
+
$LOAD_PATH.unshift LIBPATH
|
49
|
+
rv = block.call
|
50
|
+
ensure
|
51
|
+
$LOAD_PATH.shift
|
52
|
+
end
|
53
|
+
end
|
54
|
+
return rv
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the lpath for the module. If any arguments are given,
|
58
|
+
# they will be joined to the end of the path using
|
59
|
+
# <tt>File.join</tt>.
|
60
|
+
#
|
61
|
+
def self.path( *args, &block )
|
62
|
+
rv = args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
63
|
+
if block
|
64
|
+
begin
|
65
|
+
$LOAD_PATH.unshift PATH
|
66
|
+
rv = block.call
|
67
|
+
ensure
|
68
|
+
$LOAD_PATH.shift
|
69
|
+
end
|
70
|
+
end
|
71
|
+
return rv
|
72
|
+
end
|
73
|
+
|
74
|
+
# Utility method used to require all files ending in .rb that lie in the
|
75
|
+
# directory below this file that has the same name as the filename passed
|
76
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
77
|
+
# the _filename_ does not have to be equivalent to the directory.
|
78
|
+
#
|
79
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
80
|
+
dir ||= ::File.basename(fname, '.*')
|
81
|
+
search_me = ::File.expand_path(
|
82
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
83
|
+
|
84
|
+
Dir.glob(search_me).reject{|fn| fn =~ /cucumber_steps.rb$/}.sort.each {|rb| require rb}
|
85
|
+
end
|
86
|
+
|
87
|
+
LogicError = Class.new(::Exception)
|
88
|
+
SystemError = Class.new(RuntimeError)
|
89
|
+
TimeoutError = Class.new(SystemError)
|
90
|
+
ClientError = Class.new(RuntimeError)
|
91
|
+
StateError = Class.new(ClientError)
|
92
|
+
|
93
|
+
# This class offers a pass-through << operator and saves the most recent 256
|
94
|
+
# bytes which have passed through.
|
95
|
+
class TranscriptHistoryBuffer
|
96
|
+
attr_reader :buffer
|
97
|
+
|
98
|
+
def initialize(transcript)
|
99
|
+
@buffer = String.new
|
100
|
+
@transcript = transcript
|
101
|
+
end
|
102
|
+
|
103
|
+
def <<(output)
|
104
|
+
@buffer << output
|
105
|
+
@transcript << output
|
106
|
+
length = [@buffer.length, 256].min
|
107
|
+
@buffer = @buffer[-length, length]
|
108
|
+
self
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def Trigger(event, *args, &block)
|
113
|
+
klass = trigger_class_for_event(event)
|
114
|
+
klass.new(*args, &block)
|
115
|
+
end
|
116
|
+
|
117
|
+
def trigger_class_for_event(event)
|
118
|
+
::Greenletters.const_get("#{event.to_s.capitalize}Trigger")
|
119
|
+
end
|
120
|
+
|
121
|
+
class Trigger
|
122
|
+
attr_accessor :time_to_live
|
123
|
+
attr_accessor :exclusive
|
124
|
+
attr_accessor :logger
|
125
|
+
attr_accessor :interruption
|
126
|
+
|
127
|
+
alias_method :exclusive?, :exclusive
|
128
|
+
|
129
|
+
def initialize(options={}, &block)
|
130
|
+
@block = block || lambda{}
|
131
|
+
@exclusive = options.fetch(:exclusive) { false }
|
132
|
+
@logger = ::Logger.new($stdout)
|
133
|
+
@interruption = :none
|
134
|
+
end
|
135
|
+
|
136
|
+
def call(process)
|
137
|
+
@block.call(process)
|
138
|
+
true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class OutputTrigger < Trigger
|
143
|
+
def initialize(pattern=//, options={}, &block)
|
144
|
+
super(options, &block)
|
145
|
+
@pattern = pattern
|
146
|
+
end
|
147
|
+
|
148
|
+
def to_s
|
149
|
+
"output matching #{@pattern.inspect}"
|
150
|
+
end
|
151
|
+
|
152
|
+
def call(process)
|
153
|
+
scanner = process.output_buffer
|
154
|
+
@logger.debug "matching #{@pattern.inspect} against #{scanner.rest.inspect}"
|
155
|
+
if scanner.scan_until(@pattern)
|
156
|
+
@logger.debug "matched #{@pattern.inspect}"
|
157
|
+
@block.call(process, scanner)
|
158
|
+
true
|
159
|
+
else
|
160
|
+
false
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class TimeoutTrigger < Trigger
|
166
|
+
def to_s
|
167
|
+
"timeout"
|
168
|
+
end
|
169
|
+
|
170
|
+
def call(process)
|
171
|
+
@block.call(process, process.blocker)
|
172
|
+
true
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class ExitTrigger < Trigger
|
177
|
+
attr_reader :pattern
|
178
|
+
|
179
|
+
def initialize(pattern=0, options={}, &block)
|
180
|
+
super(options, &block)
|
181
|
+
@pattern = pattern
|
182
|
+
end
|
183
|
+
|
184
|
+
def call(process)
|
185
|
+
if pattern === process.status.exitstatus
|
186
|
+
@block.call(process, process.status)
|
187
|
+
true
|
188
|
+
else
|
189
|
+
false
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def to_s
|
194
|
+
"exit with status #{pattern}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class UnsatisfiedTrigger < Trigger
|
199
|
+
def to_s
|
200
|
+
"unsatisfied wait"
|
201
|
+
end
|
202
|
+
|
203
|
+
def call(process)
|
204
|
+
@block.call(process, process.interruption, process.blocker)
|
205
|
+
true
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
class Process
|
210
|
+
END_MARKER = '__GREENLETTERS_PROCESS_ENDED__'
|
211
|
+
|
212
|
+
# Shamelessly stolen from Rake
|
213
|
+
RUBY_EXT =
|
214
|
+
((Config::CONFIG['ruby_install_name'] =~ /\.(com|cmd|exe|bat|rb|sh)$/) ?
|
215
|
+
"" :
|
216
|
+
Config::CONFIG['EXEEXT'])
|
217
|
+
RUBY = File.join(
|
218
|
+
Config::CONFIG['bindir'],
|
219
|
+
Config::CONFIG['ruby_install_name'] + RUBY_EXT).
|
220
|
+
sub(/.*\s.*/m, '"\&"')
|
221
|
+
|
222
|
+
extend Forwardable
|
223
|
+
include ::Greenletters
|
224
|
+
|
225
|
+
attr_reader :command # Command to run in a subshell
|
226
|
+
attr_accessor :blocker # The Trigger currently being waited for, if any
|
227
|
+
attr_reader :input_buffer # Input waiting to be written to process
|
228
|
+
attr_reader :output_buffer # Output ready to be read from process
|
229
|
+
attr_reader :status # :not_started -> :running -> :ended -> :exited
|
230
|
+
attr_reader :cwd # Working directory for the command
|
231
|
+
|
232
|
+
def_delegators :input_buffer, :puts, :write, :print, :printf, :<<
|
233
|
+
def_delegators :output_buffer, :read, :readpartial, :read_nonblock, :gets,
|
234
|
+
:getline
|
235
|
+
def_delegators :blocker, :interruption, :interruption=
|
236
|
+
|
237
|
+
def initialize(*args)
|
238
|
+
options = if args.last.is_a?(Hash) then args.pop else {} end
|
239
|
+
@command = args
|
240
|
+
@triggers = []
|
241
|
+
@blocker = nil
|
242
|
+
@input_buffer = StringIO.new
|
243
|
+
@output_buffer = StringScanner.new("")
|
244
|
+
@env = options.fetch(:env) {{}}
|
245
|
+
@cwd = options.fetch(:cwd) {Dir.pwd}
|
246
|
+
@logger = options.fetch(:logger) {
|
247
|
+
l = ::Logger.new($stdout)
|
248
|
+
l.level = ::Logger::WARN
|
249
|
+
l
|
250
|
+
}
|
251
|
+
@state = :not_started
|
252
|
+
@shell = options.fetch(:shell) { '/bin/sh' }
|
253
|
+
@transcript = options.fetch(:transcript) {
|
254
|
+
t = Object.new
|
255
|
+
def t.<<(*)
|
256
|
+
# NOOP
|
257
|
+
end
|
258
|
+
t
|
259
|
+
}
|
260
|
+
@history = TranscriptHistoryBuffer.new(@transcript)
|
261
|
+
end
|
262
|
+
|
263
|
+
def on(event, *args, &block)
|
264
|
+
t = add_trigger(event, *args, &block)
|
265
|
+
end
|
266
|
+
|
267
|
+
def wait_for(event, *args, &block)
|
268
|
+
raise "Already waiting for #{blocker}" if blocker
|
269
|
+
t = add_blocking_trigger(event, *args, &block)
|
270
|
+
process_events
|
271
|
+
rescue
|
272
|
+
unblock!
|
273
|
+
triggers.delete(t)
|
274
|
+
raise
|
275
|
+
end
|
276
|
+
|
277
|
+
def add_trigger(event, *args, &block)
|
278
|
+
t = Trigger(event, *args, &block)
|
279
|
+
t.logger = @logger
|
280
|
+
triggers << t
|
281
|
+
@logger.debug "added trigger on #{t}"
|
282
|
+
t
|
283
|
+
end
|
284
|
+
|
285
|
+
def prepend_trigger(event, *args, &block)
|
286
|
+
t = Trigger(event, *args, &block)
|
287
|
+
t.logger = @logger
|
288
|
+
triggers.unshift(t)
|
289
|
+
@logger.debug "prepended trigger on #{t}"
|
290
|
+
t
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
def add_blocking_trigger(event, *args, &block)
|
295
|
+
t = add_trigger(event, *args, &block)
|
296
|
+
t.time_to_live = 1
|
297
|
+
@logger.debug "waiting for #{t}"
|
298
|
+
self.blocker = t
|
299
|
+
t
|
300
|
+
end
|
301
|
+
|
302
|
+
def start!
|
303
|
+
raise StateError, "Already started!" unless not_started?
|
304
|
+
@logger.debug "installing end marker handler for #{END_MARKER}"
|
305
|
+
prepend_trigger(:output, /#{END_MARKER}/, :exclusive => false, :time_to_live => 1) do |process, data|
|
306
|
+
handle_end_marker
|
307
|
+
end
|
308
|
+
handle_child_exit do
|
309
|
+
cmd = wrapped_command
|
310
|
+
@logger.debug "executing #{cmd.join(' ')}"
|
311
|
+
merge_environment(@env) do
|
312
|
+
@logger.debug "command environment:\n#{ENV.inspect}"
|
313
|
+
@output, @input, @pid = PTY.spawn(*cmd)
|
314
|
+
end
|
315
|
+
@state = :running
|
316
|
+
@logger.debug "spawned pid #{@pid}"
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def flush_output_buffer!
|
321
|
+
@logger.debug "flushing output buffer"
|
322
|
+
@output_buffer.terminate
|
323
|
+
end
|
324
|
+
|
325
|
+
def alive?
|
326
|
+
::Process.kill(0, @pid)
|
327
|
+
true
|
328
|
+
rescue Errno::ESRCH, Errno::ENOENT
|
329
|
+
false
|
330
|
+
end
|
331
|
+
|
332
|
+
def blocked?
|
333
|
+
@blocker
|
334
|
+
end
|
335
|
+
|
336
|
+
def running?
|
337
|
+
@state == :running
|
338
|
+
end
|
339
|
+
|
340
|
+
def not_started?
|
341
|
+
@state == :not_started
|
342
|
+
end
|
343
|
+
|
344
|
+
def exited?
|
345
|
+
@state == :exited
|
346
|
+
end
|
347
|
+
|
348
|
+
# Have we seen the end marker yet?
|
349
|
+
def ended?
|
350
|
+
@state == :ended
|
351
|
+
end
|
352
|
+
|
353
|
+
private
|
354
|
+
|
355
|
+
attr_reader :triggers
|
356
|
+
|
357
|
+
def wrapped_command
|
358
|
+
[RUBY,
|
359
|
+
'-C', cwd,
|
360
|
+
'-e', "system(*#{command.inspect})",
|
361
|
+
'-e', "puts(#{END_MARKER.inspect})",
|
362
|
+
'-e', "gets",
|
363
|
+
'-e', "exit $?.exitstatus"
|
364
|
+
]
|
365
|
+
end
|
366
|
+
|
367
|
+
def process_events
|
368
|
+
raise StateError, "Process not started!" if not_started?
|
369
|
+
handle_child_exit do
|
370
|
+
while blocked?
|
371
|
+
@logger.debug "select()"
|
372
|
+
input_handles = input_buffer.string.empty? ? [] : [@input]
|
373
|
+
output_handles = [@output]
|
374
|
+
error_handles = [@input, @output]
|
375
|
+
@logger.debug "select() on #{[output_handles, input_handles, error_handles].inspect}"
|
376
|
+
ready_handles = IO.select(
|
377
|
+
output_handles, input_handles, error_handles, 1.0)
|
378
|
+
if ready_handles.nil?
|
379
|
+
process_timeout
|
380
|
+
else
|
381
|
+
ready_outputs, ready_inputs, ready_errors = *ready_handles
|
382
|
+
ready_errors.each do |handle| process_error(handle) end
|
383
|
+
ready_outputs.each do |handle| process_output(handle) end
|
384
|
+
ready_inputs.each do |handle| process_input(handle) end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
def process_input(handle)
|
391
|
+
@logger.debug "input ready #{handle.inspect}"
|
392
|
+
handle.write(input_buffer.string)
|
393
|
+
@logger.debug format_output_for_log(input_buffer.string)
|
394
|
+
@logger.debug "wrote #{input_buffer.string.size} bytes"
|
395
|
+
input_buffer.string = ""
|
396
|
+
end
|
397
|
+
|
398
|
+
def process_output(handle)
|
399
|
+
@logger.debug "output ready #{handle.inspect}"
|
400
|
+
data = handle.readpartial(1024)
|
401
|
+
output_buffer << data
|
402
|
+
@history << data
|
403
|
+
@logger.debug format_input_for_log(data)
|
404
|
+
@logger.debug "read #{data.size} bytes"
|
405
|
+
handle_triggers(:output)
|
406
|
+
flush_triggers!(OutputTrigger) if ended?
|
407
|
+
# flush_output_buffer! unless ended?
|
408
|
+
end
|
409
|
+
|
410
|
+
def collect_remaining_output
|
411
|
+
if @output.nil?
|
412
|
+
@logger.debug "unable to collect output for missing output handle"
|
413
|
+
return
|
414
|
+
end
|
415
|
+
@logger.debug "collecting remaining output"
|
416
|
+
while data = @output.read_nonblock(1024)
|
417
|
+
output_buffer << data
|
418
|
+
@logger.debug "read #{data.size} bytes"
|
419
|
+
end
|
420
|
+
rescue EOFError, Errno::EIO => error
|
421
|
+
@logger.debug error.message
|
422
|
+
end
|
423
|
+
|
424
|
+
def wait_for_child_to_die
|
425
|
+
# Soon we should get a PTY::ChildExited
|
426
|
+
while running? || ended?
|
427
|
+
@logger.debug "waiting for child #{@pid} to die"
|
428
|
+
sleep 0.1
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
def process_error(handle)
|
433
|
+
@logger.debug "error on #{handle.inspect}"
|
434
|
+
raise NotImplementedError, "process_error()"
|
435
|
+
end
|
436
|
+
|
437
|
+
def process_timeout
|
438
|
+
@logger.debug "timeout"
|
439
|
+
handle_triggers(:timeout)
|
440
|
+
process_interruption(:timeout)
|
441
|
+
end
|
442
|
+
|
443
|
+
def handle_exit(status=status_from_waitpid)
|
444
|
+
return false if exited?
|
445
|
+
@logger.debug "handling exit of process #{@pid}"
|
446
|
+
@state = :exited
|
447
|
+
@status = status
|
448
|
+
handle_triggers(:exit)
|
449
|
+
if status == 0
|
450
|
+
process_interruption(:exit)
|
451
|
+
else
|
452
|
+
process_interruption(:abnormal_exit)
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
def status_from_waitpid
|
457
|
+
@logger.debug "waiting for exist status of #{@pid}"
|
458
|
+
::Process.waitpid2(@pid)[1]
|
459
|
+
end
|
460
|
+
|
461
|
+
def handle_triggers(event)
|
462
|
+
klass = trigger_class_for_event(event)
|
463
|
+
matches = 0
|
464
|
+
triggers.grep(klass).each do |t|
|
465
|
+
@logger.debug "checking #{event} against #{t}"
|
466
|
+
if t.call(self) # match
|
467
|
+
matches += 1
|
468
|
+
@logger.debug "match trigger #{t}"
|
469
|
+
if blocker.equal?(t)
|
470
|
+
unblock!
|
471
|
+
end
|
472
|
+
if t.time_to_live
|
473
|
+
if t.time_to_live > 1
|
474
|
+
t.time_to_live -= 1
|
475
|
+
@logger.debug "trigger ttl reduced to #{t.time_to_live}"
|
476
|
+
else
|
477
|
+
triggers.delete(t)
|
478
|
+
@logger.debug "trigger removed"
|
479
|
+
end
|
480
|
+
end
|
481
|
+
break if t.exclusive?
|
482
|
+
else
|
483
|
+
@logger.debug "no match"
|
484
|
+
end
|
485
|
+
end
|
486
|
+
matches > 0
|
487
|
+
end
|
488
|
+
|
489
|
+
def handle_end_marker
|
490
|
+
return false if ended?
|
491
|
+
@logger.debug "end marker found"
|
492
|
+
output_buffer.string.gsub!(/#{END_MARKER}\s*/, '')
|
493
|
+
output_buffer.unscan
|
494
|
+
@state = :ended
|
495
|
+
@logger.debug "end marker expunged from output buffer"
|
496
|
+
@logger.debug "acknowledging end marker"
|
497
|
+
self.puts
|
498
|
+
end
|
499
|
+
|
500
|
+
def unblock!
|
501
|
+
@logger.debug "unblocked"
|
502
|
+
triggers.delete(@blocker)
|
503
|
+
@blocker = nil
|
504
|
+
end
|
505
|
+
|
506
|
+
def handle_child_exit
|
507
|
+
handle_eio do
|
508
|
+
yield
|
509
|
+
end
|
510
|
+
rescue PTY::ChildExited => error
|
511
|
+
@logger.debug "caught PTY::ChildExited"
|
512
|
+
collect_remaining_output
|
513
|
+
handle_exit(error.status)
|
514
|
+
end
|
515
|
+
|
516
|
+
def handle_eio
|
517
|
+
yield
|
518
|
+
rescue Errno::EIO => error
|
519
|
+
@logger.debug "Errno::EIO caught"
|
520
|
+
wait_for_child_to_die
|
521
|
+
end
|
522
|
+
|
523
|
+
def flush_triggers!(kind)
|
524
|
+
@logger.debug "flushing triggers matching #{kind}"
|
525
|
+
triggers.delete_if{|t| kind === t}
|
526
|
+
end
|
527
|
+
|
528
|
+
def merge_environment(new_env)
|
529
|
+
old_env = new_env.inject({}) do |old, (key, value)|
|
530
|
+
old[key] = ENV[key]
|
531
|
+
ENV[key] = value
|
532
|
+
old
|
533
|
+
end
|
534
|
+
yield
|
535
|
+
ensure
|
536
|
+
old_env.each_pair do |key, value|
|
537
|
+
if value.nil? then ENV.delete(key) else ENV[key] = value end
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
def process_interruption(reason)
|
542
|
+
if blocked?
|
543
|
+
self.interruption = reason
|
544
|
+
unless handle_triggers(:unsatisfied)
|
545
|
+
raise SystemError,
|
546
|
+
"Interrupted (#{reason}) while waiting for #{blocker}.\n" \
|
547
|
+
"Recent activity:\n" +
|
548
|
+
@history.buffer
|
549
|
+
end
|
550
|
+
unblock!
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
def format_output_for_log(text)
|
555
|
+
"\n" + text.split("\n").map{|l| ">> #{l}"}.join("\n")
|
556
|
+
end
|
557
|
+
|
558
|
+
def format_input_for_log(text)
|
559
|
+
"\n" + text.split("\n").map{|l| "<< #{l}"}.join("\n")
|
560
|
+
end
|
561
|
+
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
Greenletters.require_all_libs_relative_to(__FILE__)
|
566
|
+
|
data/script/console
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
require File.expand_path(
|
3
|
+
File.join(File.dirname(__FILE__), %w[.. lib greenletters]))
|
4
|
+
|
5
|
+
Spec::Runner.configure do |config|
|
6
|
+
# == Mock Framework
|
7
|
+
#
|
8
|
+
# RSpec uses it's own mocking framework by default. If you prefer to
|
9
|
+
# use mocha, flexmock or RR, uncomment the appropriate line:
|
10
|
+
#
|
11
|
+
# config.mock_with :mocha
|
12
|
+
# config.mock_with :flexmock
|
13
|
+
# config.mock_with :rr
|
14
|
+
end
|
15
|
+
|
File without changes
|
data/version.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: greenletters
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Avdi Grimm
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-07-19 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: bones
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 3
|
29
|
+
- 4
|
30
|
+
- 7
|
31
|
+
version: 3.4.7
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
description: " Greenletterrs is a console automation framework, similar to the classic\n utility Expect. You give it a command to execute, and tell it which outputs\n or events to expect and how to respond to them.\n\n Greenletters also includes a set of Cucumber steps which simplify the task\n of spcifying interactive command-line applications.\n"
|
35
|
+
email: avdi@avdi.org
|
36
|
+
executables:
|
37
|
+
- greenletters
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- History.txt
|
42
|
+
- bin/greenletters
|
43
|
+
- version.txt
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- History.txt
|
47
|
+
- README.org
|
48
|
+
- Rakefile
|
49
|
+
- bin/greenletters
|
50
|
+
- examples/adventure.rb
|
51
|
+
- examples/cucumber/adventure.feature
|
52
|
+
- examples/cucumber/greenletters.log
|
53
|
+
- examples/cucumber/support/env.rb
|
54
|
+
- lib/greenletters.rb
|
55
|
+
- lib/greenletters/cucumber_steps.rb
|
56
|
+
- script/console
|
57
|
+
- spec/greenletters_spec.rb
|
58
|
+
- spec/spec_helper.rb
|
59
|
+
- test/test_greenletters.rb
|
60
|
+
- version.txt
|
61
|
+
has_rdoc: true
|
62
|
+
homepage: http://github.com/avdi/greenletters
|
63
|
+
licenses: []
|
64
|
+
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options:
|
67
|
+
- --main
|
68
|
+
- README.org
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
segments:
|
76
|
+
- 0
|
77
|
+
version: "0"
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
requirements: []
|
86
|
+
|
87
|
+
rubyforge_project: greenletters
|
88
|
+
rubygems_version: 1.3.6
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: A Ruby console automation framework a la Expect
|
92
|
+
test_files:
|
93
|
+
- test/test_greenletters.rb
|