cukeq 0.0.1.dev
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/.autotest +1 -0
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +73 -0
- data/Rakefile +68 -0
- data/VERSION +1 -0
- data/bin/cukeq +34 -0
- data/features/cukeq.feature +15 -0
- data/features/example1.feature +1 -0
- data/features/example2.feature +1 -0
- data/features/step_definitions/cukeq_steps.rb +22 -0
- data/features/support/cukeq_helper.rb +73 -0
- data/features/support/env.rb +13 -0
- data/features/support/report_app.rb +36 -0
- data/lib/cukeq.rb +45 -0
- data/lib/cukeq/broker.rb +65 -0
- data/lib/cukeq/em/system3.rb +53 -0
- data/lib/cukeq/job_clearer.rb +19 -0
- data/lib/cukeq/master.rb +146 -0
- data/lib/cukeq/reporter.rb +22 -0
- data/lib/cukeq/runner.rb +63 -0
- data/lib/cukeq/scenario_exploder.rb +32 -0
- data/lib/cukeq/scenario_runner.rb +134 -0
- data/lib/cukeq/scm.rb +51 -0
- data/lib/cukeq/scm/git_bridge.rb +36 -0
- data/lib/cukeq/scm/shell_svn_bridge.rb +65 -0
- data/lib/cukeq/scm/svn_bridge.rb +77 -0
- data/lib/cukeq/slave.rb +114 -0
- data/lib/cukeq/webapp.rb +38 -0
- data/spec/cukeq/broker_spec.rb +78 -0
- data/spec/cukeq/cukeq_spec.rb +10 -0
- data/spec/cukeq/master_spec.rb +153 -0
- data/spec/cukeq/reporter_spec.rb +29 -0
- data/spec/cukeq/runner_spec.rb +11 -0
- data/spec/cukeq/scenario_exploder_spec.rb +17 -0
- data/spec/cukeq/scenario_runner_spec.rb +32 -0
- data/spec/cukeq/scm/git_bridge_spec.rb +52 -0
- data/spec/cukeq/scm/svn_bridge_spec.rb +5 -0
- data/spec/cukeq/scm_spec.rb +48 -0
- data/spec/cukeq/slave_spec.rb +86 -0
- data/spec/cukeq/webapp_spec.rb +37 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +12 -0
- metadata +228 -0
data/.autotest
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "autotest/redgreen"
|
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009-2010 Jari Bakken
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
= cukeq
|
2
|
+
|
3
|
+
This code is not in a usable state yet, contributions welcome!
|
4
|
+
|
5
|
+
This project will some time in the future provide a simple way of distributing
|
6
|
+
Cucumber features across machines and collect the results.
|
7
|
+
|
8
|
+
= Dependencies
|
9
|
+
|
10
|
+
You need to have an AMQP broker (like RabbitMQ) installed and running:
|
11
|
+
|
12
|
+
$ brew install rabbitmq
|
13
|
+
$ ruby -rubygems bin/cukeq setup
|
14
|
+
$ rabbitmq-server
|
15
|
+
|
16
|
+
If you want to use CukeQ with Subversion, you'll need to install the Ruby bindings:
|
17
|
+
|
18
|
+
$ sudo apt-get install libsvn-ruby
|
19
|
+
|
20
|
+
= Interface
|
21
|
+
|
22
|
+
$ cukeq master --broker URI --report-to URI --scm URI
|
23
|
+
$ cukeq slave --broker URI
|
24
|
+
|
25
|
+
= Master
|
26
|
+
|
27
|
+
1. REST service to trigger runs, payload:
|
28
|
+
{:features => ["file:line", "file:line", ...]}
|
29
|
+
2. Update from SCM.
|
30
|
+
3. Call Cucumber to get «exploded scenarios» (JSON) - (shell out to cucumber with custom formatter + --dry-run ??).
|
31
|
+
4. Create JSON payload
|
32
|
+
[
|
33
|
+
{
|
34
|
+
:run_id => id,
|
35
|
+
:scm => {:revision => 1234, :url => "git://..."}
|
36
|
+
:exploded_scenario => ast(?)
|
37
|
+
},
|
38
|
+
...
|
39
|
+
]
|
40
|
+
5. Put payload on jobs queue.
|
41
|
+
6. Pull step result from result queue.
|
42
|
+
7. POST step result to --report-to URL, and/or let several reporters register themselves with the Master webapp (webhookish)
|
43
|
+
|
44
|
+
= Slave
|
45
|
+
|
46
|
+
1. Pull job from job queue.
|
47
|
+
2. If job revision != last revision
|
48
|
+
* Update/checkout from SCM.
|
49
|
+
* Restart Cucumber process with updated code files.
|
50
|
+
3. Invoke steps (through wire protocol?)
|
51
|
+
4. Put step/scenario/unit (undecided) result on result queue.
|
52
|
+
|
53
|
+
= Other
|
54
|
+
|
55
|
+
- All parsing is done on the master - the slave shouldn't have to parse .feature files.
|
56
|
+
- The slave should be able to invoke cucumber steps implemented in any of the supported languages
|
57
|
+
- Need a (separate?) webapp to select which features to run and show the results as the step results come in. (a --report-to recipient)
|
58
|
+
- Should also be able to trigger runs from command line (`cukeq-runner file:line file:line`, prints `http://app/runs/«run id»`)
|
59
|
+
|
60
|
+
|
61
|
+
== Note on Patches/Pull Requests
|
62
|
+
|
63
|
+
* Fork the project.
|
64
|
+
* Make your feature addition or bug fix.
|
65
|
+
* Add tests for it. This is important so I don't break it in a
|
66
|
+
future version unintentionally.
|
67
|
+
* Commit, do not mess with rakefile, version, or history.
|
68
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
69
|
+
* Send me a pull request. Bonus points for topic branches.
|
70
|
+
|
71
|
+
== Copyright
|
72
|
+
|
73
|
+
Copyright (c) 2009 Jari Bakken. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |g|
|
7
|
+
g.name = "cukeq"
|
8
|
+
g.summary = %Q{Distributed cucumbers}
|
9
|
+
g.description = %Q{Cucumber features distributed using AMQP.}
|
10
|
+
g.email = "jari.bakken@gmail.com"
|
11
|
+
g.homepage = "http://github.com/jarib/cukeq"
|
12
|
+
g.authors = ["Jari Bakken"]
|
13
|
+
|
14
|
+
g.add_dependency "amqp"
|
15
|
+
g.add_dependency "thin"
|
16
|
+
g.add_dependency "json"
|
17
|
+
g.add_dependency "git"
|
18
|
+
|
19
|
+
g.add_development_dependency "rspec", ">= 2.0.0"
|
20
|
+
g.add_development_dependency "yard", ">= 0"
|
21
|
+
g.add_development_dependency "cucumber", ">= 0"
|
22
|
+
g.add_development_dependency "rack-test", ">= 0"
|
23
|
+
end
|
24
|
+
Jeweler::GemcutterTasks.new
|
25
|
+
rescue LoadError
|
26
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
|
31
|
+
task :default => ["spec:coverage", "spec:coverage:verify"]
|
32
|
+
|
33
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
34
|
+
t.rspec_opts = ["--color", "--format", "progress"]
|
35
|
+
t.pattern = 'spec/**/*_spec.rb'
|
36
|
+
t.rcov = false
|
37
|
+
end
|
38
|
+
|
39
|
+
namespace :spec do
|
40
|
+
RSpec::Core::RakeTask.new(:coverage) do |t|
|
41
|
+
t.rspec_opts = ["--color", "--format", "progress"]
|
42
|
+
t.pattern = 'spec/**/*_spec.rb'
|
43
|
+
t.rcov = true
|
44
|
+
t.rcov_opts = ['--exclude-only', '".*"', '--include-file', '^lib']
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
task :spec => :check_dependencies
|
49
|
+
|
50
|
+
begin
|
51
|
+
require 'cucumber/rake/task'
|
52
|
+
Cucumber::Rake::Task.new(:features)
|
53
|
+
|
54
|
+
task :features => :check_dependencies
|
55
|
+
rescue LoadError
|
56
|
+
task :features do
|
57
|
+
abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
begin
|
62
|
+
require 'yard'
|
63
|
+
YARD::Rake::YardocTask.new
|
64
|
+
rescue LoadError
|
65
|
+
task :yardoc do
|
66
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
67
|
+
end
|
68
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1.dev
|
data/bin/cukeq
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') # TODO: remove this
|
4
|
+
require 'cukeq'
|
5
|
+
|
6
|
+
case ARGV.shift
|
7
|
+
when 'slave'
|
8
|
+
CukeQ::Slave.execute(ARGV.dup)
|
9
|
+
when 'master'
|
10
|
+
CukeQ::Master.execute(ARGV.dup)
|
11
|
+
when 'run'
|
12
|
+
CukeQ::Runner.execute(ARGV.dup)
|
13
|
+
when 'clear-jobs'
|
14
|
+
CukeQ::JobClearer.execute(ARGV.dup)
|
15
|
+
when 'setup'
|
16
|
+
# TODO - make password configurable
|
17
|
+
|
18
|
+
puts `rabbitmqctl add_vhost /cukeq`
|
19
|
+
|
20
|
+
# create users with password 'cukeq123'
|
21
|
+
%w[cukeq-master cukeq-slave].each do |user|
|
22
|
+
puts `rabbitmqctl add_user #{user} cukeq123`
|
23
|
+
# puts `rabbitmqctl map_user_vhost #{user} /cukeq`
|
24
|
+
|
25
|
+
# the three regex's map to config, write, read permissions respectively
|
26
|
+
puts `rabbitmqctl set_permissions -p /cukeq #{user} ".*" ".*" ".*"`
|
27
|
+
end
|
28
|
+
|
29
|
+
puts `rabbitmqctl list_users`
|
30
|
+
puts `rabbitmqctl list_vhosts`
|
31
|
+
puts `rabbitmqctl list_permissions -p /cukeq`
|
32
|
+
else
|
33
|
+
abort "USAGE: #{File.basename $0} [slave|master|run|setup]"
|
34
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Feature: Distributed cucumber
|
2
|
+
In order run my cukes quickly
|
3
|
+
As a Cucumber user
|
4
|
+
I want to distribute my cukes
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given I have a master and 2 slaves running
|
8
|
+
And a report app is running
|
9
|
+
|
10
|
+
Scenario: Distribute 2 features
|
11
|
+
When I POST the following list to the master web service:
|
12
|
+
| features/example1.feature |
|
13
|
+
| features/example2.feature |
|
14
|
+
Then the report app should know when the run has finished
|
15
|
+
And I should be able to GET the results
|
@@ -0,0 +1 @@
|
|
1
|
+
Feature: feature 1
|
@@ -0,0 +1 @@
|
|
1
|
+
Feature: feature 2
|
@@ -0,0 +1,22 @@
|
|
1
|
+
Given /^I have a master and (\d+) slaves running$/ do |n|
|
2
|
+
start_master
|
3
|
+
n.to_i.times { start_slave }
|
4
|
+
end
|
5
|
+
|
6
|
+
Given /^a report app is running$/ do
|
7
|
+
start_report_app
|
8
|
+
end
|
9
|
+
|
10
|
+
Given /^I POST the following list to the master web service:$/ do |table|
|
11
|
+
features = table.raw.flatten
|
12
|
+
post(master_url, features.to_json)
|
13
|
+
end
|
14
|
+
|
15
|
+
Then /^the report app should know when the run has finished$/ do
|
16
|
+
sleep 5 # heh.
|
17
|
+
end
|
18
|
+
|
19
|
+
Then /^I should be able to GET the results$/ do
|
20
|
+
json = get(report_app.url + "/results")
|
21
|
+
JSON.parse(json).size.should == 2
|
22
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "net/http"
|
2
|
+
|
3
|
+
module CukeQHelper
|
4
|
+
|
5
|
+
MASTER_ARGS = %w[--scm git://github.com/jarib/cukeq.git --report-to http://localhost:1212]
|
6
|
+
|
7
|
+
def cleanup
|
8
|
+
pids.each { |pid| Process.kill(:KILL, pid) }
|
9
|
+
Process.wait
|
10
|
+
end
|
11
|
+
|
12
|
+
def start_master
|
13
|
+
pids << fork { CukeQ::Master.execute(MASTER_ARGS) }
|
14
|
+
ensure_running
|
15
|
+
end
|
16
|
+
|
17
|
+
def start_slave
|
18
|
+
pids << fork { CukeQ::Slave.execute }
|
19
|
+
ensure_running
|
20
|
+
end
|
21
|
+
|
22
|
+
def start_report_app
|
23
|
+
app = report_app()
|
24
|
+
|
25
|
+
pids << fork { app.start }
|
26
|
+
ensure_running
|
27
|
+
end
|
28
|
+
|
29
|
+
def post(url, data)
|
30
|
+
uri = URI.parse(url)
|
31
|
+
req = Net::HTTP::Post.new(uri.path)
|
32
|
+
req.body = data
|
33
|
+
|
34
|
+
execute_request uri, req
|
35
|
+
end
|
36
|
+
|
37
|
+
def get(url)
|
38
|
+
uri = URI.parse(url)
|
39
|
+
execute_request uri, Net::HTTP::Get.new(uri.path)
|
40
|
+
end
|
41
|
+
|
42
|
+
def master_url
|
43
|
+
"http://localhost:9292/"
|
44
|
+
end
|
45
|
+
|
46
|
+
def ensure_running
|
47
|
+
3.times do
|
48
|
+
pid, status = Process.waitpid2(pids.last, Process::WNOHANG)
|
49
|
+
raise "process died: #{status.inspect}" if pid
|
50
|
+
sleep 0.5
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def report_app
|
55
|
+
@report_app ||= ReportApp.new
|
56
|
+
end
|
57
|
+
|
58
|
+
def pids
|
59
|
+
@pids ||= []
|
60
|
+
end
|
61
|
+
|
62
|
+
def execute_request(url, req)
|
63
|
+
res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }
|
64
|
+
|
65
|
+
case res
|
66
|
+
when Net::HTTPSuccess
|
67
|
+
res.body
|
68
|
+
else
|
69
|
+
res.error!
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'cukeq'
|
5
|
+
require 'spec/expectations'
|
6
|
+
require File.dirname(__FILE__) + "/report_app"
|
7
|
+
require File.dirname(__FILE__) + "/cukeq_helper"
|
8
|
+
|
9
|
+
World(CukeQHelper)
|
10
|
+
|
11
|
+
After do
|
12
|
+
cleanup
|
13
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class ReportApp
|
2
|
+
|
3
|
+
def url
|
4
|
+
"http://localhost:#{port}"
|
5
|
+
end
|
6
|
+
|
7
|
+
def port
|
8
|
+
1212
|
9
|
+
end
|
10
|
+
|
11
|
+
def start
|
12
|
+
Rack::Handler::Thin.run(self, :Port => port)
|
13
|
+
end
|
14
|
+
|
15
|
+
def results
|
16
|
+
@results ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
r = Rack::Request.new(env)
|
21
|
+
if r.post?
|
22
|
+
results << JSON.parse(r.body.read)
|
23
|
+
elsif r.get?
|
24
|
+
case r.path
|
25
|
+
when "/results"
|
26
|
+
return [200, {}, [results.to_json]]
|
27
|
+
when "/clear"
|
28
|
+
results.clear
|
29
|
+
else
|
30
|
+
return [404, {}, []]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
[200, {}, []]
|
35
|
+
end
|
36
|
+
end
|
data/lib/cukeq.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require "amqp"
|
2
|
+
require "mq"
|
3
|
+
require "rack/handler/thin"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
require "uri"
|
7
|
+
require "etc"
|
8
|
+
require "pp"
|
9
|
+
require "optparse"
|
10
|
+
require "socket"
|
11
|
+
|
12
|
+
require "cukeq/em/system3"
|
13
|
+
|
14
|
+
require "cukeq/broker"
|
15
|
+
require "cukeq/webapp"
|
16
|
+
require "cukeq/scm"
|
17
|
+
require "cukeq/scm/git_bridge"
|
18
|
+
|
19
|
+
begin
|
20
|
+
require "cukeq/scm/svn_bridge"
|
21
|
+
rescue LoadError
|
22
|
+
require "cukeq/scm/shell_svn_bridge"
|
23
|
+
end
|
24
|
+
|
25
|
+
require "cukeq/reporter"
|
26
|
+
require "cukeq/scenario_exploder"
|
27
|
+
require "cukeq/scenario_runner"
|
28
|
+
|
29
|
+
require "cukeq/master"
|
30
|
+
require "cukeq/slave"
|
31
|
+
require "cukeq/job_clearer"
|
32
|
+
require "cukeq/runner"
|
33
|
+
|
34
|
+
module CukeQ
|
35
|
+
def self.identifier
|
36
|
+
@identifier ||= "#{Socket.gethostname}-#{Etc.getlogin}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def log(*args)
|
41
|
+
str = [Time.now, *args].map { |e| e.inspect }.join " | "
|
42
|
+
|
43
|
+
$stdout.puts str
|
44
|
+
$stdout.flush
|
45
|
+
end
|