limited_red 0.2.4
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/LICENSE +22 -0
- data/README.rdoc +17 -0
- data/lib/limited_red.rb +26 -0
- data/lib/limited_red/adapter/httparty.rb +24 -0
- data/lib/limited_red/client.rb +74 -0
- data/lib/limited_red/config.rb +111 -0
- data/lib/limited_red/formatter/stats.rb +63 -0
- data/lib/limited_red/gzip.rb +22 -0
- data/lib/limited_red/plugin.rb +23 -0
- data/lib/limited_red/stats.rb +18 -0
- data/lib/limited_red/thread_pool.rb +28 -0
- metadata +80 -0
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.
|
data/README.rdoc
ADDED
@@ -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.
|
data/lib/limited_red.rb
ADDED
@@ -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: []
|