mockingbird 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +102 -0
- data/examples/examples.md +35 -0
- data/examples/overview.rb +14 -0
- data/examples/overview_output.txt +5 -0
- data/examples/reconnects.rb +24 -0
- data/examples/status_and_headers.rb +10 -0
- data/lib/mockingbird.rb +61 -0
- data/lib/mockingbird/commands.rb +135 -0
- data/lib/mockingbird/connection_script.rb +19 -0
- data/lib/mockingbird/script.rb +100 -0
- data/lib/mockingbird/script_runner.rb +41 -0
- data/lib/mockingbird/server.rb +83 -0
- data/lib/mockingbird/version.rb +5 -0
- metadata +96 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Hayes Davis
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
Mockingbird
|
2
|
+
===========
|
3
|
+
Mockingbird emulates the Twitter Streaming API using a simple script-like
|
4
|
+
configuration that makes it easy to test code that connects to the Streaming
|
5
|
+
API. Mockingbird can be used to simulate bad data, unexpected status codes,
|
6
|
+
hard disconnects, etc.
|
7
|
+
|
8
|
+
Mockingbird uses eventmachine to run as an actual streaming http server so
|
9
|
+
it's a drop-in replacement for code that reads from the streaming api.
|
10
|
+
Simply change the host and port your code is connecting to from Twitter to
|
11
|
+
a mockingbird instance.
|
12
|
+
|
13
|
+
Since mockingbird is designed for testing, it includes a simple
|
14
|
+
Mockingbird#setup and Mockingbird#teardown interface that makes it easy to
|
15
|
+
configure and spawn a server for testing purposes during unit tests.
|
16
|
+
|
17
|
+
Configuration Quickstart
|
18
|
+
------------------------
|
19
|
+
Mockingbird uses a simple script-like configuration API for telling a
|
20
|
+
mockingbird server what you want it to do. Here's a simple example:
|
21
|
+
|
22
|
+
Mockingbird.setup(:port=>8080) do
|
23
|
+
send '{"foo":"bar"}'
|
24
|
+
wait 1
|
25
|
+
5.times do
|
26
|
+
send '{"foo2":"bar2"}'
|
27
|
+
end
|
28
|
+
pipe "some/file.txt", :wait=>1
|
29
|
+
close
|
30
|
+
end
|
31
|
+
|
32
|
+
Here's what this does in plain english:
|
33
|
+
|
34
|
+
* Tells the server to listen on port 8080 and do the stuff in the block on
|
35
|
+
each connection.
|
36
|
+
* On a connection, send '{"foo":"bar"}' down to the client
|
37
|
+
* Wait 1 second
|
38
|
+
* Then send '{"foo2":"bar2"}' down to the client 5 times
|
39
|
+
* Then send each line from some/file.txt to the clien, waiting 1 second in
|
40
|
+
between sends
|
41
|
+
* Close the connection
|
42
|
+
|
43
|
+
Mockingbird assigns each conection an incrementing id. This means you can
|
44
|
+
specify behavior over multiple connections with different connections doing
|
45
|
+
different things. This is handy for testing reconnection code:
|
46
|
+
|
47
|
+
Mockingbird.setup(:port=>8080) do
|
48
|
+
on_connection(1) do
|
49
|
+
disconnect!
|
50
|
+
end
|
51
|
+
|
52
|
+
on_connection(2..5) do
|
53
|
+
wait(0.5)
|
54
|
+
close
|
55
|
+
end
|
56
|
+
|
57
|
+
on_connection('*') do
|
58
|
+
100.times do
|
59
|
+
send '{"foo":"bar"}'
|
60
|
+
end
|
61
|
+
close
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
Again, in plain english:
|
66
|
+
|
67
|
+
* On the first connection, we do a hard disconnect (just drop the connection)
|
68
|
+
* On connections 2-5, wait a half second, then close the connection nicely
|
69
|
+
* On all subsequent connections ("*"), send down 100 foo bars and close
|
70
|
+
|
71
|
+
See the docs on Mockingbird::Script for all the available configuration options.
|
72
|
+
Also, see the examples directory for more usage.
|
73
|
+
|
74
|
+
Using in Tests
|
75
|
+
--------------
|
76
|
+
The basic pattern for using Mockingbird in your unit tests is to simply call
|
77
|
+
Mockingbird#setup and Mockingbird#teardown at appropriate times. This will
|
78
|
+
setup a mockingbird server in a separate process and then kill it when you're
|
79
|
+
done. Make sure to *always* call Mockingbird#teardown. This is easy in test/unit
|
80
|
+
if you're actually calling these methods in setup and teardown. If you need to
|
81
|
+
setup and teardown a server in a test method, do the following:
|
82
|
+
|
83
|
+
def test_something
|
84
|
+
Mockingbird.setup(:port=>NNNN) do
|
85
|
+
# config here
|
86
|
+
end
|
87
|
+
# do tests
|
88
|
+
ensure
|
89
|
+
Mockingbird.teardown
|
90
|
+
end
|
91
|
+
|
92
|
+
Limitations
|
93
|
+
-----------
|
94
|
+
* SSL is not supported.
|
95
|
+
* Since connection ids are incrementing with each connection you won't be able
|
96
|
+
able to easily target specific connections if you have multiple clients
|
97
|
+
connecting at once to the mockingbird server. It's generally recommended that
|
98
|
+
you have a single client connecting to mockingbird serially during a single
|
99
|
+
test run. Doing otherwise would probably be confusing anyway.
|
100
|
+
* The server does not even pay attention to your actual request, it will just
|
101
|
+
always respond with the defined configuration script regardless of what you
|
102
|
+
send on connection.
|
@@ -0,0 +1,35 @@
|
|
1
|
+
How to run examples
|
2
|
+
===================
|
3
|
+
It's easiest to do this if you have curl. From the root directory of this
|
4
|
+
project run the following
|
5
|
+
|
6
|
+
$ ruby examples/EXAMPLE_FILE.rb
|
7
|
+
Waiting for Mockingbird to start...
|
8
|
+
Mockingbird is mocking you on 0.0.0.0:8080 (pid=50990)
|
9
|
+
$ curl -v http://localhost:8080
|
10
|
+
|
11
|
+
Depending on the example file and your version of curl, you'll see something
|
12
|
+
similar to this:
|
13
|
+
* About to connect() to 0.0.0.0 port 8080 (#0)
|
14
|
+
* Trying 0.0.0.0... connected
|
15
|
+
* Connected to 0.0.0.0 (0.0.0.0) port 8080 (#0)
|
16
|
+
> GET / HTTP/1.1
|
17
|
+
> User-Agent: curl/7.19.6 (i386-apple-darwin9.7.0) libcurl/7.19.6 zlib/1.2.3
|
18
|
+
> Host: 0.0.0.0:8080
|
19
|
+
> Accept: */*
|
20
|
+
>
|
21
|
+
< HTTP/1.1 200 OK
|
22
|
+
< Transfer-Encoding: chunked
|
23
|
+
< Content-Type: application/json
|
24
|
+
< Server: Mockingbird
|
25
|
+
<
|
26
|
+
* Connection #0 to host 0.0.0.0 left intact
|
27
|
+
* Closing connection #0
|
28
|
+
|
29
|
+
When you're done you need to kill the mockingbird server which is running in
|
30
|
+
its own process. You can grep it like below or use the pid printed when you
|
31
|
+
ran the example file.
|
32
|
+
|
33
|
+
$ ps | grep mockingbird
|
34
|
+
50990 ttys006 0:00.33 mockingbird:0.0.0.0:8080
|
35
|
+
$ kill -9 50990
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
require 'mockingbird'
|
5
|
+
|
6
|
+
Mockingbird.setup(:port=>8080) do
|
7
|
+
send '{"foo":"bar"}'
|
8
|
+
wait 0.5
|
9
|
+
5.times do |n|
|
10
|
+
send %Q({"foo":"bar#{n}"})
|
11
|
+
end
|
12
|
+
pipe "#{File.dirname(__FILE__)}/overview_output.txt", :wait=>1
|
13
|
+
close
|
14
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
require 'mockingbird'
|
5
|
+
|
6
|
+
# Note: you'll need to run curl several times to see the full effect of this one
|
7
|
+
|
8
|
+
Mockingbird.setup(:port=>8080) do
|
9
|
+
on_connection(1) do
|
10
|
+
disconnect!
|
11
|
+
end
|
12
|
+
|
13
|
+
on_connection(2..5) do
|
14
|
+
wait(0.5)
|
15
|
+
close
|
16
|
+
end
|
17
|
+
|
18
|
+
on_connection('*') do
|
19
|
+
100.times do
|
20
|
+
send '{"foo":"bar"}'
|
21
|
+
end
|
22
|
+
close
|
23
|
+
end
|
24
|
+
end
|
data/lib/mockingbird.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# External dependencies
|
2
|
+
require 'rubygems'
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
# Mockingbird code
|
6
|
+
require 'mockingbird/version'
|
7
|
+
require 'mockingbird/commands'
|
8
|
+
require 'mockingbird/connection_script'
|
9
|
+
require 'mockingbird/script'
|
10
|
+
require 'mockingbird/script_runner'
|
11
|
+
require 'mockingbird/server'
|
12
|
+
|
13
|
+
module Mockingbird
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
# Convenience method for starting a mockingbird server during a test.
|
18
|
+
# This will be most users' primary interface to mockingbird. The mockingbird
|
19
|
+
# server will be forked as a separate process. Ensure that your test code
|
20
|
+
# always calls teardown at some point after setup, or the separate process
|
21
|
+
# will not be terminated.
|
22
|
+
#
|
23
|
+
# Options are
|
24
|
+
# :host - The host to listen on. 0.0.0.0 by default
|
25
|
+
# :port - The port to listen on. 4879 by default
|
26
|
+
#
|
27
|
+
# The block is a Mockingbird configuration (see README).
|
28
|
+
def setup(opts={},&block)
|
29
|
+
Server.configure(&block)
|
30
|
+
@pid = fork do
|
31
|
+
opts = {:host=>'0.0.0.0',:port=>4879}.merge(opts)
|
32
|
+
$0 = "mockingbird:#{opts[:host]}:#{opts[:port]}"
|
33
|
+
Server.start!(opts)
|
34
|
+
end
|
35
|
+
Process.detach(@pid)
|
36
|
+
puts "Waiting for Mockingbird to start..."
|
37
|
+
sleep(1) # Necessary to make sure the forked proc is up and running
|
38
|
+
@pid
|
39
|
+
end
|
40
|
+
|
41
|
+
# Terminates the mockingbird server created by a call to setup. Make sure
|
42
|
+
# to always pair this call with setup to ensure that mockingbird server
|
43
|
+
# processes don't linger. If you're using test/unit, the recommended
|
44
|
+
# pattern is to actually call setup and teardown here during the setup and
|
45
|
+
# teardown phase of your unit tests. Otherwise, use the following pattern:
|
46
|
+
#
|
47
|
+
# def test_something
|
48
|
+
# Mockingbird.setup(:port=>NNNN) do
|
49
|
+
# # config here
|
50
|
+
# end
|
51
|
+
# # do tests
|
52
|
+
# ensure
|
53
|
+
# Mockingbird.teardown
|
54
|
+
# end
|
55
|
+
def teardown
|
56
|
+
Process.kill('KILL',@pid)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Mockingbird
|
2
|
+
module Commands
|
3
|
+
class Command
|
4
|
+
|
5
|
+
attr_accessor :next_command, :callback
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
self.callback = block
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(conn)
|
12
|
+
callback.call(conn) if callback
|
13
|
+
advance(conn)
|
14
|
+
end
|
15
|
+
|
16
|
+
def advance(conn)
|
17
|
+
next_command.run(conn) if next_command
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class Send < Command
|
23
|
+
|
24
|
+
def initialize(data=nil,&block)
|
25
|
+
@data = data || block
|
26
|
+
end
|
27
|
+
|
28
|
+
def run(conn)
|
29
|
+
to_send = data
|
30
|
+
conn.send_chunk(to_send)
|
31
|
+
advance(conn)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def data
|
36
|
+
if @data.respond_to?(:call)
|
37
|
+
@data.call
|
38
|
+
else
|
39
|
+
@data
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
class Disconnect < Command
|
46
|
+
|
47
|
+
def run(conn)
|
48
|
+
conn.close_connection
|
49
|
+
advance(conn)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
class Wait < Command
|
55
|
+
|
56
|
+
def initialize(time=nil,&block)
|
57
|
+
@time = time || block
|
58
|
+
end
|
59
|
+
|
60
|
+
def run(conn)
|
61
|
+
EM.add_timer(wait_time) do
|
62
|
+
advance(conn)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def wait_time
|
68
|
+
if @time.respond_to?(:call)
|
69
|
+
@time.call
|
70
|
+
else
|
71
|
+
@time
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
class Close < Command
|
78
|
+
|
79
|
+
def run(conn)
|
80
|
+
conn.send_terminal_chunk
|
81
|
+
EM.add_timer(0.1) do
|
82
|
+
conn.close_connection
|
83
|
+
end
|
84
|
+
advance(conn)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
class Quit < Command
|
90
|
+
def run(conn)
|
91
|
+
EM.add_timer(0.5) do
|
92
|
+
EM.stop
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Pipe < Command
|
98
|
+
|
99
|
+
def initialize(string_or_io,delay=nil)
|
100
|
+
if string_or_io.kind_of?(String)
|
101
|
+
@io = File.open(string_or_io,'r')
|
102
|
+
else
|
103
|
+
@io = string_or_io
|
104
|
+
end
|
105
|
+
@delay = delay
|
106
|
+
end
|
107
|
+
|
108
|
+
def run(conn)
|
109
|
+
unless @io.eof?
|
110
|
+
chunk = @io.readline.chomp
|
111
|
+
conn.send_chunk(chunk)
|
112
|
+
if delay
|
113
|
+
EM.add_timer(delay) { run(conn) }
|
114
|
+
else
|
115
|
+
EM.schedule { run(conn) }
|
116
|
+
end
|
117
|
+
else
|
118
|
+
# Reset for future calls
|
119
|
+
@io.rewind
|
120
|
+
advance(conn)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def delay
|
126
|
+
if @delay.respond_to?(:call)
|
127
|
+
@delay.call
|
128
|
+
else
|
129
|
+
@delay
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Mockingbird
|
2
|
+
|
3
|
+
class ConnectionScript
|
4
|
+
|
5
|
+
attr_accessor :status, :headers, :body
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
self.body = Commands::Command.new
|
9
|
+
@last_command = body
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_command(command=nil)
|
13
|
+
@last_command.next_command = command
|
14
|
+
@last_command = command
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Mockingbird
|
2
|
+
class Script
|
3
|
+
|
4
|
+
def initialize(&block)
|
5
|
+
@connections = []
|
6
|
+
@default_connection = ConnectionScript.new
|
7
|
+
instance_eval(&block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def for_connection(id)
|
11
|
+
match = @connections.find do |selector, *|
|
12
|
+
case selector
|
13
|
+
when Range then selector.include?(id)
|
14
|
+
when Numeric then selector == id
|
15
|
+
when Proc then selector.call(id)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
match ? match.last : @default_connection
|
19
|
+
end
|
20
|
+
|
21
|
+
# Configuration API
|
22
|
+
|
23
|
+
# Specifies behavior to run for specific connections based on the id number
|
24
|
+
# assigned to that connection. Connection ids are 1-based.
|
25
|
+
#
|
26
|
+
# The selector can be any of the following:
|
27
|
+
# Number - Run the code on that connection id
|
28
|
+
# Range - Run the code for a connection id in that range
|
29
|
+
# Proc - Call the proc with the id and run if it matches
|
30
|
+
# '*' - Run this code for any connection that doesn't match others
|
31
|
+
def on_connection(selector=nil,&block)
|
32
|
+
if selector.nil? || selector == '*'
|
33
|
+
instance_eval(&block)
|
34
|
+
else
|
35
|
+
@current_connection = ConnectionScript.new
|
36
|
+
instance_eval(&block)
|
37
|
+
@connections << [selector,@current_connection]
|
38
|
+
@current_connection = nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Send an HTTP status code to the client. e.g. a 403 or 404
|
43
|
+
def status(code, message="")
|
44
|
+
current_connection.status = [code, message]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Send the hash of headers to the client.
|
48
|
+
def headers(hash)
|
49
|
+
current_connection.headers = hash
|
50
|
+
end
|
51
|
+
|
52
|
+
# Send some text down to the client. If a block is specified that block
|
53
|
+
# will be called on each connection to determine what text to send. This
|
54
|
+
# permits sending randomized data.
|
55
|
+
def send(data=nil,&block)
|
56
|
+
add_command(Commands::Send.new(data,&block))
|
57
|
+
end
|
58
|
+
|
59
|
+
# Wait a certain number of seconds. Fractional seconds are allowed. If a
|
60
|
+
# block is specified, it will be called on each attempt. This permits things
|
61
|
+
# like adding randomized waits.
|
62
|
+
def wait(time=nil,&block)
|
63
|
+
add_command(Commands::Wait.new(time,&block))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Perform a hard disconnect by just closing the connection
|
67
|
+
def disconnect!
|
68
|
+
add_command(Commands::Disconnect.new)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Do a clean close
|
72
|
+
def close
|
73
|
+
add_command(Commands::Close.new)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Exit the server entirely
|
77
|
+
def quit
|
78
|
+
add_command(Commands::Quit.new)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Send the lines from string_or_io down one at a time. The :wait option
|
82
|
+
# can be used to specify a configurable delay between lines.
|
83
|
+
def pipe(string_or_io,opts={})
|
84
|
+
add_command(Commands::Pipe.new(string_or_io,opts[:wait]))
|
85
|
+
end
|
86
|
+
|
87
|
+
# Not really part of the public API but users could use this to
|
88
|
+
# implement their own fancy command
|
89
|
+
def add_command(command=nil,&block)
|
90
|
+
command = Commands::Command.new(&block) if block_given?
|
91
|
+
current_connection.add_command(command)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
def current_connection
|
96
|
+
@current_connection || @default_connection
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Mockingbird
|
2
|
+
|
3
|
+
class ScriptRunner
|
4
|
+
|
5
|
+
attr_accessor :conn, :script
|
6
|
+
|
7
|
+
def initialize(conn,script)
|
8
|
+
self.conn = conn
|
9
|
+
self.script = script
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
send_status
|
14
|
+
send_headers
|
15
|
+
send_body
|
16
|
+
end
|
17
|
+
|
18
|
+
def send_status
|
19
|
+
code, message = (script.status || [200,"OK"])
|
20
|
+
conn.send_status(code,message)
|
21
|
+
end
|
22
|
+
|
23
|
+
def send_headers
|
24
|
+
headers = {
|
25
|
+
"Transfer-Encoding"=>"chunked",
|
26
|
+
"Content-Type"=>"application/json",
|
27
|
+
"Server"=>"Mockingbird"
|
28
|
+
}.merge(script.headers||{})
|
29
|
+
headers.each do |name, value|
|
30
|
+
conn.send_header(name, value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def send_body
|
35
|
+
conn.start_body
|
36
|
+
script.body.run(conn)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Mockingbird
|
2
|
+
|
3
|
+
# A very simple eventmachine-based streaming server. Before you use a server
|
4
|
+
# it must be configured with configuration block:
|
5
|
+
#
|
6
|
+
# Mockingbird::Server.configure do
|
7
|
+
# # config goes here, see README for scripting
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# Once configured, a server may be started using
|
11
|
+
#
|
12
|
+
# start!(:host=>'0.0.0.0',:port=>NNN)
|
13
|
+
#
|
14
|
+
# If you're using Mockingbird directly from test (test/unit, etc), it's
|
15
|
+
# recommended that you use the simpler convenience interface defined on
|
16
|
+
# the Mockingbird module.
|
17
|
+
module Server
|
18
|
+
|
19
|
+
class << self
|
20
|
+
|
21
|
+
def start!(opts={})
|
22
|
+
opts = {:host=>'0.0.0.0',:port=>8080}.merge(opts)
|
23
|
+
host = opts[:host]
|
24
|
+
port = opts[:port]
|
25
|
+
EventMachine::run do
|
26
|
+
puts "Mockingbird is mocking you on #{host}:#{port} (pid=#{$$})"
|
27
|
+
EventMachine::start_server host, port, self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def configure(&block)
|
32
|
+
@script = Script.new(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def script
|
36
|
+
@script
|
37
|
+
end
|
38
|
+
|
39
|
+
def new_connection_id
|
40
|
+
@connection_id ||= 0
|
41
|
+
@connection_id += 1
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def receive_data(data)
|
47
|
+
conn_id = new_connection_id
|
48
|
+
runner = ScriptRunner.new(self,script.for_connection(conn_id))
|
49
|
+
runner.run
|
50
|
+
end
|
51
|
+
|
52
|
+
def new_connection_id
|
53
|
+
Mockingbird::Server.new_connection_id
|
54
|
+
end
|
55
|
+
|
56
|
+
def script
|
57
|
+
Mockingbird::Server.script
|
58
|
+
end
|
59
|
+
|
60
|
+
def send_status(code=200,text="OK")
|
61
|
+
send_data "HTTP/1.1 #{code} #{text}\r\n"
|
62
|
+
end
|
63
|
+
|
64
|
+
def send_header(name,value)
|
65
|
+
send_data "#{name}: #{value}\r\n"
|
66
|
+
end
|
67
|
+
|
68
|
+
def start_body
|
69
|
+
send_data "\r\n"
|
70
|
+
end
|
71
|
+
|
72
|
+
def send_chunk(chunk)
|
73
|
+
res = %Q(#{chunk}\r\n)
|
74
|
+
send_data "#{res.length.to_s(16)}\r\n"
|
75
|
+
send_data "#{res}\r\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
def send_terminal_chunk
|
79
|
+
send_data "0\r\n\r\n"
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mockingbird
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Hayes Davis
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-31 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: eventmachine
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 47
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 12
|
33
|
+
- 0
|
34
|
+
version: 0.12.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: " Mockingbird emulates the Twitter Streaming API using a simple script-like \n configuration that makes it easy to test code that connects to the Streaming \n API. Mockingbird can be used to simulate bad data, unexpected status codes, \n hard disconnects, etc.\n \n Mockingbird uses eventmachine to run as an actual streaming http server so\n it's a drop-in replacement for code that reads from the streaming api. \n Simply change the host and port your code is connecting to from Twitter to \n a running Mockingbird.\n"
|
38
|
+
email: hayes@appozite.com
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files:
|
44
|
+
- LICENSE
|
45
|
+
- README.md
|
46
|
+
files:
|
47
|
+
- README.md
|
48
|
+
- lib/mockingbird/commands.rb
|
49
|
+
- lib/mockingbird/connection_script.rb
|
50
|
+
- lib/mockingbird/script.rb
|
51
|
+
- lib/mockingbird/script_runner.rb
|
52
|
+
- lib/mockingbird/server.rb
|
53
|
+
- lib/mockingbird/version.rb
|
54
|
+
- lib/mockingbird.rb
|
55
|
+
- examples/examples.md
|
56
|
+
- examples/overview.rb
|
57
|
+
- examples/overview_output.txt
|
58
|
+
- examples/reconnects.rb
|
59
|
+
- examples/status_and_headers.rb
|
60
|
+
- LICENSE
|
61
|
+
has_rdoc: true
|
62
|
+
homepage: http://github.com/hayesdavis/mockingbird
|
63
|
+
licenses: []
|
64
|
+
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 3
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
requirements: []
|
89
|
+
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.3.7
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Mockingbird is a mock server for testing with the Twitter Streaming API.
|
95
|
+
test_files: []
|
96
|
+
|