bane 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +16 -6
  3. data/Rakefile +1 -1
  4. data/TODO +5 -8
  5. data/bin/bane +4 -12
  6. data/examples/multiple_behaviors.rb +7 -6
  7. data/examples/readme_example.rb +2 -1
  8. data/examples/single_behavior.rb +2 -1
  9. data/lib/bane.rb +18 -5
  10. data/lib/bane/arguments_parser.rb +73 -0
  11. data/lib/bane/behavior_maker.rb +39 -0
  12. data/lib/bane/behaviors/responders/close_after_pause.rb +21 -0
  13. data/lib/bane/behaviors/responders/close_immediately.rb +14 -0
  14. data/lib/bane/behaviors/responders/deluge_response.rb +27 -0
  15. data/lib/bane/behaviors/responders/echo_response.rb +16 -0
  16. data/lib/bane/behaviors/responders/exported.rb +9 -0
  17. data/lib/bane/behaviors/responders/fixed_response.rb +25 -0
  18. data/lib/bane/behaviors/responders/for_each_line.rb +18 -0
  19. data/lib/bane/behaviors/responders/http_refuse_all_credentials.rb +31 -0
  20. data/lib/bane/behaviors/responders/never_respond.rb +27 -0
  21. data/lib/bane/behaviors/responders/newline_response.rb +18 -0
  22. data/lib/bane/behaviors/responders/random_response.rb +24 -0
  23. data/lib/bane/behaviors/responders/slow_response.rb +32 -0
  24. data/lib/bane/behaviors/servers/exported.rb +18 -0
  25. data/lib/bane/behaviors/servers/responder_server.rb +50 -0
  26. data/lib/bane/behaviors/servers/timeout_in_listen_queue.rb +51 -0
  27. data/lib/bane/command_line_configuration.rb +22 -79
  28. data/lib/bane/extensions.rb +7 -0
  29. data/test/bane/acceptance_test.rb +65 -0
  30. data/test/bane/arguments_parser_test.rb +76 -0
  31. data/test/bane/bane_test.rb +12 -0
  32. data/test/bane/behaviors/responders/close_after_pause_test.rb +30 -0
  33. data/test/bane/behaviors/responders/close_immediately_test.rb +14 -0
  34. data/test/bane/behaviors/responders/deluge_response_test.rb +20 -0
  35. data/test/bane/behaviors/responders/echo_response_test.rb +16 -0
  36. data/test/bane/behaviors/responders/fixed_response_test.rb +14 -0
  37. data/test/bane/behaviors/responders/for_each_line_test.rb +29 -0
  38. data/test/bane/behaviors/responders/http_refuse_all_credentials_test.rb +18 -0
  39. data/test/bane/behaviors/responders/never_respond_test.rb +31 -0
  40. data/test/bane/behaviors/responders/newline_response_test.rb +14 -0
  41. data/test/bane/behaviors/responders/random_response_test.rb +15 -0
  42. data/test/bane/behaviors/responders/slow_response_test.rb +21 -0
  43. data/test/bane/behaviors/servers/responder_server_test.rb +77 -0
  44. data/test/bane/behaviors/servers/timeout_in_listen_queue_test.rb +23 -0
  45. data/test/bane/command_line_configuration_test.rb +54 -72
  46. data/test/bane/extensions_test.rb +17 -0
  47. data/test/bane/fake_connection_test.rb +1 -1
  48. data/test/bane/launchable_role_tests.rb +20 -0
  49. data/test/bane/naive_http_response_test.rb +1 -1
  50. data/test/test_helper.rb +42 -1
  51. metadata +143 -99
  52. data/lib/bane/behavior_server.rb +0 -47
  53. data/lib/bane/behaviors.rb +0 -171
  54. data/lib/bane/compatibility.rb +0 -36
  55. data/lib/bane/configuration_parser.rb +0 -89
  56. data/lib/bane/service_registry.rb +0 -21
  57. data/test/bane/behavior_server_test.rb +0 -59
  58. data/test/bane/behaviors_test.rb +0 -135
  59. data/test/bane/configuration_parser_test.rb +0 -111
  60. data/test/bane/integration_test.rb +0 -92
  61. data/test/bane/service_registry_test.rb +0 -20
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ Y2M3MGZkYWY0NDNhNjZlYTY5M2I2MGViMDljNjY3YzRiYmY2MDg3OA==
5
+ data.tar.gz: !binary |-
6
+ OWY2YTY3ZDFmNDU4Y2I1MzEwZjViNDIzZGU0YzMzNzk4ZjdiMmIxOA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ODE5MDJlYzkyMzNmYzllYzRlMTAwY2RjYjNhYzlkNjA1NTU0ODRiZmNhOGZh
10
+ NjczN2E4ZjM4YjcyMjUyYTBlODUxNjVkM2NiODc4MjIyM2Q2MjE3OWNmYzk0
11
+ YmFjZmVlNzVmYjMyOGE4ZmJhZmYzYzU3MTljOWMyODc5YjRiYjM=
12
+ data.tar.gz: !binary |-
13
+ MmI5ZjZmYjM5YTczYzQ0NjFmYjNlMTBlY2I5ZmJiY2ZkMmQ4MDM5MjczYjgw
14
+ MTJkMWQxOTVkOTExN2FiMjVjNjUxOGRlOWU1YTc3OGUxMjUxNDM1Mjk1Zjkw
15
+ NDU5YzgzYzE2OGQ0MTY4OTdlY2RjYzNlNDM0ZDEyZmEyMGI3YzI=
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Bane
2
2
 
3
+ [![Build Status](https://secure.travis-ci.org/danielwellman/bane.png)](http://travis-ci.org/danielwellman/bane) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/danielwellman/bane)
4
+
3
5
  Bane is a test harness used to test your application's interaction with other servers. It is based upon the material from Michael Nygard's ["Release It!"](http://www.pragprog.com/titles/mnee/release-it) book as described in the "Test Harness" chapter.
4
6
 
5
7
  ## Why Use Bane?
@@ -8,12 +10,14 @@ If you are building an application, you may depend on third-party servers or web
8
10
 
9
11
  ## Setup
10
12
 
11
- Bane is available as a gem. Install it with
13
+ Bane is available as a Ruby gem. Install it with
12
14
 
13
15
  `gem install bane`
14
16
 
15
17
  Note that Bane installs an executable, `bane`. Simply invoke `bane` with no arguments to get a usage description.
16
18
 
19
+ Bane requires Ruby 1.9 or later. If you would like to use a Ruby 1.8.7-compatible version, install version 0.3.0.
20
+
17
21
  ## Usage
18
22
 
19
23
  Bane is designed with a few usage scenarios in mind:
@@ -40,13 +44,14 @@ require 'bane'
40
44
  include Bane
41
45
 
42
46
  launcher = Launcher.new(
43
- [BehaviorServer.new(3000, Behaviors::FixedResponse.new(:message => "Shall we play a game?"))])
47
+ [BehaviorServer.new(3000, Behaviors::Responders::FixedResponse.new(message: "Shall we play a game?"))])
44
48
  launcher.start
45
49
  launcher.join
46
50
  ```
47
51
 
48
- See the `examples`directory for more examples. For a list of options supported by the
49
- included behaviors, see the source for the behaviors in `Bane::Behaviors` at `lib/bane/behaviors.rb`.
52
+ See the `examples` directory for more examples. For a list of options supported by the
53
+ included behaviors, see the source for the behaviors in `lib/bane/behaviors`; note that you will find both
54
+ servers (that bind to sockets directly) and responders (that assume a running TCP server and communicate with a socket connection).
50
55
 
51
56
  ## Listening on all hosts
52
57
 
@@ -60,7 +65,7 @@ By default, the socket behaviors that send any data will close the connection im
60
65
 
61
66
  For example, if you want to send a static response and then close the connection immediately, use `FixedResponse`. If you want to keep the connection open and respond to every line of input with the same data, use `FixedResponseForEachLine`. Note that these behaviors will never close the connection; they will happily respond to every line of input until you stop Bane.
62
67
 
63
- If you are implementing a new behavior, you should consider whether or not you would like to provide another variation which keeps a connection open and responds after every line of input. If so, create the basic behavior which responds and closes the connection immediately, then create another behavior which includes the `ForEachLine` module. See the source in `lib/bane/behaviors.rb` for some examples.
68
+ If you are implementing a new behavior, you should consider whether or not you would like to provide another variation which keeps a connection open and responds after every line of input. If so, create the basic behavior which responds and closes the connection immediately, then create another behavior which includes the `ForEachLine` module. See the source in `lib/bane/behaviors/responders/fixed_response.rb` for some examples.
64
69
 
65
70
  ## Background
66
71
 
@@ -83,7 +88,7 @@ See the implementation of HttpRefuseAllCredentials for a simple example of an HT
83
88
  * The service can send a response of HTML instead of the expected XML.
84
89
 
85
90
  The following behaviors are not yet supported. These require the ability to manipulate
86
- TCP packets at a low level; which may require a C or C++ extension.
91
+ TCP packets at a low level, which may require a C or C++ extension or raw sockets.
87
92
 
88
93
  * The connection can be refused.
89
94
  * The request can sit in a listen queue until the caller times out.
@@ -93,3 +98,8 @@ TCP packets at a low level; which may require a C or C++ extension.
93
98
  * The connection can be established, but packets could be lost causing retransmit delays
94
99
  * The connection can be established, but the remote end never acknowledges receiving a packet, causing endless retransmits
95
100
 
101
+ ## Support
102
+
103
+ <a href="http://www.cyrusinnovation.com/"><img src="http://www.cyrusinnovation.com/marketing/logo.png" alt="Cyrus"></a>
104
+
105
+ Thank you to [Cyrus](http://www.cyrusinnovation.com/) for supporting the development of this project.
data/Rakefile CHANGED
@@ -22,7 +22,7 @@ Jeweler::Tasks.new do |gem|
22
22
  other servers. It is based upon the material from Michael Nygard's "Release
23
23
  It!" book as described in the "Test Harness" chapter.
24
24
  END
25
- gem.authors = ["Daniel Wellman"]
25
+ gem.authors = ["Daniel Wellman", "Joe Leo"]
26
26
  gem.email = "dan@danielwellman.com"
27
27
  gem.files = FileList[ 'lib/**/*', 'bin/*', 'test/**/*', 'examples/*',
28
28
  'Rakefile' ]
data/TODO CHANGED
@@ -1,21 +1,18 @@
1
1
  Features:
2
- - Quieter exits on Ctrl-C (than plain stack trace)
3
2
 
4
3
  Design questions / Ideas:
5
- - Make BasicBehavior a module called Behavior, and include that in each behavior?
6
- - Is there a natural separation in Configuration between parsing arguments and instantiating/finding behaviors?
7
- In particular, mocking "new" on a class seems to be a smell -- at least because I keep forgetting that 'new' must
8
- return an object, and my mocks didn't do that (see CommandLineConfigurationTest, and the messier ConfigurationParserTest)
9
- - Explore the ServiceRegistry usage -- should "find" behaviors be there? Printing?
10
- - Decide if the current behaviors should be tied more directly to BehaviorServer, via subclassing or some other way.
11
4
  - Figure out where the logger configuration logic belongs in the Launcher/BehaviorServer relationship
12
5
  - Should the default logger go to STDERR or STDOUT?
13
- - Break the Behaviors out into several files and test files?
14
6
  - Log every request to the server/behavior, in addition to open, close. For this to work, it would have to be the
15
7
  behavior's responsibility, since GServer#serve gets called only once for the lifetime of the connection.
8
+ - If we extract a commong logger, we might use that to test for proper disconnection in NeverRespondTest#test_disconnects_after_client_closes_connection
16
9
 
17
10
  Future Behaviors:
18
11
  - Create a more configurable version of the DelugeResponse which allows for a header, footer, content and times to repeat.
19
12
  - Write the remaining bad HTTP behaviors. In addition, we may want to replace the NaiveHttpResponse with something
20
13
  from the standard Ruby library, so that there's less code in this project, and so we know that we're
21
14
  following the HTTP protocol.
15
+ - Have a tiny listen queue (queue size 0) and a connection never gets into the queue
16
+ - Have a giant listen queue (queue size max?) and only one thread processing -- times out in the listen queue
17
+ - Close the write part of the connection, but not the read
18
+ - Close the read part of the connection, but not the write
data/bin/bane CHANGED
@@ -1,22 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
4
3
  require 'bane'
5
4
 
6
- config = Bane::CommandLineConfiguration.new()
7
- begin
8
- servers = config.parse(ARGV)
9
- rescue Bane::ConfigurationError => ce
10
- puts ce.message
11
- puts config.usage
5
+ parser = Bane::CommandLineConfiguration.new(Bane.find_makeables)
6
+ servers = parser.process(ARGV) do |error_message|
7
+ puts error_message
12
8
  exit 1
13
9
  end
14
10
 
15
- if servers.empty?
16
- puts config.usage
17
- exit 0
18
- end
19
-
20
11
  launcher = Bane::Launcher.new(servers)
21
12
  launcher.start
13
+ trap("SIGINT") { Thread.new { launcher.stop; exit } }
22
14
  launcher.join
@@ -2,18 +2,19 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
2
  require 'bane'
3
3
 
4
4
  include Bane
5
+ include Behaviors
5
6
 
6
7
  # This example creates several behavior listening on distinct ports.
7
8
  # Note the FixedResponse port specifies to listen to all hosts (0.0.0.0), all
8
9
  # other servers listen to localhost only by default (127.0.0.1).
9
10
 
10
- close_immediately = Behaviors::CloseImmediately.new
11
- never_respond = Behaviors::NeverRespond.new
12
- fixed_response = Behaviors::FixedResponse.new(:message => "OK")
11
+ close_immediately = Responders::CloseImmediately.new
12
+ never_respond = Responders::NeverRespond.new
13
+ fixed_response = Responders::FixedResponse.new(message: "OK")
13
14
 
14
- launcher = Launcher.new([BehaviorServer.new(3000, close_immediately),
15
- BehaviorServer.new(8000, never_respond),
16
- BehaviorServer.new(8080, fixed_response, BehaviorServer::ALL_INTERFACES)])
15
+ launcher = Launcher.new([Servers::ResponderServer.new(3000, close_immediately),
16
+ Servers::ResponderServer.new(8000, never_respond),
17
+ Servers::ResponderServer.new(8080, fixed_response, Servers::ALL_INTERFACES)])
17
18
  launcher.start
18
19
  # To run until interrupt, use the following line:
19
20
  #launcher.join
@@ -2,8 +2,9 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
2
  require 'bane'
3
3
 
4
4
  include Bane
5
+ include Behaviors
5
6
 
6
7
  launcher = Launcher.new(
7
- [BehaviorServer.new(3000, Behaviors::FixedResponse.new(:message => "Shall we play a game?"))])
8
+ [Servers::ResponderServer.new(3000, Responders::FixedResponse.new(message: "Shall we play a game?"))])
8
9
  launcher.start
9
10
  launcher.join
@@ -2,11 +2,12 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
2
  require 'bane'
3
3
 
4
4
  include Bane
5
+ include Behaviors
5
6
 
6
7
  # This example creates a single behavior listening on port 3000.
7
8
  # Note that the behavior, CloseAfterPause, specifies a default duration to pause - 60 seconds.
8
9
 
9
- behavior = BehaviorServer.new(3000, Behaviors::CloseAfterPause.new(:duration => 60))
10
+ behavior = Servers::ResponderServer.new(3000, Responders::CloseAfterPause.new(duration: 60))
10
11
  launcher = Launcher.new([behavior])
11
12
  launcher.start
12
13
  # To run until interrupt, use the following line:
@@ -1,8 +1,21 @@
1
- require 'bane/compatibility'
2
- require 'bane/service_registry'
3
- require 'bane/behaviors'
1
+ require 'bane/extensions'
2
+ require 'bane/behavior_maker'
3
+ require 'bane/behaviors/responders/for_each_line'
4
+ require 'bane/behaviors/responders/close_after_pause'
5
+ require 'bane/behaviors/responders/close_immediately'
6
+ require 'bane/behaviors/responders/deluge_response'
7
+ require 'bane/behaviors/responders/echo_response'
8
+ require 'bane/behaviors/responders/fixed_response'
9
+ require 'bane/behaviors/responders/http_refuse_all_credentials'
10
+ require 'bane/behaviors/responders/never_respond'
11
+ require 'bane/behaviors/responders/newline_response'
12
+ require 'bane/behaviors/responders/random_response'
13
+ require 'bane/behaviors/responders/slow_response'
14
+ require 'bane/behaviors/responders/exported'
15
+ require 'bane/behaviors/servers/responder_server'
16
+ require 'bane/behaviors/servers/timeout_in_listen_queue'
17
+ require 'bane/behaviors/servers/exported'
4
18
  require 'bane/launcher'
5
- require 'bane/behavior_server'
19
+ require 'bane/arguments_parser'
6
20
  require 'bane/command_line_configuration'
7
- require 'bane/configuration_parser'
8
21
  require 'bane/naive_http_response'
@@ -0,0 +1,73 @@
1
+ require 'optparse'
2
+
3
+ module Bane
4
+ class ArgumentsParser
5
+ def initialize(makeable_names)
6
+ @makeable_names = makeable_names
7
+ @options = {host: default_host}
8
+ @option_parser = init_option_parser
9
+ end
10
+
11
+ def parse(args)
12
+ @option_parser.parse!(args)
13
+
14
+ raise ConfigurationError, "Missing arguments" if args.empty?
15
+
16
+ port = parse_port(args[0])
17
+ behaviors = args.drop(1)
18
+ ParsedArguments.new(port, @options[:host], behaviors)
19
+ rescue OptionParser::InvalidOption => io
20
+ raise ConfigurationError, io.message
21
+ end
22
+
23
+ def usage
24
+ @option_parser.help
25
+ end
26
+
27
+ private
28
+
29
+ def init_option_parser
30
+ OptionParser.new do |opts|
31
+ opts.banner = 'Usage: bane [options] port [behaviors]'
32
+ opts.separator ''
33
+ opts.on('-l', '--listen-on-localhost',
34
+ "Listen on localhost, (#{default_host}). [default]") do
35
+ @options[:host] = default_host
36
+ end
37
+ opts.on('-a', '--listen-on-all-hosts', "Listen on all interfaces, (#{all_interfaces})") do
38
+ @options[:host] = all_interfaces
39
+ end
40
+ opts.separator ''
41
+ opts.separator 'All behaviors:'
42
+ opts.separator @makeable_names.sort.map { |title| " - #{title}" }.join("\n")
43
+ end
44
+ end
45
+
46
+ def parse_port(port)
47
+ Integer(port)
48
+ rescue ArgumentError
49
+ raise ConfigurationError, "Invalid port number: #{port}"
50
+ end
51
+
52
+ def all_interfaces
53
+ Behaviors::Servers::ALL_INTERFACES
54
+ end
55
+
56
+ def default_host
57
+ Behaviors::Servers::LOCALHOST
58
+ end
59
+ end
60
+
61
+ class ParsedArguments
62
+
63
+ attr_reader :port, :host, :behaviors
64
+
65
+ def initialize(port, host, behaviors)
66
+ @host = host
67
+ @port = port
68
+ @behaviors = behaviors
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,39 @@
1
+ module Bane
2
+
3
+ class BehaviorMaker
4
+ def initialize(makeables)
5
+ @makeables = makeables
6
+ end
7
+
8
+ def create(behavior_names, starting_port, host)
9
+ behavior_names
10
+ .map { |behavior| makeables.fetch(behavior) { raise UnknownBehaviorError.new(behavior) } }
11
+ .map.with_index { |maker, index| maker.make(starting_port + index, host) }
12
+ end
13
+
14
+ def create_all(starting_port, host)
15
+ makeables.sort.map.with_index { |name_maker_pair, index| name_maker_pair.last.make(starting_port + index, host) }
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :makeables
21
+ end
22
+
23
+ class UnknownBehaviorError < RuntimeError
24
+ def initialize(name)
25
+ super "Unknown behavior: #{name}"
26
+ end
27
+ end
28
+
29
+ class ResponderMaker
30
+ def initialize(responder)
31
+ @responer = responder
32
+ end
33
+
34
+ def make(port, host)
35
+ Behaviors::Servers::ResponderServer.new(port, @responer.new, host)
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,21 @@
1
+ module Bane
2
+ module Behaviors
3
+ module Responders
4
+
5
+ # Accepts a connection, pauses a fixed duration, then closes the connection.
6
+ #
7
+ # Options:
8
+ # - duration: The number of seconds to wait before disconnect. Default: 30
9
+ class CloseAfterPause
10
+ def initialize(options = {})
11
+ @options = {duration: 30}.merge(options)
12
+ end
13
+
14
+ def serve(io)
15
+ sleep(@options[:duration])
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Bane
2
+ module Behaviors
3
+ module Responders
4
+
5
+ # Closes the connection immediately after a connection is made.
6
+ class CloseImmediately
7
+ def serve(io)
8
+ # do nothing
9
+ end
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ module Bane
2
+ module Behaviors
3
+ module Responders
4
+
5
+ # Sends a large response. Response consists of a repeated 'x' character.
6
+ #
7
+ # Options
8
+ # - length: The size in bytes of the response to send. Default: 1,000,000 bytes
9
+ class DelugeResponse
10
+ def initialize(options = {})
11
+ @options = {length: 1_000_000}.merge(options)
12
+ end
13
+
14
+ def serve(io)
15
+ length = @options[:length]
16
+
17
+ length.times { io.write('x') }
18
+ end
19
+ end
20
+
21
+ class DelugeResponseForEachLine < DelugeResponse
22
+ include ForEachLine
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ module Bane
2
+ module Behaviors
3
+ module Responders
4
+
5
+ class EchoResponse
6
+ def serve(io)
7
+ while (input = io.gets)
8
+ io.write(input)
9
+ end
10
+ io.close
11
+ end
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module Bane
2
+ module Behaviors
3
+ module Responders
4
+
5
+ EXPORTED = self.constants.map { |name| self.const_get(name) }.grep(Class)
6
+
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ module Bane
2
+ module Behaviors
3
+ module Responders
4
+
5
+ # Sends a static response.
6
+ #
7
+ # Options:
8
+ # - message: The response message to send. Default: "Hello, world!"
9
+ class FixedResponse
10
+ def initialize(options = {})
11
+ @options = {message: "Hello, world!"}.merge(options)
12
+ end
13
+
14
+ def serve(io)
15
+ io.write @options[:message]
16
+ end
17
+ end
18
+
19
+ class FixedResponseForEachLine < FixedResponse
20
+ include ForEachLine
21
+ end
22
+
23
+ end
24
+ end
25
+ end