belly 0.1.0 → 0.3.2

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