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 +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: []
|