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.
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