bane 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +30 -30
- data/Rakefile +31 -40
- data/TODO +18 -10
- data/bin/bane +15 -10
- data/examples/multiple_behaviors.rb +24 -0
- data/examples/readme_example.rb +9 -0
- data/examples/single_behavior.rb +18 -0
- data/lib/bane.rb +2 -2
- data/lib/bane/{delegating_gserver.rb → behavior_server.rb} +11 -6
- data/lib/bane/behaviors.rb +29 -20
- data/lib/bane/command_line_configuration.rb +96 -0
- data/lib/bane/configuration_parser.rb +19 -4
- data/lib/bane/launcher.rb +7 -8
- data/lib/bane/service_registry.rb +4 -0
- data/test/bane/{delegating_gserver_test.rb → behavior_server_test.rb} +19 -16
- data/test/bane/behaviors_test.rb +32 -36
- data/test/bane/command_line_configuration_test.rb +100 -0
- data/test/bane/configuration_parser_test.rb +42 -55
- data/test/bane/fake_connection_test.rb +1 -1
- data/test/bane/integration_test.rb +45 -20
- data/test/bane/naive_http_response_test.rb +1 -1
- data/test/bane/service_registry_test.rb +1 -1
- metadata +63 -44
- data/examples/simple_port_and_class_as_constant.rb +0 -9
- data/examples/simple_port_and_class_as_string.rb +0 -7
- data/examples/specify_behavior_options.rb +0 -16
- data/examples/specify_ports.rb +0 -15
- data/lib/bane/configuration.rb +0 -50
- data/test/bane/configuration_test.rb +0 -52
- data/test/bane/launcher_test.rb +0 -16
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Bane
|
4
|
+
class CommandLineConfiguration
|
5
|
+
def initialize()
|
6
|
+
@options = { :host => BehaviorServer::DEFAULT_HOST }
|
7
|
+
@option_parser = init_option_parser
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse(args)
|
11
|
+
parse_options(@options, args)
|
12
|
+
|
13
|
+
return [] if (args.empty?)
|
14
|
+
|
15
|
+
port = parse_port(args[0])
|
16
|
+
behaviors = parse_behaviors(args.drop(1))
|
17
|
+
|
18
|
+
behaviors = ServiceRegistry.all_servers if behaviors.empty?
|
19
|
+
LinearPortMappedBehaviorConfiguration.new(port, behaviors, @options[:host]).servers
|
20
|
+
end
|
21
|
+
|
22
|
+
def usage
|
23
|
+
@option_parser.help
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def init_option_parser
|
29
|
+
OptionParser.new do |opts|
|
30
|
+
opts.banner = "Usage: bane [options] port [behaviors]"
|
31
|
+
opts.separator ""
|
32
|
+
opts.on("-l", "--listen-on-localhost",
|
33
|
+
"Listen on localhost, (#{BehaviorServer::DEFAULT_HOST}). [default]") do
|
34
|
+
@options[:host] = BehaviorServer::DEFAULT_HOST
|
35
|
+
end
|
36
|
+
opts.on("-a", "--listen-on-all-hosts", "Listen on all interfaces, (#{BehaviorServer::ALL_INTERFACES})") do
|
37
|
+
@options[:host] = BehaviorServer::ALL_INTERFACES
|
38
|
+
end
|
39
|
+
opts.separator ""
|
40
|
+
opts.separator "All behaviors:"
|
41
|
+
opts.separator ServiceRegistry.all_server_names.map { |title| " - #{title}" }.join("\n")
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_options(options, args)
|
47
|
+
@option_parser.parse!(args)
|
48
|
+
rescue OptionParser::InvalidOption => io
|
49
|
+
raise ConfigurationError, io.message
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_port(port)
|
53
|
+
Integer(port)
|
54
|
+
rescue ArgumentError => ae
|
55
|
+
raise ConfigurationError, "Invalid port number: #{port}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_behaviors(behavior_names)
|
59
|
+
behavior_names.map { |behavior| find(behavior) }
|
60
|
+
rescue UnknownBehaviorError => ube
|
61
|
+
raise ConfigurationError, ube.message
|
62
|
+
end
|
63
|
+
|
64
|
+
def find(behavior)
|
65
|
+
raise UnknownBehaviorError.new(behavior) unless Behaviors.const_defined?(behavior)
|
66
|
+
Behaviors.const_get(behavior)
|
67
|
+
end
|
68
|
+
|
69
|
+
class LinearPortMappedBehaviorConfiguration
|
70
|
+
def initialize(port, behaviors, host)
|
71
|
+
@starting_port = port
|
72
|
+
@behaviors = behaviors
|
73
|
+
@host = host
|
74
|
+
end
|
75
|
+
|
76
|
+
def servers
|
77
|
+
configurations = []
|
78
|
+
@behaviors.each_with_index do |behavior, index|
|
79
|
+
configurations << BehaviorServer.new(@starting_port + index, behavior.new, @host)
|
80
|
+
end
|
81
|
+
configurations
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class ConfigurationError < RuntimeError; end
|
89
|
+
|
90
|
+
class UnknownBehaviorError < RuntimeError
|
91
|
+
def initialize(name)
|
92
|
+
super "Unknown behavior: #{name}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -4,8 +4,8 @@ module Bane
|
|
4
4
|
|
5
5
|
attr_reader :configurations
|
6
6
|
|
7
|
-
|
8
7
|
def initialize(*args)
|
8
|
+
warn_about_deprecation()
|
9
9
|
@configurations = []
|
10
10
|
|
11
11
|
@configurations = case args[0]
|
@@ -18,6 +18,10 @@ module Bane
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
def warn_about_deprecation
|
22
|
+
warn "Kernel#Configuration() and ConfigurationParser are deprecated. Please directly construct an array of BehaviorServers to pass to Launcher."
|
23
|
+
end
|
24
|
+
|
21
25
|
private
|
22
26
|
|
23
27
|
def map_integer_and_behavior_arguments(*args)
|
@@ -31,7 +35,7 @@ module Bane
|
|
31
35
|
|
32
36
|
def setup_linear_ports(port, behavior_classes)
|
33
37
|
behavior_classes.each_with_index do |behavior, index|
|
34
|
-
@configurations <<
|
38
|
+
@configurations << create_server(port + index, find(behavior))
|
35
39
|
end
|
36
40
|
@configurations
|
37
41
|
end
|
@@ -59,16 +63,27 @@ module Bane
|
|
59
63
|
case value
|
60
64
|
when Module
|
61
65
|
behavior = value
|
62
|
-
|
66
|
+
create_server(port, behavior)
|
63
67
|
when Hash
|
64
68
|
behavior = value.delete(:behavior)
|
65
69
|
options = value
|
66
|
-
|
70
|
+
create_server(port, behavior, options)
|
67
71
|
else
|
68
72
|
raise ConfigurationError, "Unknown configuration option: #{value.inspect}"
|
69
73
|
end
|
70
74
|
end
|
71
75
|
|
76
|
+
def create_server(port, behavior, options = {})
|
77
|
+
BehaviorServer.new(port, behavior.new(options), BehaviorServer::DEFAULT_HOST)
|
78
|
+
end
|
79
|
+
|
72
80
|
end
|
73
81
|
|
82
|
+
end
|
83
|
+
|
84
|
+
# Helper method to easily create configuration.
|
85
|
+
module Kernel
|
86
|
+
def Configuration(*args)
|
87
|
+
Bane::ConfigurationParser.new(*args).configurations
|
88
|
+
end
|
74
89
|
end
|
data/lib/bane/launcher.rb
CHANGED
@@ -2,24 +2,23 @@ module Bane
|
|
2
2
|
|
3
3
|
class Launcher
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
7
|
-
@
|
8
|
-
@running_servers = []
|
5
|
+
def initialize(servers, logger = $stderr)
|
6
|
+
@servers = servers
|
7
|
+
@servers.each { |server| server.stdlog = logger }
|
9
8
|
end
|
10
9
|
|
11
10
|
def start
|
12
|
-
@
|
11
|
+
@servers.each { |server| server.start }
|
13
12
|
end
|
14
13
|
|
15
14
|
def join
|
16
|
-
@
|
15
|
+
@servers.each { |server| server.join }
|
17
16
|
end
|
18
17
|
|
19
18
|
def stop
|
20
|
-
@
|
19
|
+
@servers.each { |server| server.stop }
|
21
20
|
end
|
22
21
|
|
23
22
|
end
|
24
|
-
|
23
|
+
|
25
24
|
end
|
@@ -1,30 +1,32 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../test_helper'
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/../test_helper'
|
2
2
|
require 'mocha'
|
3
3
|
|
4
|
-
class
|
4
|
+
class BehaviorServerTest < Test::Unit::TestCase
|
5
5
|
include Bane
|
6
6
|
|
7
7
|
IRRELEVANT_IO_STREAM = nil
|
8
8
|
IRRELEVANT_OPTIONS = {}
|
9
|
+
IRRELEVANT_HOST = "1.1.1.1"
|
10
|
+
IRRELEVANT_BEHAVIOR = nil
|
9
11
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
behavior.expects(:serve).with(anything(), is_a(Hash))
|
12
|
+
def test_initializes_server_on_specified_port
|
13
|
+
server = BehaviorServer.new(6000, IRRELEVANT_BEHAVIOR)
|
14
|
+
assert_equal 6000, server.port
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
+
def test_initializes_server_on_specified_hostname
|
18
|
+
server = BehaviorServer.new(IRRELEVANT_PORT, IRRELEVANT_BEHAVIOR, "hostname")
|
19
|
+
assert_equal "hostname", server.host
|
17
20
|
end
|
18
21
|
|
19
|
-
def
|
22
|
+
def test_delegates_serve_call_to_behavior
|
23
|
+
io = mock()
|
20
24
|
behavior = mock()
|
21
|
-
|
22
|
-
initialized_options = {:expected => :options}
|
23
|
-
server = DelegatingGServer.new(IRRELEVANT_PORT, behavior, initialized_options)
|
25
|
+
server = BehaviorServer.new(IRRELEVANT_PORT, behavior)
|
24
26
|
|
25
|
-
behavior.expects(:serve).with(
|
27
|
+
behavior.expects(:serve).with(io)
|
26
28
|
|
27
|
-
server.serve(
|
29
|
+
server.serve(io)
|
28
30
|
end
|
29
31
|
|
30
32
|
def test_connection_log_messages_use_short_behavior_name_to_shorten_log_messages
|
@@ -45,8 +47,9 @@ class DelegatingGserverTest < Test::Unit::TestCase
|
|
45
47
|
|
46
48
|
def assert_log_message_uses_short_behavior_name_for(method)
|
47
49
|
logger = StringIO.new
|
48
|
-
server =
|
49
|
-
|
50
|
+
server = BehaviorServer.new(IRRELEVANT_PORT, Bane::Behaviors::CloseImmediately.new)
|
51
|
+
server.stdlog = logger
|
52
|
+
|
50
53
|
yield server
|
51
54
|
|
52
55
|
assert_match /CloseImmediately/, logger.string, "Log for #{method} should contain class short name"
|
data/test/bane/behaviors_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../test_helper'
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/../test_helper'
|
2
2
|
require 'timeout'
|
3
3
|
require 'mocha'
|
4
4
|
|
@@ -11,72 +11,80 @@ class BehaviorsTest < Test::Unit::TestCase
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def test_fixed_response_sends_the_specified_message
|
14
|
-
query_server(
|
14
|
+
query_server(FixedResponse.new(:message => "Test Message"))
|
15
15
|
|
16
16
|
assert_equal "Test Message", response
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_newline_response_sends_only_a_newline_character
|
20
|
-
query_server(
|
20
|
+
query_server(NewlineResponse.new)
|
21
21
|
|
22
22
|
assert_equal "\n", response
|
23
23
|
end
|
24
24
|
|
25
25
|
def test_deluge_response_sends_one_million_bytes_by_default
|
26
|
-
query_server(
|
26
|
+
query_server(DelugeResponse.new)
|
27
27
|
|
28
28
|
assert_response_length 1_000_000
|
29
29
|
end
|
30
30
|
|
31
31
|
def test_deluge_response_accepts_length_parameter
|
32
|
-
query_server(
|
32
|
+
query_server(DelugeResponse.new(:length => 1))
|
33
33
|
|
34
34
|
assert_response_length 1
|
35
35
|
end
|
36
36
|
|
37
37
|
def test_close_immediately_sends_no_response
|
38
|
-
query_server(
|
38
|
+
query_server(CloseImmediately.new)
|
39
39
|
|
40
40
|
assert_empty_response()
|
41
41
|
end
|
42
42
|
|
43
43
|
def test_never_respond_never_sends_a_response
|
44
|
-
server =
|
44
|
+
server = NeverRespond.new
|
45
45
|
|
46
46
|
assert_raise Timeout::Error do
|
47
|
-
Timeout::timeout(
|
47
|
+
Timeout::timeout(1) { query_server(server) }
|
48
48
|
end
|
49
49
|
assert_empty_response
|
50
50
|
end
|
51
51
|
|
52
|
-
def
|
53
|
-
server =
|
52
|
+
def test_close_after_pause_sleeps_30_seconds_by_default
|
53
|
+
server = CloseAfterPause.new
|
54
54
|
server.expects(:sleep).with(30)
|
55
|
-
|
56
|
-
query_server(server)
|
57
55
|
|
58
|
-
|
56
|
+
query_server(server)
|
59
57
|
end
|
60
58
|
|
59
|
+
|
61
60
|
def test_close_after_pause_accepts_duration_parameter
|
62
|
-
server =
|
61
|
+
server = CloseAfterPause.new(:duration => 1)
|
62
|
+
server.expects(:sleep).with(1)
|
63
63
|
|
64
|
-
|
64
|
+
query_server(server)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_close_after_pause_sends_nothing
|
68
|
+
server = CloseAfterPause.new
|
69
|
+
server.stubs(:sleep)
|
70
|
+
|
71
|
+
query_server(server)
|
65
72
|
end
|
66
73
|
|
67
74
|
def test_slow_response_sends_a_message_slowly
|
68
|
-
server = create(SlowResponse)
|
69
75
|
message = "Hi!"
|
70
76
|
delay = 0.5
|
71
|
-
max_delay = (message.length + 1) * delay
|
72
77
|
|
73
|
-
|
78
|
+
server = SlowResponse.new(:pause_duration => delay, :message => message)
|
79
|
+
server.expects(:sleep).with(delay).at_least(message.length)
|
80
|
+
|
81
|
+
query_server(server)
|
74
82
|
|
75
83
|
assert_equal message, response
|
76
84
|
end
|
77
85
|
|
78
86
|
def test_random_response_sends_a_nonempty_response
|
79
|
-
query_server(
|
87
|
+
query_server(RandomResponse.new)
|
80
88
|
|
81
89
|
assert (!response.empty?), "Should have served a nonempty response"
|
82
90
|
end
|
@@ -84,7 +92,7 @@ class BehaviorsTest < Test::Unit::TestCase
|
|
84
92
|
def test_refuse_all_http_credentials_sends_401_response_code
|
85
93
|
@fake_connection.will_send("GET /some/irrelevant/path HTTP/1.1")
|
86
94
|
|
87
|
-
server =
|
95
|
+
server = HttpRefuseAllCredentials.new
|
88
96
|
query_server(server)
|
89
97
|
|
90
98
|
assert @fake_connection.read_all_queries?, "Should have read the HTTP query before sending response"
|
@@ -96,11 +104,11 @@ class BehaviorsTest < Test::Unit::TestCase
|
|
96
104
|
end
|
97
105
|
|
98
106
|
def test_for_each_line_reads_a_line_before_responding
|
99
|
-
server =
|
107
|
+
server = Bane::Behaviors::FixedResponseForEachLine.new({:message => "Dynamic"})
|
100
108
|
|
101
109
|
@fake_connection.will_send "irrelevant\n"
|
102
110
|
|
103
|
-
query_server(server
|
111
|
+
query_server(server)
|
104
112
|
assert_equal "Dynamic", response
|
105
113
|
|
106
114
|
assert @fake_connection.read_all_queries?
|
@@ -108,12 +116,8 @@ class BehaviorsTest < Test::Unit::TestCase
|
|
108
116
|
|
109
117
|
private
|
110
118
|
|
111
|
-
def
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
def query_server(server, options = {})
|
116
|
-
server.serve(@fake_connection, options)
|
119
|
+
def query_server(server)
|
120
|
+
server.serve(@fake_connection)
|
117
121
|
end
|
118
122
|
|
119
123
|
def response
|
@@ -128,12 +132,4 @@ class BehaviorsTest < Test::Unit::TestCase
|
|
128
132
|
assert_equal expected_length, response.length, "Response was the wrong length"
|
129
133
|
end
|
130
134
|
|
131
|
-
def within(duration)
|
132
|
-
begin
|
133
|
-
Timeout::timeout(duration) { yield }
|
134
|
-
rescue Timeout::Error
|
135
|
-
flunk "Test took too long - should have completed within #{duration} seconds."
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
135
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/../test_helper'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
class CommandLineConfigurationTest < Test::Unit::TestCase
|
5
|
+
include Bane
|
6
|
+
|
7
|
+
IRRELEVANT_BEHAVIOR = "CloseImmediately"
|
8
|
+
|
9
|
+
def test_creates_specified_behavior_on_given_port
|
10
|
+
expect_server_created_with(:port => 3000, :behavior => Behaviors::CloseImmediately)
|
11
|
+
|
12
|
+
create_configuration_for([3000, "CloseImmediately"])
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_creates_all_known_behavior_if_only_port_specified
|
16
|
+
expect_server_created_with :port => 4000, :behavior => Behaviors::CloseImmediately
|
17
|
+
expect_server_created_with :port => 4001, :behavior => Behaviors::NeverRespond
|
18
|
+
|
19
|
+
ServiceRegistry.stubs(:all_servers).returns([Behaviors::CloseImmediately, Behaviors::NeverRespond])
|
20
|
+
|
21
|
+
create_configuration_for([4000])
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_creates_multiple_behaviors_starting_on_given_port
|
25
|
+
expect_server_created_with :port => 3000, :behavior => Behaviors::CloseImmediately
|
26
|
+
expect_server_created_with :port => 3001, :behavior => Behaviors::CloseAfterPause
|
27
|
+
|
28
|
+
create_configuration_for([3000, "CloseImmediately", "CloseAfterPause"])
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_dash_l_option_sets_listen_host_to_localhost
|
32
|
+
expect_server_created_with :host => BehaviorServer::DEFAULT_HOST
|
33
|
+
|
34
|
+
create_configuration_for(["-l", IRRELEVANT_PORT, IRRELEVANT_BEHAVIOR])
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_listen_on_localhost_sets_listen_host_to_localhost
|
38
|
+
expect_server_created_with :host => BehaviorServer::DEFAULT_HOST
|
39
|
+
|
40
|
+
create_configuration_for(["--listen-on-localhost", IRRELEVANT_PORT, IRRELEVANT_BEHAVIOR])
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_dash_a_option_sets_listen_host_to_all_interfaces
|
44
|
+
expect_server_created_with :host => BehaviorServer::ALL_INTERFACES
|
45
|
+
|
46
|
+
create_configuration_for(["-a", IRRELEVANT_PORT, IRRELEVANT_BEHAVIOR])
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_listen_on_all_hosts_option_sets_listen_host_to_all_interfaces
|
50
|
+
expect_server_created_with :host => BehaviorServer::ALL_INTERFACES
|
51
|
+
|
52
|
+
create_configuration_for(["--listen-on-all-hosts", IRRELEVANT_PORT, IRRELEVANT_BEHAVIOR])
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_no_arguments_returns_empty_configuration
|
56
|
+
assert(create_configuration_for([]).empty?,
|
57
|
+
"Should have returned no configurations for empty arguments")
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_non_integer_port_fails_with_error_message
|
61
|
+
assert_invaild_arguments_fail_matching_message(["text_instead_of_an_integer"], /Invalid Port Number/i,
|
62
|
+
"Should have indicated the port was invalid.")
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_unknown_behavior_fails_with_unknown_behavior_message
|
66
|
+
assert_invaild_arguments_fail_matching_message([IRRELEVANT_PORT, "AnUknownBehavior"], /Unknown Behavior/i,
|
67
|
+
"Should have indicated the given behavior is unknown.")
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_invalid_option_fails_with_error_message
|
71
|
+
assert_invaild_arguments_fail_matching_message(["--unknown-option", IRRELEVANT_PORT], /Invalid Option/i,
|
72
|
+
"Should have indicated the --uknown-option switch was unknown.")
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def create_configuration_for(array)
|
79
|
+
CommandLineConfiguration.new().parse(array)
|
80
|
+
end
|
81
|
+
|
82
|
+
def unique_behavior
|
83
|
+
Class.new
|
84
|
+
end
|
85
|
+
|
86
|
+
def expect_server_created_with(arguments)
|
87
|
+
arguments = { :port => anything(), :host => anything() }.merge(arguments)
|
88
|
+
behavior_matcher = arguments[:behavior] ? instance_of(arguments[:behavior]) : anything()
|
89
|
+
BehaviorServer.expects(:new).with(arguments[:port], behavior_matcher, arguments[:host])
|
90
|
+
end
|
91
|
+
|
92
|
+
def assert_invaild_arguments_fail_matching_message(arguments, message_matcher, assertion_failure_message)
|
93
|
+
create_configuration_for(arguments)
|
94
|
+
fail "Should have failed"
|
95
|
+
rescue ConfigurationError => ce
|
96
|
+
assert_match(message_matcher, ce.message, assertion_failure_message)
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
end
|