cukeq 0.0.1.dev

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.autotest +1 -0
  2. data/.document +5 -0
  3. data/.gitignore +21 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +73 -0
  6. data/Rakefile +68 -0
  7. data/VERSION +1 -0
  8. data/bin/cukeq +34 -0
  9. data/features/cukeq.feature +15 -0
  10. data/features/example1.feature +1 -0
  11. data/features/example2.feature +1 -0
  12. data/features/step_definitions/cukeq_steps.rb +22 -0
  13. data/features/support/cukeq_helper.rb +73 -0
  14. data/features/support/env.rb +13 -0
  15. data/features/support/report_app.rb +36 -0
  16. data/lib/cukeq.rb +45 -0
  17. data/lib/cukeq/broker.rb +65 -0
  18. data/lib/cukeq/em/system3.rb +53 -0
  19. data/lib/cukeq/job_clearer.rb +19 -0
  20. data/lib/cukeq/master.rb +146 -0
  21. data/lib/cukeq/reporter.rb +22 -0
  22. data/lib/cukeq/runner.rb +63 -0
  23. data/lib/cukeq/scenario_exploder.rb +32 -0
  24. data/lib/cukeq/scenario_runner.rb +134 -0
  25. data/lib/cukeq/scm.rb +51 -0
  26. data/lib/cukeq/scm/git_bridge.rb +36 -0
  27. data/lib/cukeq/scm/shell_svn_bridge.rb +65 -0
  28. data/lib/cukeq/scm/svn_bridge.rb +77 -0
  29. data/lib/cukeq/slave.rb +114 -0
  30. data/lib/cukeq/webapp.rb +38 -0
  31. data/spec/cukeq/broker_spec.rb +78 -0
  32. data/spec/cukeq/cukeq_spec.rb +10 -0
  33. data/spec/cukeq/master_spec.rb +153 -0
  34. data/spec/cukeq/reporter_spec.rb +29 -0
  35. data/spec/cukeq/runner_spec.rb +11 -0
  36. data/spec/cukeq/scenario_exploder_spec.rb +17 -0
  37. data/spec/cukeq/scenario_runner_spec.rb +32 -0
  38. data/spec/cukeq/scm/git_bridge_spec.rb +52 -0
  39. data/spec/cukeq/scm/svn_bridge_spec.rb +5 -0
  40. data/spec/cukeq/scm_spec.rb +48 -0
  41. data/spec/cukeq/slave_spec.rb +86 -0
  42. data/spec/cukeq/webapp_spec.rb +37 -0
  43. data/spec/spec.opts +1 -0
  44. data/spec/spec_helper.rb +12 -0
  45. metadata +228 -0
data/.autotest ADDED
@@ -0,0 +1 @@
1
+ require "autotest/redgreen"
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
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