greenletters 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|