em-http-test 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/README.md +52 -0
- data/em-http-test.gemspec +26 -0
- data/lib/em-http-test/definitions.rb +13 -0
- data/lib/em-http-test/load-test.rb +74 -0
- data/lib/em-http-test/test-failure.rb +20 -0
- data/lib/em-http-test/test-request.rb +42 -0
- data/lib/em-http-test/test-runner.rb +53 -0
- data/lib/em-http-test/version.rb +5 -0
- data/lib/em-http-test.rb +57 -0
- metadata +87 -0
data/Gemfile
ADDED
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
|
data/lib/em-http-test.rb
ADDED
@@ -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: []
|