em-http-test 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "eventmachine"
4
+ gem "em-http-request"
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ em-http-test
2
+ ============
3
+
4
+ Ruby EventMachine-base HTTP load testing library that allows writing simple web application testing scenarios
5
+ that are run using high performance asynchronous IO powered by EventMachine and em-http-request.
6
+
7
+ Requirements are EventMachine, em-http-request and fiber.
8
+
9
+ Usage is very simple, for example:
10
+
11
+ #!/usr/bin/ruby
12
+
13
+ require 'em-http-test'
14
+
15
+ concurrency = 10000
16
+ runtime = 300
17
+
18
+ p EM::HttpTest::run(concurrency, runtime) do
19
+ response = EM::HttpTest::post('http://mytestapp/login') :query => { 'username' => 'oded', 'password' => '123' }
20
+ sessionid = response['PHPSESSIONID']
21
+ raise EM::HttpTest::TestFailure, "Error in login" unless response.response_header.status == 200
22
+ response = EM::HttpTest::get('http://mytestapp/list') :query => { 'filter' => 'all' },
23
+ :head { 'PHPSESSIONID' => sessionid }
24
+ raise EM::HttpTest::TestFailure, "Error in list" unless response.response_header.status == 200
25
+ end
26
+
27
+ The block passed to `run()` will be executed continously for 300 seconds, with 10,000 sessions running simoultaneously.
28
+ The `EventMachine::HttpTest::post` and `EventMachine::HttpTest::get` are used to dispatch the HTTP requests in an
29
+ apparently synchronous manner allowing test sessions to be written using a simple programming model.
30
+
31
+ The named arguments passed to `post()` and `get()` are passed to em-http-request and any arguments supported by
32
+ em-http-request can be used. To facilitate ease of use, `HttpTest` will convert `:query` data to `:body` content
33
+ when using `post()`. The return value for `post()` and `get()` is the em-http-request client with the response
34
+ data, which can be examined according to the em-http-request API.
35
+
36
+ To abort a testing session without aborting the entire load test, raise `EventMachine::HttpTest::TestFailure`. All such
37
+ errors will be collected, the aborted session will be counted as a test failure and the exception data will be available
38
+ in the test results summary.
39
+
40
+ The return value from `run()` is a hash with the following fields:
41
+
42
+ * `:total` - total number of sessions that were run
43
+ * `:success` - total number of sessions that completed without raising a TestFailure
44
+ * `:failure` - total number of sessions that raised a TestFailure
45
+ * `:failures` - array containing the exception data element for each failed session
46
+ * `:failure_rate` - the rate of session failures compared to completed sessions (0 to 1)
47
+ * `:min` - the number of seconds that the fastest session completed in
48
+ * `:max` - the number of seconds that the slowest session completed in
49
+ * `:average` - average number of seconds for all test sessions (including failures)
50
+ * `:percentile95` - 95% of the sessions completed in this number of seconds or less
51
+
52
+ Results in seconds could be fractional seconds.
@@ -0,0 +1,26 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ require 'em-http-test/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "em-http-test"
7
+ s.version = EventMachine::HttpTest::VERSION
8
+
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Oded Arbel"]
11
+ s.email = ["oded@heptagon.co.il"]
12
+ s.homepage = "https://github.com/guss77/em-http-test"
13
+ s.summary = "EventMachine based, high performance web application load test framework"
14
+ s.description = s.summary
15
+ #s.rubyforge_project = "em-http-test"
16
+
17
+ #s.add_dependency "eventmachine", ">= 0.12.0"
18
+ s.add_dependency "em-http-request", ">= 0.3.0"
19
+
20
+ s.add_development_dependency "rspec"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ #s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,13 @@
1
+ # EventMachine based HTTP load testing tool
2
+ #
3
+ # Author:: Oded Arbel (mailto:oded@heptagon.co.il)
4
+ # Copyright:: Copyright (c) 2013 Heptagon Software
5
+ # License:: GPL v3
6
+
7
+ # define name space modules
8
+
9
+ module EventMachine
10
+ end
11
+
12
+ module EventMachine::HttpTest
13
+ end
@@ -0,0 +1,74 @@
1
+ # EventMachine based HTTP load testing tool
2
+ #
3
+ # Author:: Oded Arbel (mailto:oded@heptagon.co.il)
4
+ # Copyright:: Copyright (c) 2013 Heptagon Software
5
+ # License:: GPL v3
6
+
7
+ module EventMachine::HttpTest
8
+
9
+ # Run the specified test in the specified concurrency
10
+ class LoadTest
11
+ include EM::Deferrable
12
+
13
+ def initialize(concurrency, &block)
14
+ @concurrency = concurrency
15
+ @runners = [] # list of currently running tests
16
+ @failureCB = [] # list of failure callbacks
17
+ @successCB = [] # list of success callbacks
18
+ @test = block
19
+ @running = true
20
+
21
+ @concurrency.times { self.startTest }
22
+ end
23
+
24
+ # stop the test and don't execute any more test runners
25
+ def abort
26
+ @running = false
27
+ end
28
+
29
+ # register a failure handler
30
+ def onfailure(&block)
31
+ @failureCB << block
32
+ end
33
+
34
+ # register a success handler
35
+ def onsuccess(&block)
36
+ @successCB << block
37
+ end
38
+
39
+ def count
40
+ return @runners.size
41
+ end
42
+
43
+ # Add a new test runner
44
+ def startTest
45
+ return unless @running # don't start new tests if the EventMachine is stopping
46
+ startAt = Time.new
47
+ # create a new runner and add it to the queue
48
+ runner = TestRunner.new(Fiber.new { @test.call })
49
+ @runners << runner
50
+ runner.errback do |e| # if the runner failed
51
+ @runners.delete(runner) # remove it from the queue
52
+ @failureCB.each { |b| b.call(Time.new - startAt, e) } # notify the failure handler
53
+ self.tick # then tick the load test
54
+ end
55
+ runner.callback do # if the runnner completed
56
+ @runners.delete(runner) # remove it from the queue
57
+ @successCB.each { |b| b.call(Time.new - startAt) } # notify the success handler
58
+ self.tick # then tick the load test
59
+ end
60
+ end
61
+
62
+ # Replenish test runners
63
+ def tick
64
+ if !@running and @runners.count < 1
65
+ EM.stop
66
+ return
67
+ end
68
+ missing = @concurrency - @runners.count
69
+ missing.times { self.startTest }
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,20 @@
1
+ # EventMachine based HTTP load testing tool
2
+ #
3
+ # Author:: Oded Arbel (mailto:oded@heptagon.co.il)
4
+ # Copyright:: Copyright (c) 2013 Heptagon Software
5
+ # License:: GPL v3
6
+
7
+ module EventMachine::HttpTest
8
+
9
+ # Exception to raise in test code to signify a test failure
10
+ class TestFailure < Exception
11
+
12
+ attr_accessor :message
13
+
14
+ def initialize(text)
15
+ self.message = text;
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,42 @@
1
+ # EventMachine based HTTP load testing tool
2
+ #
3
+ # Author:: Oded Arbel (mailto:oded@heptagon.co.il)
4
+ # Copyright:: Copyright (c) 2013 Heptagon Software
5
+ # License:: GPL v3
6
+
7
+ module EventMachine::HttpTest
8
+
9
+ # Base test request object that contains the test data
10
+ class Request
11
+
12
+ def initialize(type, url, options = {})
13
+ @type = type
14
+ @url = url
15
+ @options = options
16
+ if @type == :POST and !@options[:query].nil?
17
+ @options[:body] = @options[:query]
18
+ @options.delete(:query)
19
+ end
20
+ end
21
+
22
+ def toRequest
23
+ case @type
24
+ when :GET
25
+ EM::HttpRequest.new(@url).get @options
26
+ when :POST
27
+ EM::HttpRequest.new(@url).post @options
28
+ end
29
+ end
30
+ end
31
+
32
+ # do a evented HTTP GET
33
+ def self.get(url, options = {})
34
+ Fiber.yield Request.new(:GET, url, options)
35
+ end
36
+
37
+ # do a evented HTTP POST
38
+ def self.post(url, options = {})
39
+ Fiber.yield Request.new(:POST, url, options)
40
+ end
41
+
42
+ end
@@ -0,0 +1,53 @@
1
+ # EventMachine based HTTP load testing tool
2
+ #
3
+ # Author:: Oded Arbel (mailto:oded@heptagon.co.il)
4
+ # Copyright:: Copyright (c) 2013 Heptagon Software
5
+ # License:: GPL v3
6
+
7
+ # Run a single HTTP test by iterating through the test operatins until the test
8
+ # either raises TestFailure or completes successfully
9
+ module EventMachine::HttpTest
10
+
11
+ class TestRunner
12
+ include EM::Deferrable
13
+
14
+ def initialize(f)
15
+ @fiber = f
16
+ @testreq = @fiber.resume # first call, so no data to test
17
+ process
18
+ end
19
+
20
+ # Run a step of the test by calling the test generated URL
21
+ # and submitting the results to be processed
22
+ def process
23
+ unless @fiber.alive?
24
+ # if the fiber has completed, then we succeeded
25
+ self.succeed()
26
+ return
27
+ end
28
+
29
+ # do another run
30
+ request = @testreq.toRequest
31
+ request.errback { |r| self.fail("HTTP error calling #{r.last_effective_url}: #{r.response_header.status}") }
32
+ request.callback do |r|
33
+ if r.response_header.status >= 400
34
+ self.fail("HTTP error #{r.response_header.status}")
35
+ next
36
+ end
37
+
38
+ begin
39
+ @testreq = @fiber.resume r
40
+ case when @testreq.nil?
41
+ self.succeed("Test is done")
42
+ else
43
+ self.process
44
+ end
45
+ rescue TestFailure => e
46
+ self.fail(e.message)
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,5 @@
1
+ module EventMachine
2
+ class HttpTest
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,57 @@
1
+ # EventMachine based HTTP load testing tool
2
+ #
3
+ # Author:: Oded Arbel (mailto:oded@heptagon.co.il)
4
+ # Copyright:: Copyright (c) 2013 Heptagon Software
5
+ # License:: GPL v3
6
+
7
+ require 'rubygems'
8
+ require 'eventmachine'
9
+ require 'em-http'
10
+ require 'fiber'
11
+
12
+ require 'em-http-test/definitions'
13
+ require 'em-http-test/test-failure'
14
+ require 'em-http-test/test-runner'
15
+ require 'em-http-test/load-test'
16
+ require 'em-http-test/test-request'
17
+
18
+ module EventMachine::HttpTest
19
+
20
+ # run a load test by executing the test block multiple times in the
21
+ # specified concurrency. The test will be executed for a total of runtime seconds
22
+ def self.run(concurrency, runtime, &block)
23
+ stats = {
24
+ :total => 0,
25
+ :success => 0,
26
+ :failure => 0,
27
+ :failures => [],
28
+ }
29
+
30
+ successTimes = []
31
+
32
+ if block.nil?
33
+ stats[:error] = 'Invalid test block'
34
+ return stats
35
+ end
36
+
37
+ startTime = Time.new
38
+
39
+ EventMachine.run do
40
+ test = LoadTest.new(concurrency) { block.call }
41
+ test.onsuccess { |t| stats[:success] += 1; stats[:total] += 1; successTimes << t }
42
+ test.onfailure { |t,e| stats[:failure] += 1; stats[:total] += 1; successTimes << t; stats[:failures] << e }
43
+ EventMachine.add_periodic_timer(1) { puts "#{stats[:total]} tests run in #{(Time.new - startTime).to_i} seconds" }
44
+ EventMachine.add_timer(runtime) { test.abort }
45
+ end
46
+
47
+ successTimes = successTimes.sort
48
+ stats[:average] = (successTimes.reduce(:+).to_f / successTimes.size).round(2)
49
+ stats[:percentile95] = (successTimes[(successTimes.count*0.95).to_i]).round(2)
50
+ stats[:min] = (successTimes[0]).round(2)
51
+ stats[:max] = (successTimes[-1]).round(2)
52
+ stats[:failure_rate] = (stats[:failure].to_f / stats[:total]).round(2)
53
+
54
+ return stats
55
+ end
56
+
57
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-http-test
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Oded Arbel
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: em-http-request
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.3.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.3.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: EventMachine based, high performance web application load test framework
47
+ email:
48
+ - oded@heptagon.co.il
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - Gemfile
54
+ - README.md
55
+ - em-http-test.gemspec
56
+ - lib/em-http-test.rb
57
+ - lib/em-http-test/definitions.rb
58
+ - lib/em-http-test/load-test.rb
59
+ - lib/em-http-test/test-failure.rb
60
+ - lib/em-http-test/test-request.rb
61
+ - lib/em-http-test/test-runner.rb
62
+ - lib/em-http-test/version.rb
63
+ homepage: https://github.com/guss77/em-http-test
64
+ licenses: []
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 1.8.23
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: EventMachine based, high performance web application load test framework
87
+ test_files: []