limited_red 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2010 Joseph Wilk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ = Limited Red Client
2
+
3
+ == Docs
4
+
5
+ https://github.com/josephwilk/limited-red-client/wikis
6
+
7
+ == Running tests
8
+
9
+ rake
10
+
11
+ == Authors
12
+ * Joseph Wilk <joe@josephwilk.net>
13
+
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010,2011 Joseph Wilk. See LICENSE for details.
@@ -0,0 +1,26 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'rubygems'
4
+ require 'httparty'
5
+ require 'cucumber'
6
+
7
+ module LimitedRed
8
+ ROOT = File.expand_path('..', __FILE__)
9
+
10
+ autoload :Gzip, "#{ROOT}/limited_red/gzip"
11
+ autoload :Client, "#{ROOT}/limited_red/client"
12
+
13
+ autoload :ThreadPool, "#{ROOT}/limited_red/thread_pool"
14
+ autoload :FakeThreadPool, "#{ROOT}/limited_red/thread_pool"
15
+
16
+ autoload :Stats, "#{ROOT}/limited_red/stats"
17
+ autoload :Config, "#{ROOT}/limited_red/config"
18
+
19
+ module Adapter
20
+ autoload :HttParty, "#{ROOT}/limited_red/adapter/httparty"
21
+ end
22
+
23
+ module Formatter
24
+ autoload :Stats, "#{ROOT}/limited_red/formatter/stats"
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ module LimitedRed
2
+ module Adapter
3
+ class HttParty
4
+ def self.new(config, options = {})
5
+ host = config['host'] || 'https://limited-red-data-service.heroku.com/'
6
+ port = config['port'] || ''
7
+ uri = "#{host}#{port == '' ? '' : ":#{port}"}"
8
+
9
+ HttPartyAdapter.base_uri(uri)
10
+ HttPartyAdapter
11
+ end
12
+ end
13
+
14
+ class HttPartyAdapter
15
+ include HTTParty
16
+
17
+ def self.encode_and_compress(data)
18
+ compressed_result = Gzip.compress(data)
19
+ Base64.encode64(compressed_result)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,74 @@
1
+ module LimitedRed
2
+ class Client
3
+ def initialize(config, out = STDOUT, adapter = nil, thread_pool = ThreadPool)
4
+ @config ||= config
5
+ @out = out
6
+ @project_id = @config['project name']
7
+ @username = @config['username']
8
+ @api_key = @config['api key']
9
+
10
+ @adapter = adapter || LimitedRed::Adapter::HttParty.new(@config)
11
+ @thread_pool = thread_pool
12
+ end
13
+
14
+ def log_result(build_id, data)
15
+ data[:result] = @adapter.encode_and_compress(data[:result])
16
+ data = data.merge({:user => @username, :token => token_for(data.merge(:build_id => build_id))})
17
+
18
+ @thread_pool.with_a_thread_run do
19
+ result = @adapter.post("/projects/#{@project_id}/builds/#{build_id}/results", :body => data)
20
+ @out.puts error_message(result) if error?(result)
21
+ end
22
+ end
23
+
24
+ def log_build(build_id, data)
25
+ data[:build_id] = build_id
26
+ data = data.merge({:user => @username, :build_id => build_id, :token => token_for(data)})
27
+
28
+ @thread_pool.with_a_thread_run do
29
+ result = @adapter.post("/projects/#{@project_id}/builds", :body => data)
30
+ @out.puts error_message(result) if error?(result)
31
+ end
32
+ end
33
+
34
+ def find_failing_features
35
+ raise "No project name was found in params: #{@config.inspect}" if @project_id.nil?
36
+
37
+ response = @adapter.get("/projects/#{@project_id}/features/fails?user=#{@username}")
38
+
39
+ return [] if response.nil? || response.empty?
40
+ response.code == 200 ? response.body.split(" ") : []
41
+ end
42
+
43
+ private
44
+ def token_for(data)
45
+ data_string = @username.to_s +
46
+ @project_id.to_s +
47
+ build_data_string(data[:fails]) +
48
+ build_data_string(data[:passes]) +
49
+ (data[:result] ? data[:result] : "") +
50
+ (data[:build_id].to_s) +
51
+ @api_key.to_s
52
+
53
+ Digest::SHA1.hexdigest(data_string)
54
+ end
55
+
56
+ def build_data_string(data)
57
+ data == "" || data.nil? ? "" : data.join("")
58
+ end
59
+
60
+ def error?(result)
61
+ result && !result.body.empty?
62
+ end
63
+
64
+ def error_message(error_msg)
65
+ if ENV['LIMITED_RED_ENV'] == 'test' || ENV['LIMITED_RED_ENV'] == 'development'
66
+ message = #{error_msg}
67
+ else
68
+ message = ""
69
+ end
70
+
71
+ "\nLimited Red had a problem logging your test results.\n#{message}"
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,111 @@
1
+ module LimitedRed
2
+ class Config
3
+ def initialize(out_stream = STDOUT, in_stream = STDIN)
4
+ @in = in_stream
5
+ @out = out_stream
6
+ end
7
+
8
+ def self.load_and_validate_config
9
+ new.load_and_validate_config
10
+ end
11
+
12
+ def load_and_validate_config
13
+ ensure_config_exists
14
+ if valid_config?
15
+ config
16
+ else
17
+ errors = "Make sure you have the following set in your .limited_red:\n"
18
+ errors += " * project name\n" unless config['project name']
19
+ errors += " * username\n" unless config['username']
20
+ errors += " * api key\n" unless config['api key']
21
+ @out.print errors
22
+ end
23
+ end
24
+
25
+ def config
26
+ @config ||= begin
27
+ limited_red_project_config = load_yaml(limited_red_project_config_file)
28
+ limited_red_shared_config = load_yaml(limited_red_shared_config_file)
29
+ limited_red_shared_config.merge(limited_red_project_config)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def load_yaml(file)
36
+ if file
37
+ content = IO.read(file)
38
+ YAML::load(content)
39
+ else
40
+ {}
41
+ end
42
+ end
43
+
44
+ def ensure_config_exists
45
+ unless limited_red_project_config_file_exists?
46
+ details = ask_for_setup_details
47
+
48
+ File.open("#{home_dir}/.limited_red", 'w') do |f|
49
+ f.write("username: #{details['username']}\n")
50
+ f.write("api key: #{details['api key']}\n")
51
+ end
52
+
53
+ FileUtils.touch(".limited_red")
54
+ File.open('.limited_red', 'w') do |f|
55
+ f.write("project name: #{details['project name']}\n")
56
+ end
57
+ end
58
+ end
59
+
60
+ def ask_for_setup_details
61
+ details = {}
62
+ @out.print "Project name"
63
+ project_name = guess_project_name
64
+ @out.print " (default: #{project_name})" if project_name
65
+ @out.print ": "
66
+ name = @in.readline.strip
67
+ name = name.empty? ? project_name : name
68
+ name.downcase
69
+ details['project name'] = name.gsub(/[^a-zA-Z0-9']+/, "-")
70
+ @out.print "username: "
71
+ username = @in.readline.strip
72
+ details['username'] = username
73
+ @out.print "api key: "
74
+ api_key = @in.readline.strip
75
+ details['api key'] = api_key
76
+ details
77
+ end
78
+
79
+ def valid_config?
80
+ config &&
81
+ config['project name'] &&
82
+ config['username'] &&
83
+ config['api key']
84
+ end
85
+
86
+ def limited_red_project_config_file_exists?
87
+ limited_red_project_config_file
88
+ end
89
+
90
+ def limited_red_shared_config_file
91
+ @global_config_file ||= Dir.glob("#{home_dir}/.limited_red").first
92
+ end
93
+
94
+ def limited_red_project_config_file
95
+ @project_config_file ||= Dir.glob('.limited_red').first
96
+ end
97
+
98
+ private
99
+ def home_dir
100
+ ENV['HOME'] || File.expand_path('~') #Dir.home
101
+ end
102
+
103
+ def guess_project_name
104
+ dir = Dir.pwd
105
+ if dir
106
+ current_dir = dir.split('/')[-1]
107
+ return current_dir if current_dir && !current_dir.empty?
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,63 @@
1
+ require 'cucumber/formatter/json'
2
+ require 'limited_red/client'
3
+
4
+ module LimitedRed
5
+ module Formatter
6
+ class Stats < Cucumber::Formatter::Json
7
+
8
+ def initialize(step_mother, path_or_io, options)
9
+ @t = Time.now
10
+ @build_id = Time.now.to_i
11
+ @step_mother = step_mother
12
+ @client = LimitedRed::Client.new(LimitedRed::Config.load_and_validate_config)
13
+
14
+ super
15
+ end
16
+
17
+ def after_feature(feature)
18
+ if supports_feature_hash?
19
+ json = feature_hash.to_json
20
+ @client.log_result(@build_id, :result => json)
21
+ else
22
+ puts "Error: Gherkin version is out of date and does not support @feature_hash: #{gherkin_formatter.instance_variables}"
23
+ end
24
+ end
25
+
26
+ def after_features(features)
27
+ print_summary
28
+ end
29
+
30
+ def print_summary
31
+ @client.log_build(@build_id, {:fails => failing_files,
32
+ :passes => passing_files})
33
+
34
+ ThreadPool.wait_for_all_threads_to_finish
35
+ end
36
+
37
+ def failing_files
38
+ failures = @step_mother.scenarios(:failed).select { |s| s.is_a?(Cucumber::Ast::Scenario) || s.is_a?(Cucumber::Ast::OutlineTable::ExampleRow) }
39
+ failures = failures.collect { |s| (s.is_a?(Cucumber::Ast::OutlineTable::ExampleRow)) ? s.scenario_outline : s }
40
+ failures.map{|fail| fail.file_colon_line}
41
+ end
42
+
43
+ def passing_files
44
+ passing = @step_mother.scenarios(:passed).select { |s| s.is_a?(Cucumber::Ast::Scenario) || s.is_a?(Cucumber::Ast::OutlineTable::ExampleRow) }
45
+ passing = passing.collect { |s| (s.is_a?(Cucumber::Ast::OutlineTable::ExampleRow)) ? s.scenario_outline : s }
46
+ passing.map{|pass| pass.file_colon_line}
47
+ end
48
+
49
+ def supports_feature_hash?
50
+ gherkin_formatter.instance_variables.include?(:@feature_hash)
51
+ end
52
+
53
+ def feature_hash
54
+ gherkin_formatter.instance_variable_get("@feature_hash")
55
+ end
56
+
57
+ def gherkin_formatter
58
+ @gf
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,22 @@
1
+ require 'zlib'
2
+ require 'stringio'
3
+
4
+ module LimitedRed
5
+ module Gzip
6
+ class Stream < StringIO
7
+ def close; rewind; end
8
+ end
9
+
10
+ def self.compress(source)
11
+ output = Stream.new
12
+ gz = Zlib::GzipWriter.new(output)
13
+ gz.write(source)
14
+ gz.close
15
+ output.string
16
+ end
17
+
18
+ def self.decompress(source)
19
+ Zlib::GzipReader.new(StringIO.new(source)).read
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ AfterConfiguration do |config|
2
+ unless ENV['STOP_RECORDING']
3
+ require 'limited_red'
4
+
5
+ options = config.instance_variable_get("@options")
6
+ options[:formats] << ['LimitedRed::Formatter::Stats', config.out_stream]
7
+
8
+ limited_red_config = LimitedRed::Config.load_and_validate_config
9
+ if limited_red_config
10
+ cuke_stats = LimitedRed::Stats.new(limited_red_config)
11
+
12
+ feature_files = options[:paths]
13
+ feature_files = ["features"] if feature_files.empty?
14
+
15
+ prioritised_features = cuke_stats.feature_files & config.feature_files
16
+
17
+ options[:paths] = prioritised_features + feature_files
18
+ else
19
+ puts "Aborting Cucumber run"
20
+ exit
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ module LimitedRed
2
+ class Stats
3
+ def initialize(config)
4
+ @client = Client.new(config)
5
+ end
6
+
7
+ def feature_files
8
+ files = failing_features.uniq
9
+ files.select{|file| File.exists?(file)}
10
+ end
11
+
12
+ private
13
+
14
+ def failing_features
15
+ @client.find_failing_features
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ module LimitedRed
2
+ class FakeThreadPool
3
+ class << self
4
+ def with_a_thread_run(&block)
5
+ yield
6
+ end
7
+
8
+ def wait_for_all_threads_to_finish
9
+ end
10
+ end
11
+ end
12
+
13
+ class ThreadPool
14
+ class << self
15
+ attr_writer :threads
16
+
17
+ def with_a_thread_run(&block)
18
+ @threads ||= []
19
+ @threads << Thread.new(&block)
20
+ end
21
+
22
+ def wait_for_all_threads_to_finish
23
+ @threads.each { |thread| thread.join }
24
+ end
25
+
26
+ end
27
+ end
28
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: limited_red
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.4
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Joseph Wilk
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-21 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: httparty
16
+ requirement: &70300184545600 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - =
20
+ - !ruby/object:Gem::Version
21
+ version: 0.5.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70300184545600
25
+ - !ruby/object:Gem::Dependency
26
+ name: cucumber
27
+ requirement: &70300184545120 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 0.8.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70300184545120
36
+ description: Run tests priorited by those that are most likely to fail
37
+ email: joe@josephwilk.net
38
+ executables: []
39
+ extensions: []
40
+ extra_rdoc_files:
41
+ - LICENSE
42
+ - README.rdoc
43
+ files:
44
+ - lib/limited_red/adapter/httparty.rb
45
+ - lib/limited_red/client.rb
46
+ - lib/limited_red/config.rb
47
+ - lib/limited_red/formatter/stats.rb
48
+ - lib/limited_red/gzip.rb
49
+ - lib/limited_red/plugin.rb
50
+ - lib/limited_red/stats.rb
51
+ - lib/limited_red/thread_pool.rb
52
+ - lib/limited_red.rb
53
+ - LICENSE
54
+ - README.rdoc
55
+ homepage:
56
+ licenses: []
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --charset=UTF-8
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 1.8.6
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Run tests priorited by those that are most likely to fail
80
+ test_files: []