bane 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +8 -0
- data/TODO +13 -0
- data/bin/bane +17 -0
- data/examples/simple_port_and_class_as_constant.rb +9 -0
- data/examples/simple_port_and_class_as_string.rb +7 -0
- data/examples/specify_behavior_options.rb +16 -0
- data/examples/specify_ports.rb +15 -0
- data/lib/bane.rb +8 -0
- data/lib/bane/behaviors.rb +151 -0
- data/lib/bane/compatibility.rb +20 -0
- data/lib/bane/configuration.rb +50 -0
- data/lib/bane/configuration_parser.rb +74 -0
- data/lib/bane/delegating_gserver.rb +42 -0
- data/lib/bane/launcher.rb +25 -0
- data/lib/bane/naive_http_response.rb +46 -0
- data/lib/bane/service_registry.rb +17 -0
- data/test/bane/behaviors_test.rb +137 -0
- data/test/bane/configuration_parser_test.rb +124 -0
- data/test/bane/configuration_test.rb +52 -0
- data/test/bane/delegating_gserver_test.rb +56 -0
- data/test/bane/integration_test.rb +67 -0
- data/test/bane/launcher_test.rb +16 -0
- data/test/bane/naive_http_response_test.rb +69 -0
- data/test/bane/service_registry_test.rb +20 -0
- data/test/test_helper.rb +9 -0
- metadata +88 -0
data/Rakefile
ADDED
data/TODO
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
- Remove some duplication / clarify the ConfigurationParser class as needed
|
2
|
+
- Dynamically create new behaviors like "SlowResponseForEachLine" by using Module.const_missing
|
3
|
+
- Use ActiveSupport or create simple extensions for time durations (seconds, minutes, etc.)
|
4
|
+
- Write the bad TCP/IP behaviors - using something like C/C++.
|
5
|
+
- Write the remaining bad HTTP behaviors. In addition, we may want to replace the NaiveHttpResponse with something
|
6
|
+
from the standard Ruby library, so that there's less code in this project, and so we know that we're
|
7
|
+
following the HTTP protocol.
|
8
|
+
- Log every request to the server/behavior, in addition to open, close. For this to work, it would have to be the
|
9
|
+
behavior's responsibility, since GServer#serve gets called only once for the lifetime of the connection.
|
10
|
+
|
11
|
+
Ideas:
|
12
|
+
- Make the Launcher use a Factory to create the server given a dependency, rather than depending directly on the
|
13
|
+
DelegatingGServer class. This would make it possible to swap the base server implementation more easily.
|
data/bin/bane
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
4
|
+
require 'bane'
|
5
|
+
|
6
|
+
if ARGV.empty?
|
7
|
+
puts "Usage: bane port_number <servers>"
|
8
|
+
puts
|
9
|
+
puts "All behaviors:"
|
10
|
+
behavior_names = Bane::ServiceRegistry.all_servers.map(&:simple_name)
|
11
|
+
behavior_names.sort.each { |behavior| puts " - #{behavior}" }
|
12
|
+
else
|
13
|
+
launcher = Bane::Launcher.new(Configuration(*ARGV))
|
14
|
+
launcher.start
|
15
|
+
launcher.join
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'bane'
|
3
|
+
|
4
|
+
include Bane
|
5
|
+
include Behaviors
|
6
|
+
|
7
|
+
launcher = Launcher.new(Configuration(
|
8
|
+
10256 => {:behavior => CloseAfterPause, :duration => 3},
|
9
|
+
10593 => {:behavior => FixedResponse, :message => "Hey!"},
|
10
|
+
10689 => {:behavior => SlowResponse, :message => "Custom message", :pause_duration => 15},
|
11
|
+
11239 => CloseAfterPause # Use the defaults for this behavior, don't need a Hash
|
12
|
+
)
|
13
|
+
)
|
14
|
+
launcher.start
|
15
|
+
launcher.join
|
16
|
+
# runs until interrupt...
|
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'bane'
|
3
|
+
|
4
|
+
include Bane
|
5
|
+
include Behaviors
|
6
|
+
|
7
|
+
launcher = Launcher.new(Configuration(
|
8
|
+
10256 => CloseAfterPause,
|
9
|
+
10689 => CloseAfterPause, # severs may be repeated
|
10
|
+
11999 => CloseImmediately
|
11
|
+
)
|
12
|
+
)
|
13
|
+
launcher.start
|
14
|
+
launcher.join
|
15
|
+
# runs until interrupt...
|
data/lib/bane.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
module Bane
|
2
|
+
|
3
|
+
module Behaviors
|
4
|
+
|
5
|
+
class BasicBehavior
|
6
|
+
def self.inherited(clazz)
|
7
|
+
ServiceRegistry.register(clazz)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.simple_name
|
11
|
+
self.name.split("::").last
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# This module can be used to wrap another behavior with
|
16
|
+
# a "while(io.gets)" loop, which reads a line from the input and
|
17
|
+
# then performs the given behavior.
|
18
|
+
module ForEachLine
|
19
|
+
def serve(io, options)
|
20
|
+
while (io.gets)
|
21
|
+
super(io, options)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Closes the connection immediately after a connection is made.
|
27
|
+
class CloseImmediately < BasicBehavior
|
28
|
+
def serve(io, options)
|
29
|
+
# do nothing
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Accepts a connection, pauses a fixed duration, then closes the connection.
|
34
|
+
#
|
35
|
+
# Options:
|
36
|
+
# - duration: The number of seconds to wait before disconnect. Default: 30
|
37
|
+
class CloseAfterPause < BasicBehavior
|
38
|
+
def serve(io, options)
|
39
|
+
options = {:duration => 30}.merge(options)
|
40
|
+
|
41
|
+
sleep(options[:duration])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Sends a static response.
|
46
|
+
#
|
47
|
+
# Options:
|
48
|
+
# - message: The response message to send. Default: "Hello, world!"
|
49
|
+
class FixedResponse < BasicBehavior
|
50
|
+
def serve(io, options)
|
51
|
+
options = {:message => "Hello, world!"}.merge(options)
|
52
|
+
|
53
|
+
io.write options[:message]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class FixedResponseForEachLine < FixedResponse
|
58
|
+
include ForEachLine
|
59
|
+
end
|
60
|
+
|
61
|
+
# Sends a random response.
|
62
|
+
class RandomResponse < BasicBehavior
|
63
|
+
def serve(io, options)
|
64
|
+
io.write random_string
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def random_string
|
69
|
+
(1..rand(26)+1).map { |i| ('a'..'z').to_a[rand(26)] }.join
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
class RandomResponseForEachLine < RandomResponse
|
75
|
+
include ForEachLine
|
76
|
+
end
|
77
|
+
|
78
|
+
# Sends a fixed response chacter-by-character, pausing in between each character.
|
79
|
+
#
|
80
|
+
# Options:
|
81
|
+
# - message: The response to send. Default: "Hello, world!"
|
82
|
+
# - pause_duration: The number of seconds to pause between each character. Default: 10 seconds
|
83
|
+
class SlowResponse < BasicBehavior
|
84
|
+
def serve(io, options)
|
85
|
+
options = {:message => "Hello, world!", :pause_duration => 10}.merge(options)
|
86
|
+
message = options[:message]
|
87
|
+
pause_duration = options[:pause_duration]
|
88
|
+
|
89
|
+
message.each_char do |char|
|
90
|
+
io.write char
|
91
|
+
sleep pause_duration
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class SlowResponseForEachLine < SlowResponse
|
97
|
+
include ForEachLine
|
98
|
+
end
|
99
|
+
|
100
|
+
# Accepts a connection and never sends a byte of data. The connection is
|
101
|
+
# left open indefinitely.
|
102
|
+
class NeverRespond < BasicBehavior
|
103
|
+
def serve(io, options)
|
104
|
+
loop { sleep 1 }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Sends a large response. Response consists of a repeated 'x' character.
|
109
|
+
#
|
110
|
+
# Options
|
111
|
+
# - length: The size in bytes of the response to send. Default: 1,000,000 bytes
|
112
|
+
class DelugeResponse < BasicBehavior
|
113
|
+
def serve(io, options)
|
114
|
+
options = {:length => 1_000_000}.merge(options)
|
115
|
+
length = options[:length]
|
116
|
+
|
117
|
+
length.times { io.write('x') }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class DelugeResponseForEachLine < DelugeResponse
|
122
|
+
include ForEachLine
|
123
|
+
end
|
124
|
+
|
125
|
+
# Sends an HTTP 401 response (Unauthorized) for every request. This
|
126
|
+
# attempts to mimic an HTTP server by reading a line (the request)
|
127
|
+
# and then sending the response. This behavior responds to all
|
128
|
+
# incoming request URLs on the running port.
|
129
|
+
class HttpRefuseAllCredentials < BasicBehavior
|
130
|
+
UNAUTHORIZED_RESPONSE_BODY = <<EOF
|
131
|
+
<!DOCTYPE html>
|
132
|
+
<html>
|
133
|
+
<head>
|
134
|
+
<title>Bane Server</title>
|
135
|
+
</head>
|
136
|
+
<body>
|
137
|
+
<h1>Unauthorized</h1>
|
138
|
+
</body>
|
139
|
+
</html>
|
140
|
+
EOF
|
141
|
+
|
142
|
+
def serve(io, options)
|
143
|
+
io.gets # Read the request before responding
|
144
|
+
response = NaiveHttpResponse.new(401, "Unauthorized", "text/html", UNAUTHORIZED_RESPONSE_BODY)
|
145
|
+
io.write(response.to_s)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class String
|
2
|
+
unless "".respond_to?(:each_char)
|
3
|
+
# copied from jcode in Ruby 1.8.6
|
4
|
+
def each_char
|
5
|
+
if block_given?
|
6
|
+
scan(/./m) do |x|
|
7
|
+
yield x
|
8
|
+
end
|
9
|
+
else
|
10
|
+
scan(/./m)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
unless "".respond_to?(:lines)
|
16
|
+
require "enumerator"
|
17
|
+
|
18
|
+
alias_method :lines, :to_a
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Bane
|
2
|
+
|
3
|
+
class Configuration
|
4
|
+
|
5
|
+
def initialize(configurations)
|
6
|
+
@configurations = configurations
|
7
|
+
end
|
8
|
+
|
9
|
+
def start(logger)
|
10
|
+
@configurations.map do |config|
|
11
|
+
config.start(logger)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ConfigurationRecord
|
16
|
+
|
17
|
+
def initialize(port, behavior, options = {})
|
18
|
+
@port = port
|
19
|
+
@behavior = behavior
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
def start(logger)
|
24
|
+
new_server = DelegatingGServer.new(@port, @behavior.new, @options, logger)
|
25
|
+
new_server.start
|
26
|
+
new_server
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class ConfigurationError < RuntimeError; end
|
33
|
+
|
34
|
+
class UnknownBehaviorError < RuntimeError
|
35
|
+
def initialize(name)
|
36
|
+
super "Unknown behavior: #{name}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
# Helper method to easily create configuration.
|
43
|
+
#
|
44
|
+
# This should likely take the constructor block from the Configuration class
|
45
|
+
# and then we can simplify this class so it's not so big.
|
46
|
+
module Kernel
|
47
|
+
def Configuration(*args)
|
48
|
+
Bane::Configuration.new(Bane::ConfigurationParser.new(*args).configurations)
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Bane
|
2
|
+
|
3
|
+
class ConfigurationParser
|
4
|
+
|
5
|
+
attr_reader :configurations
|
6
|
+
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
@configurations = []
|
10
|
+
|
11
|
+
@configurations = case args[0]
|
12
|
+
when String, Integer
|
13
|
+
map_integer_and_behavior_arguments(*args)
|
14
|
+
when Hash
|
15
|
+
map_hash_arguments(args[0])
|
16
|
+
else
|
17
|
+
raise ConfigurationError, "Unknown configuration arguments #{args.inspect}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def map_integer_and_behavior_arguments(*args)
|
24
|
+
port = Integer(args.shift)
|
25
|
+
|
26
|
+
behavior_classes = args
|
27
|
+
behavior_classes = ServiceRegistry.all_servers if behavior_classes.empty?
|
28
|
+
|
29
|
+
setup_linear_ports(port, behavior_classes)
|
30
|
+
end
|
31
|
+
|
32
|
+
def setup_linear_ports(port, behavior_classes)
|
33
|
+
behavior_classes.each_with_index do |behavior, index|
|
34
|
+
@configurations << Bane::Configuration::ConfigurationRecord.new(port + index, find(behavior))
|
35
|
+
end
|
36
|
+
@configurations
|
37
|
+
end
|
38
|
+
|
39
|
+
def find(behavior)
|
40
|
+
case behavior
|
41
|
+
when String
|
42
|
+
raise UnknownBehaviorError.new(behavior) unless Behaviors.const_defined?(behavior.to_sym)
|
43
|
+
Behaviors.const_get(behavior)
|
44
|
+
when Module
|
45
|
+
behavior
|
46
|
+
else
|
47
|
+
raise UnknownBehaviorError.new(behavior)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def map_hash_arguments(options)
|
52
|
+
options.each_pair do |key, value|
|
53
|
+
@configurations << extract_configuration_from(key, value)
|
54
|
+
end
|
55
|
+
@configurations
|
56
|
+
end
|
57
|
+
|
58
|
+
def extract_configuration_from(port, value)
|
59
|
+
case value
|
60
|
+
when Module
|
61
|
+
behavior = value
|
62
|
+
Bane::Configuration::ConfigurationRecord.new(port, behavior)
|
63
|
+
when Hash
|
64
|
+
behavior = value.delete(:behavior)
|
65
|
+
options = value
|
66
|
+
Bane::Configuration::ConfigurationRecord.new(port, behavior, options)
|
67
|
+
else
|
68
|
+
raise ConfigurationError, "Unknown configuration option: #{value.inspect}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'gserver'
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
class DelegatingGServer < GServer
|
5
|
+
def initialize(port, behavior, options = {}, logger = $stderr)
|
6
|
+
super(port)
|
7
|
+
@behavior = behavior
|
8
|
+
@options = options
|
9
|
+
self.audit = true
|
10
|
+
self.stdlog = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def serve(io)
|
14
|
+
@behavior.serve(io, @options)
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
alias_method :original_log, :log
|
20
|
+
|
21
|
+
def log(message)
|
22
|
+
original_log("#{@behavior.class.simple_name} #{@host}:#{@port} #{message}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def connecting(client)
|
26
|
+
addr = client.peeraddr
|
27
|
+
log("client:#{addr[1]} #{addr[2]}<#{addr[3]}> connect")
|
28
|
+
end
|
29
|
+
|
30
|
+
def disconnecting(client_port)
|
31
|
+
log("client:#{client_port} disconnect")
|
32
|
+
end
|
33
|
+
|
34
|
+
def starting()
|
35
|
+
log("start")
|
36
|
+
end
|
37
|
+
|
38
|
+
def stopping()
|
39
|
+
log("stop")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Bane
|
2
|
+
|
3
|
+
class Launcher
|
4
|
+
|
5
|
+
def initialize(configurations, logger = $stderr)
|
6
|
+
@configuration = configurations
|
7
|
+
@logger = logger
|
8
|
+
@running_servers = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def start
|
12
|
+
@running_servers = @configuration.start(@logger)
|
13
|
+
end
|
14
|
+
|
15
|
+
def join
|
16
|
+
@running_servers.each { |server| server.join }
|
17
|
+
end
|
18
|
+
|
19
|
+
def stop
|
20
|
+
@running_servers.each { |server| server.stop }
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class NaiveHttpResponse
|
2
|
+
|
3
|
+
CRLF = "\r\n"
|
4
|
+
|
5
|
+
class HttpHeader
|
6
|
+
def initialize(headers)
|
7
|
+
@headers = headers
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
@headers.map { |k, v| "#{k}: #{v}" }.join(CRLF)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(response_code, response_description, content_type, body)
|
17
|
+
@code = response_code
|
18
|
+
@description = response_description
|
19
|
+
@content_type = content_type
|
20
|
+
@body = body
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
str = []
|
25
|
+
str << "HTTP/1.1 #{@code} #{@description}"
|
26
|
+
str << http_header.to_s
|
27
|
+
str << ""
|
28
|
+
str << @body
|
29
|
+
str.join(CRLF)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def http_header()
|
35
|
+
HttpHeader.new(
|
36
|
+
{"Server" => "Bane HTTP Server",
|
37
|
+
"Connection" => "close",
|
38
|
+
"Date" => http_date(Time.now),
|
39
|
+
"Content-Type" => @content_type,
|
40
|
+
"Content-Length" => @body.length})
|
41
|
+
end
|
42
|
+
|
43
|
+
def http_date(time)
|
44
|
+
time.gmtime.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Bane
|
2
|
+
|
3
|
+
module ServiceRegistry
|
4
|
+
def self.all_servers
|
5
|
+
@servers ||= []
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.register(server)
|
9
|
+
all_servers << server unless all_servers.include?(server)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.unregister(server)
|
13
|
+
all_servers.delete server
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require 'timeout'
|
3
|
+
require 'mocha'
|
4
|
+
|
5
|
+
class BehaviorsTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include Bane::Behaviors
|
8
|
+
|
9
|
+
class FakeConnection < StringIO
|
10
|
+
def will_send(query)
|
11
|
+
@query = query
|
12
|
+
end
|
13
|
+
|
14
|
+
def gets
|
15
|
+
@query
|
16
|
+
@read_query = true
|
17
|
+
end
|
18
|
+
|
19
|
+
def read_query?
|
20
|
+
@read_query
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup
|
25
|
+
@fake_connection = FakeConnection.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_fixed_response_sends_the_specified_message
|
29
|
+
query_server(create(FixedResponse), :message => "Test Message")
|
30
|
+
|
31
|
+
assert_equal "Test Message", response
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_deluge_response_sends_one_million_bytes_by_default
|
35
|
+
query_server(create DelugeResponse)
|
36
|
+
|
37
|
+
assert_response_length 1_000_000
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_deluge_response_accepts_length_parameter
|
41
|
+
query_server(create(DelugeResponse), :length => 1)
|
42
|
+
|
43
|
+
assert_response_length 1
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_close_immediately_sends_no_response
|
47
|
+
query_server(create CloseImmediately)
|
48
|
+
|
49
|
+
assert_empty_response()
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_never_respond_never_sends_a_response
|
53
|
+
server = create NeverRespond
|
54
|
+
|
55
|
+
assert_raise Timeout::Error do
|
56
|
+
Timeout::timeout(3) { query_server(server) }
|
57
|
+
end
|
58
|
+
assert_empty_response
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_close_after_pause_sleeps_30_seconds_by_default_and_sends_nothing
|
62
|
+
server = create CloseAfterPause
|
63
|
+
server.expects(:sleep).with(30)
|
64
|
+
|
65
|
+
query_server(server)
|
66
|
+
|
67
|
+
assert_empty_response
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_close_after_pause_accepts_duration_parameter
|
71
|
+
server = create CloseAfterPause
|
72
|
+
|
73
|
+
within(2) { query_server(server, :duration => 1) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_slow_response_sends_a_message_slowly
|
77
|
+
server = create SlowResponse
|
78
|
+
message = "Hi!"
|
79
|
+
delay = 0.5
|
80
|
+
max_delay = (message.length + 1) * delay
|
81
|
+
|
82
|
+
within(max_delay) { query_server(server, :pause_duration => delay, :message => message)}
|
83
|
+
|
84
|
+
assert_equal message, response
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_random_response_sends_a_nonempty_response
|
88
|
+
query_server(create RandomResponse)
|
89
|
+
|
90
|
+
assert (!response.empty?), "Should have served a nonempty response"
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_refuse_all_http_credentials_sends_401_response_code
|
94
|
+
@fake_connection.will_send("GET /some/irrelevant/path HTTP/1.1")
|
95
|
+
|
96
|
+
server = create(HttpRefuseAllCredentials)
|
97
|
+
query_server(server)
|
98
|
+
|
99
|
+
assert @fake_connection.read_query?, "Should have read the HTTP query before sending response"
|
100
|
+
assert_match /HTTP\/1.1 401 Unauthorized/, response, 'Should have responded with the 401 response code'
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_simple_name_strips_away_the_namespace
|
104
|
+
assert_equal "SlowResponse", Bane::Behaviors::SlowResponse.simple_name
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def create(server_class)
|
110
|
+
server_class.new()
|
111
|
+
end
|
112
|
+
|
113
|
+
def query_server(server, options = {})
|
114
|
+
server.serve(@fake_connection, options)
|
115
|
+
end
|
116
|
+
|
117
|
+
def response
|
118
|
+
@fake_connection.string
|
119
|
+
end
|
120
|
+
|
121
|
+
def assert_empty_response
|
122
|
+
assert_equal 0, response.length, "Should have sent nothing"
|
123
|
+
end
|
124
|
+
|
125
|
+
def assert_response_length(expected_length)
|
126
|
+
assert_equal expected_length, response.length, "Response was the wrong length"
|
127
|
+
end
|
128
|
+
|
129
|
+
def within(duration)
|
130
|
+
begin
|
131
|
+
Timeout::timeout(duration) { yield }
|
132
|
+
rescue Timeout::Error
|
133
|
+
flunk "Test took too long - should have completed within #{duration} seconds."
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
class ConfigurationParserTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
include Bane
|
7
|
+
|
8
|
+
IRRELEVANT_BEHAVIOR = Module.new
|
9
|
+
|
10
|
+
def test_should_map_single_port_and_server_name
|
11
|
+
parser = ConfigurationParser.new(3000, "CloseAfterPause")
|
12
|
+
assert_matches_configuration([
|
13
|
+
{:port => 3000, :behavior => Behaviors::CloseAfterPause}
|
14
|
+
], parser)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_should_map_multiple_servers_given_one_starting_port
|
18
|
+
parser = ConfigurationParser.new(3000, "CloseImmediately", "CloseAfterPause")
|
19
|
+
assert_matches_configuration([
|
20
|
+
{:port => 3000, :behavior => Behaviors::CloseImmediately},
|
21
|
+
{:port => 3001, :behavior => Behaviors::CloseAfterPause}
|
22
|
+
], parser)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_should_map_string_port
|
26
|
+
parser = Bane::ConfigurationParser.new("3000", IRRELEVANT_BEHAVIOR)
|
27
|
+
actual = parser.configurations[0]
|
28
|
+
assert_equal 3000, actual.instance_variable_get(:@port), "Should have mapped port given a String"
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_should_raise_if_unknown_server_name
|
32
|
+
assert_raises Bane::UnknownBehaviorError do
|
33
|
+
Bane::ConfigurationParser.new(IRRELEVANT_PORT, "ABehaviorThatDoesNotExist")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_should_map_server_when_given_class
|
38
|
+
parser = ConfigurationParser.new(IRRELEVANT_PORT, Behaviors::CloseAfterPause)
|
39
|
+
actual = parser.configurations[0]
|
40
|
+
assert_equal Behaviors::CloseAfterPause, actual.instance_variable_get(:@behavior), "Wrong behavior"
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_should_ask_service_registry_for_all_behaviors_if_none_specified
|
44
|
+
fake_behavior = unique_behavior
|
45
|
+
another_fake_behavior = unique_behavior
|
46
|
+
|
47
|
+
ServiceRegistry.expects(:all_servers).returns([fake_behavior, another_fake_behavior])
|
48
|
+
parser = ConfigurationParser.new(4000)
|
49
|
+
assert_matches_configuration([
|
50
|
+
{:port => 4000, :behavior => fake_behavior},
|
51
|
+
{:port => 4001, :behavior => another_fake_behavior}
|
52
|
+
], parser)
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_should_raise_exception_if_no_arguments
|
56
|
+
assert_raises ConfigurationError do
|
57
|
+
ConfigurationParser.new
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_should_raise_exception_if_nil_port_with_behaviors
|
62
|
+
assert_raises ConfigurationError do
|
63
|
+
ConfigurationParser.new(nil, IRRELEVANT_BEHAVIOR)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_should_map_single_hash_entry_as_port_and_behavior
|
68
|
+
parser = ConfigurationParser.new(
|
69
|
+
10256 => Behaviors::CloseAfterPause
|
70
|
+
)
|
71
|
+
|
72
|
+
assert_matches_configuration([
|
73
|
+
{:port => 10256, :behavior => Behaviors::CloseAfterPause}
|
74
|
+
], parser)
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_should_map_multiple_hash_entries_as_port_and_behavior
|
78
|
+
parser = ConfigurationParser.new(
|
79
|
+
10256 => Behaviors::CloseAfterPause,
|
80
|
+
6450 => Behaviors::CloseImmediately
|
81
|
+
)
|
82
|
+
|
83
|
+
assert_matches_configuration([
|
84
|
+
{:port => 10256, :behavior => Behaviors::CloseAfterPause},
|
85
|
+
{:port => 6450, :behavior => Behaviors::CloseImmediately}
|
86
|
+
], parser)
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_should_map_hash_with_options
|
90
|
+
parser = ConfigurationParser.new(
|
91
|
+
10256 => {:behavior => Behaviors::CloseAfterPause, :duration => 3},
|
92
|
+
11239 => Behaviors::CloseImmediately
|
93
|
+
)
|
94
|
+
|
95
|
+
assert_matches_configuration([
|
96
|
+
{:port => 10256, :behavior => Behaviors::CloseAfterPause},
|
97
|
+
{:port => 11239, :behavior => Behaviors::CloseImmediately}
|
98
|
+
], parser)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def assert_matches_configuration(expected_config, actual_config)
|
104
|
+
actual_elements = actual_config.configurations
|
105
|
+
assert_equal expected_config.size, actual_elements.size, "Did not create correct number of configurations. Actual: #{actual_elements}, expected #{expected_config}"
|
106
|
+
|
107
|
+
expected_config.each do |expected|
|
108
|
+
# We make no guarantee on the order of the configurations
|
109
|
+
assert_includes_configuration(actual_elements, expected)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def assert_includes_configuration(actual_elements, expected)
|
114
|
+
expected_port = expected[:port]
|
115
|
+
matching_config = actual_elements.detect { |actual| actual.instance_variable_get(:@port) == expected_port }
|
116
|
+
assert_not_nil matching_config, "Should have found a configuration with port #{expected_port}"
|
117
|
+
assert_equal expected[:behavior], matching_config.instance_variable_get(:@behavior), "Wrong behavior for port #{expected_port}"
|
118
|
+
end
|
119
|
+
|
120
|
+
def unique_behavior
|
121
|
+
Module.new
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
module Bane
|
5
|
+
module Behaviors
|
6
|
+
class FakeTestServer < BasicBehavior
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class ConfigurationTest < Test::Unit::TestCase
|
12
|
+
|
13
|
+
include Bane
|
14
|
+
|
15
|
+
def test_starts_server_on_specified_port
|
16
|
+
target_port = 4000
|
17
|
+
DelegatingGServer.expects(:new).with(equals(target_port), anything(), anything(), anything()).returns(fake_server)
|
18
|
+
|
19
|
+
start_configuration(target_port, Behaviors::FakeTestServer)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_starts_server_with_specified_behavior
|
23
|
+
behavior_instance = stub('constructed behavior instance')
|
24
|
+
Behaviors::FakeTestServer.expects(:new).returns(behavior_instance)
|
25
|
+
DelegatingGServer.expects(:new).with(anything(), equals(behavior_instance), anything(), anything()).returns(fake_server)
|
26
|
+
|
27
|
+
start_configuration(IRRELEVANT_PORT, Behaviors::FakeTestServer)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_constructs_server_with_specified_options
|
31
|
+
options = { :an_option => :a_value, :another_option => :another_value }
|
32
|
+
DelegatingGServer.expects(:new).with(anything(), anything, has_entries(options), anything()).returns(fake_server)
|
33
|
+
|
34
|
+
start_configuration(IRRELEVANT_PORT, Behaviors::FakeTestServer, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def start_configuration(target_port, behavior, options = {})
|
40
|
+
configuration = configuration_with(target_port, behavior, options)
|
41
|
+
configuration.start(StringIO.new)
|
42
|
+
end
|
43
|
+
|
44
|
+
def configuration_with(port, behavior, options)
|
45
|
+
Configuration.new([Configuration::ConfigurationRecord.new(port, behavior, options)])
|
46
|
+
end
|
47
|
+
|
48
|
+
def fake_server
|
49
|
+
stub_everything('fake_server')
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
class DelegatingGserverTest < Test::Unit::TestCase
|
5
|
+
include Bane
|
6
|
+
|
7
|
+
IRRELEVANT_IO_STREAM = nil
|
8
|
+
IRRELEVANT_OPTIONS = {}
|
9
|
+
|
10
|
+
def test_serve_passes_a_hash_of_options_even_if_not_initialized_with_options
|
11
|
+
behavior = mock()
|
12
|
+
server = DelegatingGServer.new(IRRELEVANT_PORT, behavior)
|
13
|
+
|
14
|
+
behavior.expects(:serve).with(anything(), is_a(Hash))
|
15
|
+
|
16
|
+
server.serve(IRRELEVANT_IO_STREAM)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_serve_passes_constructor_options_to_behaviors_serve_method
|
20
|
+
behavior = mock()
|
21
|
+
|
22
|
+
initialized_options = {:expected => :options}
|
23
|
+
server = DelegatingGServer.new(IRRELEVANT_PORT, behavior, initialized_options)
|
24
|
+
|
25
|
+
behavior.expects(:serve).with(anything(), equals(initialized_options))
|
26
|
+
|
27
|
+
server.serve(IRRELEVANT_IO_STREAM)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_connection_log_messages_use_short_behavior_name_to_shorten_log_messages
|
31
|
+
[:connecting, :disconnecting].each do |method|
|
32
|
+
assert_log_message_uses_short_behavior_name_for(method) do |server|
|
33
|
+
server.send(method, stub_everything(:peeraddr => [127, 0, 0, 1]))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_start_stop_log_messages_use_short_behavior_name_to_shorten_log_messages
|
39
|
+
[:starting, :stopping].each do |method|
|
40
|
+
assert_log_message_uses_short_behavior_name_for(method) do |server|
|
41
|
+
server.send(method)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def assert_log_message_uses_short_behavior_name_for(method)
|
47
|
+
logger = StringIO.new
|
48
|
+
server = DelegatingGServer.new(IRRELEVANT_PORT, Bane::Behaviors::CloseImmediately.new, IRRELEVANT_OPTIONS, logger)
|
49
|
+
|
50
|
+
yield server
|
51
|
+
|
52
|
+
assert_match /CloseImmediately/, logger.string, "Log for #{method} should contain class short name"
|
53
|
+
assert_no_match /Behaviors::CloseImmediately/, logger.string, "Log for #{method} should not contain expanded module name"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require 'net/telnet'
|
3
|
+
require 'open-uri'
|
4
|
+
|
5
|
+
class BaneIntegrationTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
TEST_PORT = 4000
|
8
|
+
|
9
|
+
def test_uses_specified_port_and_server
|
10
|
+
run_server_with(TEST_PORT, "FixedResponse") do
|
11
|
+
telnet_to TEST_PORT do |response|
|
12
|
+
assert !response.empty?, "Should have had a non-empty response"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_uses_behavior_options
|
18
|
+
expected_message = "Expected test message"
|
19
|
+
options = {TEST_PORT => {:behavior => Bane::Behaviors::FixedResponse,
|
20
|
+
:message => expected_message}}
|
21
|
+
|
22
|
+
run_server_with(options) do
|
23
|
+
telnet_to TEST_PORT do |response|
|
24
|
+
assert_equal expected_message, response, "Wrong response from server"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_serves_http_requests
|
30
|
+
run_server_with(TEST_PORT, "HttpRefuseAllCredentials") do
|
31
|
+
begin
|
32
|
+
open("http://localhost:#{TEST_PORT}/some/url").read
|
33
|
+
flunk "Should have refused access"
|
34
|
+
rescue OpenURI::HTTPError => e
|
35
|
+
assert_match /401/, e.message
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def run_server_with(*options)
|
44
|
+
begin
|
45
|
+
launcher = Bane::Launcher.new(Configuration(*options), quiet_logger)
|
46
|
+
launcher.start
|
47
|
+
yield
|
48
|
+
ensure
|
49
|
+
launcher.stop if launcher
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def quiet_logger
|
54
|
+
StringIO.new
|
55
|
+
end
|
56
|
+
|
57
|
+
def telnet_to(port)
|
58
|
+
begin
|
59
|
+
telnet = Net::Telnet::new("Host" => "localhost",
|
60
|
+
"Port" => port,
|
61
|
+
"Timeout" => 5)
|
62
|
+
yield telnet.read
|
63
|
+
ensure
|
64
|
+
telnet.close
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
class LauncherTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
include Bane
|
7
|
+
|
8
|
+
def test_start_delegates_to_configuration
|
9
|
+
configuration = mock()
|
10
|
+
logger = stub()
|
11
|
+
launcher = Launcher.new(configuration, logger)
|
12
|
+
|
13
|
+
configuration.expects(:start).with(equals(logger))
|
14
|
+
launcher.start
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class NaiveHttpResponseTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
IRRELEVANT_RESPONSE_CODE = "999"
|
6
|
+
IRRELEVANT_RESPONSE_DESCRIPTION = "Irrelevant description"
|
7
|
+
IRRELEVANT_CONTENT_TYPE = "irrelevant content type"
|
8
|
+
IRRELEVANT_BODY = "irrelevant body"
|
9
|
+
|
10
|
+
def test_should_send_http_format_string
|
11
|
+
response = response_for("200", "OK", IRRELEVANT_CONTENT_TYPE, IRRELEVANT_BODY)
|
12
|
+
assert_equal "HTTP/1.1 200 OK\r\n", response.lines.first, "First line should be HTTP status"
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_should_include_date
|
16
|
+
assert_match /Date: .*/, any_response, 'Should have included a Date header'
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_should_set_content_type
|
20
|
+
response = response_for(IRRELEVANT_RESPONSE_CODE,
|
21
|
+
IRRELEVANT_RESPONSE_DESCRIPTION,
|
22
|
+
"text/xml",
|
23
|
+
IRRELEVANT_BODY)
|
24
|
+
assert_match /Content-Type: text\/xml/, response, 'Should have included content type'
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_should_set_content_length_as_length_of_body_in_bytes
|
28
|
+
message = "Hello, there!"
|
29
|
+
response = response_for(IRRELEVANT_RESPONSE_CODE,
|
30
|
+
IRRELEVANT_RESPONSE_DESCRIPTION,
|
31
|
+
IRRELEVANT_CONTENT_TYPE,
|
32
|
+
message)
|
33
|
+
|
34
|
+
assert_match /Content-Length: #{message.length}/, response, 'Should have included content length'
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_should_include_newline_between_headers_and_body
|
38
|
+
message = "This is some body content."
|
39
|
+
response = response_for(IRRELEVANT_RESPONSE_CODE,
|
40
|
+
IRRELEVANT_RESPONSE_DESCRIPTION,
|
41
|
+
IRRELEVANT_CONTENT_TYPE,
|
42
|
+
message)
|
43
|
+
|
44
|
+
response_lines = response.lines.to_a
|
45
|
+
index_of_body_start = response_lines.index(message)
|
46
|
+
line_before_body = response_lines[index_of_body_start - 1]
|
47
|
+
assert line_before_body.strip.empty?, "Should have had blank line before the body"
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_should_include_the_body_at_the_end_of_the_response
|
51
|
+
message = "This is some body content."
|
52
|
+
response = response_for(IRRELEVANT_RESPONSE_CODE,
|
53
|
+
IRRELEVANT_RESPONSE_DESCRIPTION,
|
54
|
+
IRRELEVANT_CONTENT_TYPE,
|
55
|
+
message)
|
56
|
+
assert_match /#{message}$/, response, "Should have ended the response with the body content"
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def response_for(response_code, response_description, content_type, body)
|
62
|
+
NaiveHttpResponse.new(response_code, response_description, content_type, body).to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
def any_response
|
66
|
+
response_for(IRRELEVANT_RESPONSE_CODE, IRRELEVANT_RESPONSE_DESCRIPTION, IRRELEVANT_CONTENT_TYPE, IRRELEVANT_BODY)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class ServiceRegistryTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
include Bane
|
6
|
+
|
7
|
+
def test_should_add_and_remove_behaviors
|
8
|
+
fake = fake_behavior
|
9
|
+
|
10
|
+
ServiceRegistry.register(fake)
|
11
|
+
assert ServiceRegistry.all_servers.include?(fake), "Should have added the new behavior"
|
12
|
+
|
13
|
+
ServiceRegistry.unregister(fake)
|
14
|
+
assert !(ServiceRegistry.all_servers.include?(fake)), "Should have removed the new behavior"
|
15
|
+
end
|
16
|
+
|
17
|
+
def fake_behavior
|
18
|
+
Class.new
|
19
|
+
end
|
20
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bane
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Wellman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-05-16 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: mocha
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.8
|
24
|
+
version:
|
25
|
+
description: " Bane is a test harness used to test your application's interaction with \n other servers. It is based upon the material from Michael Nygard's \"Release\n It!\" book as described in the \"Test Harness\" chapter.\n"
|
26
|
+
email: dan@danielwellman.com
|
27
|
+
executables:
|
28
|
+
- bane
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- TODO
|
33
|
+
files:
|
34
|
+
- lib/bane/behaviors.rb
|
35
|
+
- lib/bane/compatibility.rb
|
36
|
+
- lib/bane/configuration.rb
|
37
|
+
- lib/bane/configuration_parser.rb
|
38
|
+
- lib/bane/delegating_gserver.rb
|
39
|
+
- lib/bane/launcher.rb
|
40
|
+
- lib/bane/naive_http_response.rb
|
41
|
+
- lib/bane/service_registry.rb
|
42
|
+
- lib/bane.rb
|
43
|
+
- bin/bane
|
44
|
+
- test/bane/behaviors_test.rb
|
45
|
+
- test/bane/configuration_parser_test.rb
|
46
|
+
- test/bane/configuration_test.rb
|
47
|
+
- test/bane/delegating_gserver_test.rb
|
48
|
+
- test/bane/integration_test.rb
|
49
|
+
- test/bane/launcher_test.rb
|
50
|
+
- test/bane/naive_http_response_test.rb
|
51
|
+
- test/bane/service_registry_test.rb
|
52
|
+
- test/test_helper.rb
|
53
|
+
- examples/simple_port_and_class_as_constant.rb
|
54
|
+
- examples/simple_port_and_class_as_string.rb
|
55
|
+
- examples/specify_behavior_options.rb
|
56
|
+
- examples/specify_ports.rb
|
57
|
+
- Rakefile
|
58
|
+
- TODO
|
59
|
+
has_rdoc: true
|
60
|
+
homepage: http://github.com/danielwellman/bane
|
61
|
+
licenses: []
|
62
|
+
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
version:
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.3.5
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: A test harness for socket connections based upon ideas from Michael Nygard's 'Release It!'
|
87
|
+
test_files: []
|
88
|
+
|