belly 0.1.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/.belly ADDED
@@ -0,0 +1,2 @@
1
+ hub: localhost:3000
2
+ project: belly-gem
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.3.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{belly}
8
- s.version = "0.1.0"
8
+ s.version = "0.3.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Matt Wynne"]
12
- s.date = %q{2010-07-21}
12
+ s.date = %q{2010-08-16}
13
13
  s.default_executable = %q{belly}
14
14
  s.description = %q{Client app for the incredible new belly web service, coming soon.}
15
15
  s.email = %q{matt@mattwynne.net}
@@ -20,7 +20,8 @@ Gem::Specification.new do |s|
20
20
  "TODO"
21
21
  ]
22
22
  s.files = [
23
- ".document",
23
+ ".belly",
24
+ ".document",
24
25
  ".gitignore",
25
26
  "LICENSE",
26
27
  "README.rdoc",
@@ -29,9 +30,11 @@ Gem::Specification.new do |s|
29
30
  "VERSION",
30
31
  "belly.gemspec",
31
32
  "bin/belly",
33
+ "features/fakeweb.feature",
32
34
  "features/publish_scenario_results.feature",
33
35
  "features/step_definitions/belly_steps.rb",
34
36
  "features/step_definitions/cucumber_steps.rb",
37
+ "features/support/belly.rb",
35
38
  "features/support/env.rb",
36
39
  "features/support/fake_hub.rb",
37
40
  "lib/belly.rb",
@@ -39,9 +42,11 @@ Gem::Specification.new do |s|
39
42
  "lib/belly/cli/init.rb",
40
43
  "lib/belly/client.rb",
41
44
  "lib/belly/client/config.rb",
45
+ "lib/belly/client/default_config.rb",
46
+ "lib/belly/client/fakeweb_hack.rb",
47
+ "lib/belly/client/hub_proxy.rb",
42
48
  "lib/belly/for/cucumber.rb",
43
- "lib/belly/project_initializer.rb",
44
- "lib/belly/user_credentials.rb",
49
+ "lib/belly/messages/cucumber_scenario_result_message.rb",
45
50
  "spec/belly/project_initializer_spec.rb",
46
51
  "spec/spec_helper.rb"
47
52
  ]
data/bin/belly CHANGED
@@ -10,7 +10,8 @@ Trollop::options do
10
10
  banner <<-EOS
11
11
  === Commands
12
12
 
13
- init Create a new project for the current folder
13
+ init Initialize this project for use with Belly
14
+ rerun:cucumber Show Cucumber scenarios that need to be re-run
14
15
  help <command> Get some help on a particular command
15
16
 
16
17
  EOS
@@ -25,4 +26,4 @@ unless Belly::Cli::COMMANDS.include?(command)
25
26
  Trollop.die("Don't know that command, sorry")
26
27
  end
27
28
 
28
- require "belly/cli/#{command}"
29
+ require "belly/cli/#{command.gsub(':','_')}"
@@ -0,0 +1,17 @@
1
+ @announce
2
+ Feature: Fakeweb
3
+ In order to work on a codebase that uses Fakeweb,
4
+ belly needs to disable Fakeweb during it's own net communication
5
+
6
+ Scenario: Features have FakeWeb enabled with allow_net_connect = false
7
+ Given a Cucumber test suite with a single passing scenario
8
+ And Belly has been installed in the features
9
+ And a file named "features/support/fake_web.rb" with:
10
+ """
11
+ require 'fake_web'
12
+ FakeWeb.allow_net_connect = false
13
+
14
+ """
15
+ When I run the features
16
+ Then it should pass
17
+ And the hub should have received a test result
@@ -7,53 +7,75 @@ Feature: Publish scenario results
7
7
  Given a standard Cucumber project directory structure
8
8
  And a file named "features/support/belly.rb" with:
9
9
  """
10
- require 'belly' rescue puts("Could not load belly - do you need to install the gem?")
10
+ require 'belly/for/cucumber'
11
11
 
12
12
  """
13
13
 
14
14
  And a file named "features/step_definitions/foo_steps.rb" with:
15
15
  """
16
- Given /^I am a rock$/ do
16
+ When /pass/ do
17
17
  end
18
18
 
19
- Given /^I am thin ice$/ do
20
- raise("yikes")
19
+ When /fail/ do
20
+ raise("FAIL")
21
21
  end
22
22
 
23
23
  """
24
24
  And a file named ".belly" with:
25
25
  """
26
26
  hub: localhost:12345
27
+ project: test-project
27
28
 
28
29
  """
29
- And there is a belly-hub running on localhost:12345
30
+ And there is a hub running on localhost:12345
30
31
 
32
+ @announce
31
33
  Scenario: Run a test with a scenario that passes
32
34
  Given a file named "features/foo.feature" with:
33
35
  """
34
- Feature: Test
35
- Scenario: Solid
36
- Given I am a rock
36
+ Feature: Passing Feature
37
+ Scenario: Passing Scenario
38
+ When I pass
37
39
 
38
40
  """
39
- When I run cucumber -r belly -r features -v
40
- And the belly-hub should have received the following requests:
41
- | type | path | data |
42
- | POST | /scenarios | {"status":"passed","id":{"scenario":"Solid","feature":"Test"}} |
41
+ When I run cucumber features
42
+ Then the hub should have received a POST to "/test_results" with:
43
+ """
44
+ {
45
+ "id": {
46
+ "feature":"Passing Feature",
47
+ "line":"2",
48
+ "feature_file":"features/foo.feature",
49
+ "scenario":"Passing Scenario"
50
+ },
51
+ "project":"test-project",
52
+ "type":"Belly::Messages::CucumberScenarioResultMessage"
53
+ }
54
+ """
43
55
 
44
56
  Scenario: Run a test with a scenario that fails
45
57
  And a file named "features/foo.feature" with:
46
58
  """
47
- Feature: Test
48
- Scenario: Solid
49
- Given I am a rock
59
+ Feature: Passing Feature
60
+ Scenario: Passing Scenario
61
+ When I pass
50
62
 
51
- Scenario: Shaky
52
- Given I am thin ice
63
+ Scenario: Failing Scenario
64
+ When I fail
53
65
 
54
66
  """
55
67
  When I run cucumber -r belly -r features -v
56
- And the belly-hub should have received the following requests:
57
- | type | path | data |
58
- | POST | /scenarios | {"status":"passed","id":{"scenario":"Solid","feature":"Test"}} |
59
- | POST | /scenarios | {"status":"failed","id":{"scenario":"Shaky","feature":"Test"}} |
68
+ Then the hub should have received a POST to "/test_results" with:
69
+ """
70
+ {
71
+ "project":"test-project",
72
+ "status":"passed"
73
+ }
74
+ """
75
+ And the hub should have received a POST to "/test_results" with:
76
+ """
77
+ {
78
+ "project":"test-project",
79
+ "status":"failed"
80
+ }
81
+ """
@@ -1,19 +1,61 @@
1
1
  require 'aruba'
2
2
 
3
- Given /^there is a belly\-hub running on localhost:12345$/ do
4
- @hub = Belly::FakeHub.run(12345)
3
+ module RequestsHelper
4
+ def requests
5
+ @hub.requests.map do |r|
6
+ result = r.dup
7
+ result["data"] = JSON.parse(r["data"])
8
+ result
9
+ end
10
+ end
11
+
12
+ def start_hub!
13
+ @hub = Belly::FakeHub.run(12345)
14
+ end
15
+ end
16
+
17
+ World(RequestsHelper)
18
+
19
+ Given /^there is a hub running on localhost:12345$/ do
20
+ start_hub!
21
+ end
22
+
23
+ Given /^Belly has been installed in the features$/ do
24
+ start_hub!
25
+ create_file "features/support/belly.rb", <<-EOF
26
+ require 'belly/for/cucumber'
27
+ EOF
28
+ create_file ".belly", <<-EOF
29
+ hub: localhost:12345
30
+ project: test-project
31
+ EOF
5
32
  end
6
33
 
7
- Then /^the belly\-hub should have received the following requests:$/ do |table|
8
- requests = @hub.requests.map do |r|
9
- result = r.dup
10
- result["data"] = JSON.parse(r["data"])
11
- result
34
+ Then /^the hub should have received a POST to "([^"]*)" with:$/ do |path, expected_json|
35
+ expected_data = JSON.parse(expected_json)
36
+ match = false
37
+ candidates = requests.select do |request|
38
+ request["type"] == "POST" && request["path"] == path
12
39
  end
13
40
 
14
- table.map_column!('data') do |raw_data|
15
- JSON.parse(raw_data)
41
+ unless candidates.any?
42
+ raise("Couldn't find any POST requests to #{path} in: \n\n #{requests.inspect}")
16
43
  end
17
44
 
18
- table.diff! requests
19
- end
45
+ candidates.each do |request|
46
+ match = expected_data.keys.all? do |key|
47
+ request["data"][key] == expected_data[key]
48
+ end
49
+ break if match
50
+ end
51
+
52
+ unless match
53
+ raise("Couldn't find any suitable requests in:\n\n #{candidates.inspect}")
54
+ end
55
+ end
56
+
57
+ Then /^the hub should have received a test result$/ do
58
+ unless(requests.any? { |r| r["type"] == "POST" && r["path"] == "/test_results" })
59
+ raise("Couldn't find any test_results POST requests in:\n\n #{requests.inspect}")
60
+ end
61
+ end
@@ -10,3 +10,26 @@ When /^I run cucumber (.*)$/ do |cucumber_opts|
10
10
  cmd = "#{Cucumber::RUBY_BINARY} -I#{belly_lib_path} #{Cucumber::BINARY} --no-color #{cucumber_opts}"
11
11
  run(cmd, false)
12
12
  end
13
+
14
+ Given /^a Cucumber test suite with a single passing scenario$/ do
15
+ FileUtils.mkdir_p 'features/support'
16
+ FileUtils.mkdir_p 'features/step_definitions'
17
+ create_file 'features/step_definitions/steps.rb', <<-EOF
18
+ When /pass/ do
19
+ end
20
+ EOF
21
+
22
+ create_file "features/pass.feature", <<-EOF
23
+ Feature: Passing Feature
24
+ Scenario: Passing Scenario
25
+ When I pass
26
+ EOF
27
+ end
28
+
29
+ When /^I run the features$/ do
30
+ When "I run cucumber features"
31
+ end
32
+
33
+ Then /^it should pass$/ do
34
+ Then "the exit status should be 0"
35
+ end
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/../../lib/belly/for/cucumber'
@@ -34,9 +34,14 @@ module Belly
34
34
  end
35
35
 
36
36
  post '*' do
37
+ # For some reason env["PATH_INFO"] is blank, so we do this instead
38
+ host = request.env["HTTP_HOST"]
39
+ uri = request.env["REQUEST_URI"]
40
+ path = uri.match(/#{host}(.*)/).captures[0]
41
+
37
42
  Requests.add({
38
43
  'type' => request.env['REQUEST_METHOD'],
39
- 'path' => '/scenarios',
44
+ 'path' => path,
40
45
  'data' => request.body.read }
41
46
  )
42
47
  ''
@@ -1,44 +1,12 @@
1
1
  $:.push(File.expand_path(File.dirname(__FILE__)))
2
2
 
3
- require 'belly/client'
4
- require 'belly/client/config'
5
- require 'json'
6
-
7
3
  module Belly
8
4
  class << self
9
5
  def log(message)
10
6
  return unless ENV['BELLY_LOG']
11
7
  puts "* [Belly] #{message}"
12
8
  end
13
-
14
- def publish(scenario)
15
- feature_name = scenario.feature.name.split("\n").first # TODO: only needed for older cucumbers
16
- data = {
17
- :type => :cucumber_scenario_result,
18
- :id => { :feature => feature_name, :scenario => scenario.name },
19
- :status => scenario.status,
20
- :project => config.project
21
- }.to_json
22
-
23
- Belly.log("publishing #{data}")
24
-
25
- # Break out a thread so we don't slow down the tests
26
- thread = Thread.new { hub.post_test_result(data) }
27
-
28
- # Make sure the thread gets a chance to finish before the process exits
29
- at_exit { thread.join }
30
- end
31
-
32
- def config
33
- @config ||= Config.new
34
- end
35
-
36
- def user_credentials
37
- @user_credentials ||= UserCredentials.new(config)
38
- end
39
-
40
- def hub
41
- @hub ||= Belly::Client.new(config)
42
- end
43
9
  end
44
- end
10
+ end
11
+
12
+ require 'belly/client'
@@ -1,5 +1,5 @@
1
1
  module Belly
2
2
  module Cli
3
- COMMANDS = %w(init)
3
+ COMMANDS = %w(init rerun:cucumber)
4
4
  end
5
5
  end
@@ -1,15 +1,35 @@
1
- require 'belly/project_initializer'
2
-
3
1
  options = Trollop::options do
4
2
  banner <<-EOF
5
3
  This is the help for the init command. To see all commands availlable, type belly --help
6
4
 
7
- Usage: belly init <args>
5
+ Usage: belly init
8
6
 
9
- Initialize your project for use with the belly web service
7
+ Initialize your project for use with the belly service
10
8
 
11
9
  EOF
12
- opt :name, "Name of the project", :default => File.basename(Dir.pwd)
13
10
  end
14
11
 
15
- Belly::ProjectInitializer.new(options).run(Trollop)
12
+ if File.exists?('features/support')
13
+ target = 'features/support/belly.rb'
14
+ if File.exists?(target)
15
+ Trollop.die("There's already a #{target} which I was going to create. Not much for me to do")
16
+ end
17
+ File.open(target, 'w') do |f|
18
+ f.puts "require 'belly/for/cucumber'"
19
+ end
20
+ puts "Created #{target}"
21
+ puts <<-EOF
22
+
23
+ Your project is now initialized for working with Belly.
24
+
25
+ You can configure Belly's settings by creating a .belly file in root of your project.
26
+ If you don't create one, I'll just use some defaults.
27
+
28
+ Here's an example:
29
+
30
+ project: #{Belly.config.project}
31
+ user:
32
+ name: #{Belly.config.user[:name]}
33
+ email: #{Belly.config.user[:email]}
34
+ EOF
35
+ end
@@ -1,23 +1,63 @@
1
- require 'net/http'
2
- require 'uri'
3
-
4
1
  module Belly
5
- class Client
6
- def initialize(config)
7
- @config = config
8
- end
9
-
10
- def get(uri)
2
+ module Client
3
+ def publish(scenario)
4
+ return if offline?
11
5
 
12
- end
13
-
14
- def post_test_result(data)
15
- request = Net::HTTP::Post.new(@config.url + '/test_results', {'Content-Type' =>'application/json'})
16
- request.body = data
17
- response = Net::HTTP.new(@config.host, @config.port).start {|http| http.request(request) }
18
- unless response.code == "200"
19
- raise("Failed to talk to belly hub: Response #{response.code} #{response.message}: #{response.body}")
6
+ feature_name = scenario.feature.name.split("\n").first # TODO: only needed for older cucumbers
7
+ feature_file, line = scenario.file_colon_line.split(':')
8
+
9
+ data = Messages::CucumberScenarioResultMessage.new(
10
+ feature_name,
11
+ scenario.name,
12
+ scenario.status,
13
+ config.user,
14
+ config.project,
15
+ feature_file,
16
+ line).to_json
17
+
18
+ Belly.log("publishing #{data}")
19
+
20
+ # Break out a thread so we don't slow down the tests
21
+ thread = Thread.new { hub.post_test_result(data) }
22
+
23
+ # Make sure the thread gets a chance to finish before the process exits
24
+ at_exit do
25
+ begin
26
+ thread.join
27
+ rescue SocketError, Errno::ECONNREFUSED => exception
28
+ # TODO: test this
29
+ unless offline?
30
+ warn("Belly couldn't send your results to the server (#{Belly.config.url}).")
31
+ offline!
32
+ end
33
+ end
20
34
  end
21
35
  end
36
+
37
+ def config
38
+ @config ||= Config.new
39
+ end
40
+
41
+ def hub
42
+ @hub ||= HubProxy.new(config)
43
+ end
44
+
45
+ private
46
+
47
+ def offline?
48
+ @offline
49
+ end
50
+
51
+ def offline!
52
+ @offline = true
53
+ end
22
54
  end
23
- end
55
+ end
56
+
57
+ Belly.extend(Belly::Client)
58
+
59
+ require 'json'
60
+ require 'belly/client/hub_proxy'
61
+ require 'belly/client/config'
62
+ require 'belly/messages/cucumber_scenario_result_message'
63
+ require 'belly/client/fakeweb_hack'
@@ -1,13 +1,12 @@
1
- module Belly
1
+ require 'belly/client/default_config'
2
+
3
+ module Belly::Client
2
4
  class Config
3
- class NoConfig
4
- end
5
-
6
5
  attr_reader :host, :port
7
6
 
8
7
  class << self
9
8
  def new(*args)
10
- return NoConfig.new unless File.exists?(path)
9
+ return DefaultConfig.new unless File.exists?(path)
11
10
  super
12
11
  end
13
12
 
@@ -17,6 +16,7 @@ module Belly
17
16
  end
18
17
 
19
18
  def initialize
19
+ @default = DefaultConfig.new
20
20
  @host, @port = config_file['hub'].split(':')
21
21
  end
22
22
 
@@ -25,7 +25,11 @@ module Belly
25
25
  end
26
26
 
27
27
  def project
28
- config_file['project']
28
+ config_file['project'] || @default.project
29
+ end
30
+
31
+ def user
32
+ config_file['user'] || @default.user
29
33
  end
30
34
 
31
35
  private
@@ -0,0 +1,32 @@
1
+ module Belly::Client
2
+ class DefaultConfig
3
+ def project
4
+ File.basename(Dir.pwd)
5
+ end
6
+
7
+ def url
8
+ "http://#{host}:#{port}"
9
+ end
10
+
11
+ def host
12
+ "belly.heroku.com"
13
+ end
14
+
15
+ def port
16
+ 80
17
+ end
18
+
19
+ def user
20
+ {
21
+ :name => git_config('user.name'),
22
+ :email => git_config('user.email')
23
+ }
24
+ end
25
+
26
+ private
27
+
28
+ def git_config(value)
29
+ `git config --get #{value}`.strip
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ module Belly
2
+ class << self
3
+ def install_fakeweb_hack_if_necessary
4
+ return if @fakeweb_hack_installed
5
+
6
+ FakeWeb.class_eval do
7
+ def self.allow_net_connect?
8
+ return true if Thread.current == @overriding_thread
9
+ @allow_net_connect
10
+ end
11
+
12
+ def self.allowing_net_connect_in_this_thread
13
+ @overriding_thread = Thread.current
14
+ result = yield
15
+ @overriding_thread = nil
16
+ result
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ Belly.hub.around_request do |block|
24
+ if defined?(FakeWeb)
25
+ Belly.install_fakeweb_hack_if_necessary
26
+ FakeWeb.allowing_net_connect_in_this_thread(&block)
27
+ else
28
+ block.call
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ module Belly::Client
5
+ class HubProxy
6
+ def initialize(config)
7
+ @config = config
8
+ end
9
+
10
+ def post_test_result(data)
11
+ request = Net::HTTP::Post.new(@config.url + '/test_results', {'Content-Type' =>'application/json'})
12
+ request.body = data
13
+ response = Net::HTTP.new(@config.host, @config.port).start do |http|
14
+ if @around_request_block
15
+ block = Proc.new { http.request(request) }
16
+ @around_request_block.call(block)
17
+ else
18
+ http.request(request)
19
+ end
20
+ end
21
+ unless response.code == "200"
22
+ raise("Failed to talk to belly hub: Response #{response.code} #{response.message}: #{response.body}")
23
+ end
24
+ end
25
+
26
+ def around_request(&block)
27
+ @around_request_block = block
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ module Belly
2
+ module Messages
3
+ class CucumberScenarioResultMessage
4
+ def initialize(feature_name, scenario_name, status, user, project_name, feature_file, line)
5
+ @data = {
6
+ :type => self.class.name,
7
+ :id => {
8
+ :feature => feature_name,
9
+ :scenario => scenario_name,
10
+ :feature_file => feature_file,
11
+ :line => line
12
+ },
13
+ :status => status,
14
+ :user => user,
15
+ :project => project_name
16
+ }
17
+ end
18
+
19
+ def to_json
20
+ @data.to_json
21
+ end
22
+ end
23
+ end
24
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: belly
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 0
10
- version: 0.1.0
8
+ - 3
9
+ - 2
10
+ version: 0.3.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Matt Wynne
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-07-21 00:00:00 +01:00
18
+ date: 2010-08-16 00:00:00 +01:00
19
19
  default_executable: belly
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -57,6 +57,7 @@ extra_rdoc_files:
57
57
  - README.rdoc
58
58
  - TODO
59
59
  files:
60
+ - .belly
60
61
  - .document
61
62
  - .gitignore
62
63
  - LICENSE
@@ -66,9 +67,11 @@ files:
66
67
  - VERSION
67
68
  - belly.gemspec
68
69
  - bin/belly
70
+ - features/fakeweb.feature
69
71
  - features/publish_scenario_results.feature
70
72
  - features/step_definitions/belly_steps.rb
71
73
  - features/step_definitions/cucumber_steps.rb
74
+ - features/support/belly.rb
72
75
  - features/support/env.rb
73
76
  - features/support/fake_hub.rb
74
77
  - lib/belly.rb
@@ -76,9 +79,11 @@ files:
76
79
  - lib/belly/cli/init.rb
77
80
  - lib/belly/client.rb
78
81
  - lib/belly/client/config.rb
82
+ - lib/belly/client/default_config.rb
83
+ - lib/belly/client/fakeweb_hack.rb
84
+ - lib/belly/client/hub_proxy.rb
79
85
  - lib/belly/for/cucumber.rb
80
- - lib/belly/project_initializer.rb
81
- - lib/belly/user_credentials.rb
86
+ - lib/belly/messages/cucumber_scenario_result_message.rb
82
87
  - spec/belly/project_initializer_spec.rb
83
88
  - spec/spec_helper.rb
84
89
  has_rdoc: true
@@ -1,30 +0,0 @@
1
- require 'belly/user_credentials'
2
- require 'belly/client'
3
-
4
- module Belly
5
- class ProjectInitializer
6
- def initialize(options, ui)
7
- project_name = options[:name] or raise("Need a :name in the options")
8
- @uri = "/projects/#{project_name}"
9
- @ui = ui
10
- end
11
-
12
- def run
13
- client.get(@uri, self)
14
- end
15
-
16
- def not_found
17
- client.put(@uri)
18
- end
19
-
20
- def not_authorized
21
- @ui.die("You don't have access to this project. You'll need to contact one of the project's collaborators and ask them to give you access.")
22
- end
23
-
24
- private
25
-
26
- def client
27
- @client ||= Client.new(Belly.user_credentials)
28
- end
29
- end
30
- end
@@ -1,7 +0,0 @@
1
- module Belly
2
- class UserCredentials
3
- def initialize(config)
4
-
5
- end
6
- end
7
- end