probe-dock-rspec 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NTJlNjI0N2E1ZjkzZGZjMzExNWUwZjY1MTlkNDRjZTFjNzZiZjJiMQ==
5
+ data.tar.gz: !binary |-
6
+ NmU1M2JjNzY2Y2E5MjE1OWIyOTQ1ZjI4MjcxYTkxODBkZGZiMGVlNg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ OGRhYWU3MjIxMjUxMWNlYTAxNWI3NWEzNDE2MTI4YmY0NDlmMGRjNDY3YTYw
10
+ ZjhmN2ExZWQ2MTJjMDFkMjNmMDcyN2U5MGU3ZTcwMjIxOTFlMDVjZTM0Zjhj
11
+ NTM4OGU5MjBiMTQyZTY3MjUxMGU2ZjM3MDNhNDkzMjg2ZTc5MDY=
12
+ data.tar.gz: !binary |-
13
+ NGIwZmM2NWJiZjc2MTExMjU3MWFlNDFiM2UxMjVjYjFlZmRhN2U4M2VjZjBi
14
+ NTI3ODBkMzJjNTg0MGVhMzg2YmZjYTNhNjIxZmU1MzE5ZWQ3MGI3YzI2OWIz
15
+ ODIyYTE4ZWExODQxOTI3NjBiYTFhODFmYzg1ZmUzYjg5MDU5NjE=
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ gem 'rspec', '~> 3.1'
7
+ gem 'oj', '~> 2.1'
8
+ gem 'httparty', '~> 0.13'
9
+ gem 'paint', '~> 1.0'
10
+ gem 'rake', '~> 10.1'
11
+
12
+ # Add dependencies to develop your gem here.
13
+ # Include everything needed to run rake, tests, features, etc.
14
+ group :development do
15
+ gem 'jeweler', '~> 2.0'
16
+ gem 'rake-version', '~> 0.4'
17
+ gem 'simplecov', '~> 0.10'
18
+ gem 'fakefs', '~> 0.6', require: 'fakefs/safe'
19
+ gem 'rspec-its', '~> 1.2'
20
+ gem 'rspec-collection_matchers', '~> 1.1'
21
+ end
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2015 Probe Dock
2
+ Copyright (c) 2012-2013 Lotaris SA
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,69 @@
1
+ # Probe Dock RSpec
2
+
3
+ **RSpec probe for [Probe Dock](https://github.com/probe-dock/probe-dock).**
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/probe-dock-rspec.png)](http://badge.fury.io/rb/probe-dock-rspec)
6
+
7
+ ## Requirements
8
+
9
+ * RSpec 3+
10
+
11
+ ## Installation
12
+
13
+ In your Gemfile:
14
+
15
+ ```rb
16
+ gem 'probe-dock-rspec', '~> 0.4.1'
17
+ ```
18
+
19
+ Manually:
20
+
21
+ gem install probe-dock-rspec
22
+
23
+ ## Usage
24
+
25
+ If you haven't done it already, follow the [setup procedure](#setup) below.
26
+
27
+ To track a test with a Probe Dock test key, use RSpec metadata:
28
+
29
+ ```rb
30
+ it "should work", probe_dock: { key: 'abcdefghijkl' } do
31
+ expect(true).to be(true)
32
+ end
33
+
34
+ it(nil, probe_dock: { key: 'bcdefghijklm' }){ should validate_presence_of(:name) }
35
+ ```
36
+
37
+ <a name="setup"></a>
38
+ ## Setup
39
+
40
+ You must first set up the configuration file(s) for the project.
41
+ This procedure is described here:
42
+
43
+ * [Probe Setup Procedure](https://github.com/probe-dock/probe-dock-clients#setup-procedure)
44
+
45
+ You must then enable the client in your spec helper file (e.g. `spec/spec_helper.rb`).
46
+
47
+ ```yml
48
+ ProbeDockRSpec.configure do |config|
49
+
50
+ # Optional category to add to all the tests sent with this client.
51
+ config.project.category = 'RSpec'
52
+ end
53
+ ```
54
+
55
+ The next time you run your test suite, the RSpec probe will send the results to your Probe Dock server.
56
+
57
+ ## Contributing
58
+
59
+ * [Fork](https://help.github.com/articles/fork-a-repo)
60
+ * Create a topic branch - `git checkout -b my_feature`
61
+ * Push to your branch - `git push origin my_feature`
62
+ * Create a [pull request](http://help.github.com/pull-requests/) from your branch
63
+
64
+ Please add a [changelog](CHANGELOG.md) entry with your name for new features and bug fixes.
65
+
66
+ ## License
67
+
68
+ Probe Dock RSpec is licensed under the [MIT License](http://opensource.org/licenses/MIT).
69
+ See [LICENSE.txt](LICENSE.txt) for the full license.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
@@ -0,0 +1 @@
1
+ require 'probe_dock_rspec'
@@ -0,0 +1,11 @@
1
+ # encoding: UTF-8
2
+ require 'rspec'
3
+
4
+ module ProbeDockRSpec
5
+ VERSION = '0.5.0'
6
+
7
+ class Error < StandardError; end
8
+ class PayloadError < Error; end
9
+ end
10
+
11
+ Dir[File.join File.dirname(__FILE__), File.basename(__FILE__, '.*'), '*.rb'].each{ |lib| require lib }
@@ -0,0 +1,61 @@
1
+
2
+ module ProbeDockRSpec
3
+
4
+ class Cache
5
+
6
+ class Error < ProbeDockRSpec::Error; end
7
+
8
+ def initialize options = {}
9
+ @tests = {}
10
+ @workspace, @server_name, @project_api_id = options[:workspace], options[:server_name], options[:project_api_id]
11
+ end
12
+
13
+ def save test_run
14
+ validate!
15
+
16
+ @tests = { @project_api_id => @tests[@project_api_id] || {} }
17
+ test_run.results.select(&:key).each{ |r| @tests[@project_api_id][r.key] = test_result_hash(r) }
18
+
19
+ FileUtils.mkdir_p File.dirname(cache_file)
20
+ File.open(cache_file, 'w'){ |f| f.write Oj.dump(@tests, mode: :strict) }
21
+
22
+ self
23
+ end
24
+
25
+ def load
26
+ validate!
27
+
28
+ @tests = if File.exists? cache_file
29
+ Oj.load(File.read(cache_file), mode: :strict) rescue {}
30
+ else
31
+ {}
32
+ end
33
+
34
+ self
35
+ end
36
+
37
+ def known? test_result
38
+ test_result.key && !!@tests[@project_api_id] && !!@tests[@project_api_id][test_result.key]
39
+ end
40
+
41
+ def stale? test_result
42
+ !!@tests[@project_api_id] && test_result_hash(test_result) != @tests[@project_api_id][test_result.key]
43
+ end
44
+
45
+ private
46
+
47
+ def validate!
48
+ required = { "workspace" => @workspace, "server name" => @server_name, "project API identifier" => @project_api_id }
49
+ missing = required.keys.select{ |k| !required[k] }
50
+ raise Error.new("Missing cache options: #{missing.join ', '}") if missing.any?
51
+ end
52
+
53
+ def test_result_hash r
54
+ Digest::SHA2.hexdigest "#{r.name} || #{r.category} || #{r.tags.collect(&:to_s).sort.join(' ')} || #{r.tickets.collect(&:to_s).sort.join(' ')}"
55
+ end
56
+
57
+ def cache_file
58
+ @cache_file ||= File.join(@workspace, 'rspec', 'servers', @server_name, 'cache.json')
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,120 @@
1
+
2
+ module ProbeDockRSpec
3
+
4
+ class Client
5
+
6
+ def initialize server, options = {}
7
+
8
+ @server = server
9
+ @publish, @local_mode, @workspace = options[:publish], options[:local_mode], options[:workspace]
10
+ @cache_payload, @print_payload, @save_payload = options[:cache_payload], options[:print_payload], options[:save_payload]
11
+
12
+ cache_options = { workspace: @workspace }
13
+ cache_options.merge! server_name: @server.name, project_api_id: @server.project_api_id if @server
14
+ @cache = Cache.new cache_options
15
+
16
+ @uid = UID.new workspace: @workspace
17
+ end
18
+
19
+ def process test_run
20
+
21
+ return fail "No server to publish results to" if !@server
22
+
23
+ test_run.uid = @uid.load_uid
24
+
25
+ payload_options = @server.payload_options
26
+
27
+ cache_enabled = @cache_payload && load_cache
28
+ payload_options[:cache] = @cache if cache_enabled
29
+
30
+ return false unless payload = build_payload(test_run, payload_options)
31
+
32
+ published = if !@publish
33
+ puts Paint["Probe Dock - Publishing disabled", :yellow]
34
+ false
35
+ elsif publish_payload payload
36
+ @cache.save test_run if cache_enabled
37
+ true
38
+ else
39
+ false
40
+ end
41
+
42
+ save_payload payload if @save_payload
43
+ print_payload payload if @print_payload
44
+
45
+ puts
46
+
47
+ published
48
+ end
49
+
50
+ private
51
+
52
+ def build_payload test_run, options = {}
53
+ begin
54
+ TestPayload.new(test_run).to_h options
55
+ rescue PayloadError => e
56
+ fail e.message
57
+ end
58
+ end
59
+
60
+ def fail msg, type = :error
61
+ styles = { warning: [ :yellow ], error: [ :bold, :red ] }
62
+ warn Paint["Probe Dock - #{msg}", *styles[type]]
63
+ false
64
+ end
65
+
66
+ def load_cache
67
+ begin
68
+ @cache.load
69
+ rescue Cache::Error => e
70
+ warn Paint["Probe Dock - #{e.message}", :yellow]
71
+ false
72
+ end
73
+ end
74
+
75
+ def print_payload payload
76
+ puts Paint['Probe Dock - Printing payload...', :yellow]
77
+ begin
78
+ puts JSON.pretty_generate(payload)
79
+ rescue
80
+ puts payload.inspect
81
+ end
82
+ end
83
+
84
+ def save_payload payload
85
+
86
+ missing = { "workspace" => @workspace, "server" => @server }.inject([]){ |memo,(k,v)| !v ? memo << k : memo }
87
+ return fail "Cannot save payload without a #{missing.join ' and '}" if missing.any?
88
+
89
+ FileUtils.mkdir_p File.dirname(payload_file)
90
+ File.open(payload_file, 'w'){ |f| f.write Oj.dump(payload, mode: :strict) }
91
+ end
92
+
93
+ def payload_file
94
+ @payload_file ||= File.join(@workspace, 'rspec', 'servers', @server.name, 'payload.json')
95
+ end
96
+
97
+ def publish_payload payload
98
+
99
+ puts Paint["Probe Dock - Sending payload to #{@server.api_url}...", :magenta]
100
+
101
+ begin
102
+ if @local_mode
103
+ puts Paint['Probe Dock - LOCAL MODE: not actually sending payload.', :yellow]
104
+ else
105
+ @server.upload payload
106
+ end
107
+ puts Paint["Probe Dock - Done!", :green]
108
+ true
109
+ rescue Server::Error => e
110
+ warn Paint["Probe Dock - Upload failed!", :red, :bold]
111
+ warn Paint["Probe Dock - #{e.message}", :red, :bold]
112
+ if e.response
113
+ warn Paint["Probe Dock - Dumping response body...", :red, :bold]
114
+ warn e.response.body
115
+ end
116
+ false
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,173 @@
1
+ require 'yaml'
2
+
3
+ # Utilities to send test results to Probe Dock.
4
+ module ProbeDockRSpec
5
+
6
+ def self.config
7
+ @config ||= Config.new.tap(&:load)
8
+ end
9
+
10
+ def self.configure options = {}
11
+ yield config if block_given?
12
+ config.load_warnings.each{ |w| warn Paint["Probe Dock - #{w}", :yellow] }
13
+ config.setup! if options[:setup] != false
14
+ config
15
+ end
16
+
17
+ class Config
18
+ # TODO: add silent/verbose option(s)
19
+ class Error < ProbeDockRSpec::Error; end
20
+ attr_writer :publish, :local_mode, :cache_payload, :print_payload, :save_payload
21
+ attr_reader :project, :server, :workspace, :load_warnings
22
+
23
+ def initialize
24
+ @servers = []
25
+ @project = Project.new
26
+ @publish, @local_mode, @cache_payload, @print_payload, @save_payload = false, false, false, false, false
27
+ @load_warnings = []
28
+ end
29
+
30
+ def workspace= dir
31
+ @workspace = dir ? File.expand_path(dir) : nil
32
+ end
33
+
34
+ def servers
35
+ @servers.dup
36
+ end
37
+
38
+ # Plugs Probe Dock utilities into RSpec.
39
+ def setup!
40
+ ::RSpec.configure do |c|
41
+ c.add_formatter Formatter
42
+ end
43
+ end
44
+
45
+ %w(publish local_mode cache_payload print_payload save_payload).each do |name|
46
+ define_method("#{name}?"){ instance_variable_get("@#{name}") }
47
+ end
48
+
49
+ def client_options
50
+ {
51
+ publish: @publish,
52
+ local_mode: @local_mode,
53
+ workspace: @workspace,
54
+ cache_payload: @cache_payload,
55
+ print_payload: @print_payload,
56
+ save_payload: @save_payload
57
+ }.select{ |k,v| !v.nil? }
58
+ end
59
+
60
+ def load
61
+
62
+ @load_warnings = []
63
+ return unless config = load_config_files
64
+
65
+ @publish = parse_env_flag :publish, !!config[:publish]
66
+ @server_name = parse_env_option(:server) || config[:server]
67
+ @local_mode = parse_env_flag(:local) || !!config[:local]
68
+
69
+ self.workspace = parse_env_option(:workspace) || config[:workspace]
70
+ @cache_payload = parse_env_flag :cache_payload, !!config[:payload][:cache]
71
+ @print_payload = parse_env_flag :print_payload, !!config[:payload][:print]
72
+ @save_payload = parse_env_flag :save_payload, !!config[:payload][:save]
73
+
74
+ @servers, @server = build_servers config
75
+
76
+ if @servers.empty?
77
+ @load_warnings << "No server defined"
78
+ elsif !@server_name
79
+ @load_warnings << "No server name given"
80
+ elsif !@server
81
+ @load_warnings << "Unknown server '#{@server_name}'"
82
+ end
83
+
84
+ project_options = config[:project]
85
+ project_options.merge! api_id: @server.project_api_id if @server and @server.project_api_id
86
+ @project.update project_options
87
+
88
+ self
89
+ end
90
+
91
+ private
92
+
93
+ def build_servers config
94
+
95
+ default_server_options = { project_api_id: config[:project][:api_id] }
96
+ servers = config[:servers].inject({}) do |memo,(name, options)|
97
+ memo[name] = Server.new default_server_options.merge(options).merge(name: name)
98
+ memo
99
+ end
100
+
101
+ [ servers.values, servers[@server_name.to_s.strip] ]
102
+ end
103
+
104
+ def load_config_files
105
+
106
+ configs = [ home_config_file, working_config_file ]
107
+ actual_configs = configs.select{ |f| File.exists? f }
108
+
109
+ if actual_configs.empty?
110
+ @load_warnings << %|no config file found, looking for:\n #{configs.join "\n "}|
111
+ return false
112
+ end
113
+
114
+ actual_configs.collect!{ |f| YAML.load_file f }
115
+
116
+ actual_configs.inject({ servers: {} }) do |memo,yml|
117
+ memo.merge! parse_general_options(yml)
118
+
119
+ if yml['servers'].kind_of? Hash
120
+ yml['servers'].each_pair do |k,v|
121
+ if v.kind_of? Hash
122
+ memo[:servers][k] = (memo[:servers][k] || {}).merge(parse_server_options(v))
123
+ end
124
+ end
125
+ end
126
+
127
+ memo[:payload] = (memo[:payload] || {}).merge parse_payload_options(yml['payload'])
128
+ memo[:project] = (memo[:project] || {}).merge parse_project_options(yml['project'])
129
+
130
+ memo
131
+ end
132
+ end
133
+
134
+ def home_config_file
135
+ File.join File.expand_path('~'), '.probe-dock', 'config.yml'
136
+ end
137
+
138
+ def working_config_file
139
+ File.expand_path ENV['PROBE_DOCK_CONFIG'] || 'probe-dock.yml', Dir.pwd
140
+ end
141
+
142
+ def parse_env_flag name, default = false
143
+ val = parse_env_option name
144
+ val ? !!val.to_s.strip.match(/\A(1|t|true)\Z/i) : default
145
+ end
146
+
147
+ def parse_env_option name
148
+ var = "PROBE_DOCK_#{name.upcase}"
149
+ ENV.key?(var) ? ENV[var] : nil
150
+ end
151
+
152
+ def parse_general_options h
153
+ parse_options h, %w(publish server local workspace)
154
+ end
155
+
156
+ def parse_server_options h
157
+ parse_options h, %w(name apiUrl apiToken apiVersion projectApiId)
158
+ end
159
+
160
+ def parse_payload_options h
161
+ parse_options h, %w(save cache print)
162
+ end
163
+
164
+ def parse_project_options h
165
+ parse_options h, %w(version apiId category tags tickets)
166
+ end
167
+
168
+ def parse_options h, keys
169
+ return {} unless h.kind_of? Hash
170
+ keys.inject({}){ |memo,k| memo[k.gsub(/(.)([A-Z])/, '\1_\2').downcase.to_sym] = h[k] if h.key?(k); memo }
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,83 @@
1
+ require 'paint'
2
+ require 'fileutils'
3
+ require 'rspec/core/formatters/base_formatter'
4
+
5
+ module ProbeDockRSpec
6
+
7
+ class Formatter
8
+
9
+ RSpec::Core::Formatters.register self, :start, :stop, :close,
10
+ :example_group_started, :example_started, :example_passed, :example_failed, :example_group_finished
11
+
12
+ def initialize output
13
+
14
+ config = ProbeDockRSpec.config
15
+ @client = Client.new config.server, config.client_options
16
+ @test_run = TestRun.new config.project
17
+
18
+ @groups = []
19
+ end
20
+
21
+ def start notification
22
+ # TODO: measure milliseconds
23
+ @start_time = Time.now
24
+ end
25
+
26
+ def example_group_started group_notification
27
+ @groups << group_notification.group
28
+ end
29
+
30
+ def example_group_finished group_notification
31
+ @groups.pop
32
+ end
33
+
34
+ def example_started example_notification
35
+ @current_time = Time.now
36
+ end
37
+
38
+ def example_passed example_notification
39
+ add_result example_notification, true
40
+ end
41
+
42
+ def example_failed example_notification
43
+ add_result example_notification, false
44
+ end
45
+
46
+ def stop notification
47
+ end_time = Time.now
48
+ @test_run.duration = ((end_time - @start_time) * 1000).round
49
+ end
50
+
51
+ def close notification
52
+ @client.process @test_run
53
+ end
54
+
55
+ private
56
+
57
+ def add_result example_notification, successful
58
+
59
+ options = {
60
+ passed: successful,
61
+ duration: ((Time.now - @current_time) * 1000).round
62
+ }
63
+
64
+ options[:message] = failure_message example_notification unless successful
65
+
66
+ @test_run.add_result example_notification.example, @groups, options
67
+ end
68
+
69
+ def failure_message example_notification
70
+ String.new.tap do |m|
71
+ m << example_notification.description
72
+ m << "\n"
73
+ m << example_notification.message_lines.collect{ |l| " #{l}" }.join("\n")
74
+ m << "\n"
75
+ m << example_notification.formatted_backtrace.collect{ |l| " # #{l}" }.join("\n")
76
+ end
77
+ end
78
+
79
+ def full_example_name example
80
+ (@groups.collect{ |g| g.description.strip } << example.description.strip).join ' '
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,30 @@
1
+ module ProbeDockRSpec
2
+
3
+ class Project
4
+ attr_accessor :version, :api_id, :category, :tags, :tickets
5
+
6
+ def initialize options = {}
7
+ update options
8
+ end
9
+
10
+ def update options = {}
11
+ %w(version api_id category).each do |k|
12
+ instance_variable_set "@#{k}", options[k.to_sym] ? options[k.to_sym].to_s : nil if options.key? k.to_sym
13
+ end
14
+ @tags = wrap(options[:tags]).compact if options.key? :tags
15
+ @tickets = wrap(options[:tickets]).compact if options.key? :tickets
16
+ end
17
+
18
+ def validate!
19
+ required = { "version" => @version, "API identifier" => @api_id }
20
+ missing = required.inject([]){ |memo,(k,v)| v.to_s.strip.length <= 0 ? memo << k : memo }
21
+ raise PayloadError.new("Missing project options: #{missing.join ', '}") if missing.any?
22
+ end
23
+
24
+ private
25
+
26
+ def wrap a
27
+ a.kind_of?(Array) ? a : [ a ]
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,58 @@
1
+ require 'oj'
2
+ require 'httparty'
3
+
4
+ module ProbeDockRSpec
5
+
6
+ class Server
7
+ attr_reader :name, :api_url, :api_token, :api_version, :project_api_id
8
+
9
+ class Error < ProbeDockRSpec::Error
10
+ attr_reader :response
11
+
12
+ def initialize msg, response = nil
13
+ super msg
14
+ @response = response
15
+ end
16
+ end
17
+
18
+ def initialize options = {}
19
+ @name = options[:name].to_s.strip
20
+ @api_url = options[:api_url].to_s if options[:api_url]
21
+ @api_token = options[:api_token].to_s if options[:api_token]
22
+ @api_version = options[:api_version] || 1
23
+ @project_api_id = options[:project_api_id].to_s if options[:project_api_id]
24
+ end
25
+
26
+ def payload_options
27
+ { version: @api_version }
28
+ end
29
+
30
+ def upload payload
31
+ validate!
32
+
33
+ body = Oj.dump payload, mode: :strict
34
+ res = HTTParty.post payload_uri, body: body, headers: payload_headers
35
+
36
+ if res.code != 202
37
+ raise Error.new("Expected HTTP 202 Accepted when submitting payload, got #{res.code}", res)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def validate!
44
+ required = { "apiUrl" => @api_url }
45
+ required.merge!({ "apiToken" => @api_token, "projectApiId" => @project_api_id }) if @api_version >= 1
46
+ missing = required.inject([]){ |memo,(k,v)| v.to_s.strip.length <= 0 ? memo << k : memo }
47
+ raise Error.new("Server #{@name} is missing the following options: #{missing.join ', '}") if missing.any?
48
+ end
49
+
50
+ def payload_headers
51
+ { 'Authorization' => "Bearer #{@api_token}", 'Content-Type' => 'application/vnd.probe-dock.payload.v1+json' }
52
+ end
53
+
54
+ def payload_uri
55
+ "#{@api_url}/publish"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,62 @@
1
+ require 'fileutils'
2
+ require 'rake/tasklib'
3
+
4
+ module ProbeDockRSpec
5
+
6
+ class Tasks < ::Rake::TaskLib
7
+
8
+ def initialize
9
+
10
+ namespace :spec do
11
+
12
+ namespace 'probe-dock' do
13
+
14
+ desc "Generate a test run UID to group test results in Probe Dock (stored in an environment variable)"
15
+ task :uid do
16
+ trace do
17
+ uid = uid_manager.generate_uid_to_env
18
+ puts Paint["Probe Dock - Generated UID for test run: #{uid}", :cyan]
19
+ end
20
+ end
21
+
22
+ namespace :uid do
23
+
24
+ desc "Generate a test run UID to group test results in Probe Dock (stored in a file)"
25
+ task :file do
26
+ trace do
27
+ uid = uid_manager.generate_uid_to_file
28
+ puts Paint["Probe Dock - Generated UID for test run: #{uid}", :cyan]
29
+ end
30
+ end
31
+
32
+ desc "Clean the test run UID (file and environment variable)"
33
+ task :clean do
34
+ trace do
35
+ uid_manager.clean_uid
36
+ puts Paint["Probe Dock - Cleaned test run UID", :cyan]
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def trace &block
47
+ if Rake.application.options.trace
48
+ block.call
49
+ else
50
+ begin
51
+ block.call
52
+ rescue UID::Error => e
53
+ warn Paint["Probe Dock - #{e.message}", :red]
54
+ end
55
+ end
56
+ end
57
+
58
+ def uid_manager
59
+ UID.new ProbeDockRSpec.config.client_options
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,18 @@
1
+ require 'fileutils'
2
+ require 'digest/sha2'
3
+
4
+ module ProbeDockRSpec
5
+
6
+ class TestPayload
7
+
8
+ class Error < ProbeDockRSpec::Error; end
9
+
10
+ def initialize run
11
+ @run = run
12
+ end
13
+
14
+ def to_h options = {}
15
+ @run.to_h options
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,119 @@
1
+
2
+ module ProbeDockRSpec
3
+
4
+ class TestResult
5
+ attr_reader :key, :name, :category, :tags, :tickets, :data, :duration, :message
6
+
7
+ def initialize project, example, groups = [], options = {}
8
+
9
+ @category = project.category
10
+ @tags = project.tags
11
+ @tickets = project.tickets
12
+
13
+ @grouped = extract_grouped example, groups
14
+
15
+ [ :key, :name, :category, :tags, :tickets, :data ].each do |attr|
16
+ instance_variable_set "@#{attr}".to_sym, send("extract_#{attr}".to_sym, example, groups)
17
+ end
18
+
19
+ @passed = !!options[:passed]
20
+ @duration = options[:duration]
21
+ @message = options[:message]
22
+ end
23
+
24
+ def passed?
25
+ @passed
26
+ end
27
+
28
+ def grouped?
29
+ @grouped
30
+ end
31
+
32
+ def update options = {}
33
+ @passed &&= options[:passed]
34
+ @duration += options[:duration]
35
+ @message = [ @message, options[:message] ].select{ |m| m }.join("\n\n") if options[:message]
36
+ end
37
+
38
+ def to_h options = {}
39
+ {
40
+ 'p' => @passed,
41
+ 'd' => @duration
42
+ }.tap do |h|
43
+
44
+ h['k'] = @key if @key
45
+ h['m'] = @message if @message
46
+
47
+ cache = options[:cache]
48
+ first = !cache || !cache.known?(self)
49
+ stale = !first && cache.stale?(self)
50
+ h['n'] = @name if stale or first
51
+ h['c'] = @category if stale or (first and @category)
52
+ h['g'] = @tags if stale or (first and !@tags.empty?)
53
+ h['t'] = @tickets if stale or (first and !@tickets.empty?)
54
+ h['a'] = @data if @data # FIXME: cache custom data
55
+ end
56
+ end
57
+
58
+ def self.extract_grouped example, groups = []
59
+ !!groups.collect{ |g| meta(g)[:grouped] }.compact.last
60
+ end
61
+
62
+ def self.extract_key example, groups = []
63
+ (groups.collect{ |g| meta(g)[:key] } << meta(example)[:key]).compact.reject{ |k| k.strip.empty? }.last
64
+ end
65
+
66
+ def self.meta holder
67
+ meta = holder.metadata[:probe_dock] || {}
68
+ if meta.kind_of? String
69
+ { key: meta }
70
+ elsif meta.kind_of? Hash
71
+ meta
72
+ else
73
+ {}
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def meta *args
80
+ self.class.meta *args
81
+ end
82
+
83
+ def extract_grouped *args
84
+ self.class.extract_grouped *args
85
+ end
86
+
87
+ def extract_key *args
88
+ self.class.extract_key *args
89
+ end
90
+
91
+ def extract_name example, groups = []
92
+ parts = groups.dup
93
+ parts = parts[0, parts.index{ |p| meta(p)[:grouped] } + 1] if @grouped
94
+ parts << example unless @grouped
95
+ parts.collect{ |p| p.description.strip }.join ' '
96
+ end
97
+
98
+ def extract_category example, groups = []
99
+ cat = (groups.collect{ |g| meta(g)[:category] }.unshift(@category) << meta(example)[:category]).compact.last
100
+ cat ? cat.to_s : nil
101
+ end
102
+
103
+ def extract_tags example, groups = []
104
+ (wrap(@tags) + groups.collect{ |g| wrap meta(g)[:tags] } + (wrap meta(example)[:tags])).flatten.compact.uniq.collect(&:to_s)
105
+ end
106
+
107
+ def extract_tickets example, groups = []
108
+ (wrap(@tickets) + groups.collect{ |g| wrap meta(g)[:tickets] } + (wrap meta(example)[:tickets])).flatten.compact.uniq.collect(&:to_s)
109
+ end
110
+
111
+ def extract_data example, groups = []
112
+ meta(example)[:data]
113
+ end
114
+
115
+ def wrap a
116
+ a.kind_of?(Array) ? a : [ a ]
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,85 @@
1
+
2
+ module ProbeDockRSpec
3
+
4
+ class TestRun
5
+ attr_reader :results, :project
6
+ attr_accessor :duration, :uid
7
+
8
+ def initialize project
9
+ @results = []
10
+ @project = project
11
+ end
12
+
13
+ def add_result example, groups = [], options = {}
14
+ if TestResult.extract_grouped(example, groups) and (existing_result = @results.find{ |r| r.grouped? && r.key == TestResult.extract_key(example, groups) })
15
+ existing_result.update options
16
+ else
17
+ @results << TestResult.new(@project, example, groups, options)
18
+ end
19
+ end
20
+
21
+ def results_without_key
22
+ @results.select{ |r| !r.key or r.key.to_s.strip.empty? }
23
+ end
24
+
25
+ def results_by_key
26
+ @results.inject({}) do |memo,r|
27
+ (memo[r.key] ||= []) << r unless !r.key or r.key.to_s.strip.empty?
28
+ memo
29
+ end
30
+ end
31
+
32
+ def to_h options = {}
33
+ validate!
34
+
35
+ {
36
+ 'projectId' => @project.api_id,
37
+ 'version' => @project.version,
38
+ 'duration' => @duration,
39
+ 'results' => @results.collect{ |r| r.to_h options }
40
+ }.tap do |h|
41
+ h['reports'] = [ { 'uid' => @uid } ] if @uid
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def validate!
48
+ # TODO: validate duration
49
+
50
+ raise PayloadError.new("Missing project") if !@project
51
+ @project.validate!
52
+
53
+ # FIXME: log info/warnings
54
+ #validate_no_results_without_key
55
+ #validate_no_duplicate_keys
56
+ end
57
+
58
+ def validate_no_duplicate_keys
59
+
60
+ results_with_duplicate_key = results_by_key.select{ |k,r| r.length >= 2 }
61
+ return if results_with_duplicate_key.none?
62
+
63
+ msg = "the following keys are used by multiple test results".tap do |s|
64
+ results_with_duplicate_key.each_pair do |key,results|
65
+ s << "\n - #{key}"
66
+ results.each{ |r| s << "\n - #{r.name}" }
67
+ end
68
+ end
69
+
70
+ raise PayloadError.new(msg)
71
+ end
72
+
73
+ def validate_no_results_without_key
74
+
75
+ keyless = results_without_key
76
+ return if keyless.empty?
77
+
78
+ msg = "the following test results are missing a key".tap do |s|
79
+ keyless.each{ |r| s << "\n - #{r.name}" }
80
+ end
81
+
82
+ raise PayloadError.new(msg)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,60 @@
1
+ require 'securerandom'
2
+
3
+ module ProbeDockRSpec
4
+
5
+ class UID
6
+ ENVIRONMENT_VARIABLE = 'PROBE_DOCK_TEST_REPORT_UID'
7
+
8
+ class Error < ProbeDockRSpec::Error; end
9
+
10
+ def initialize options = {}
11
+ @workspace = options[:workspace]
12
+ end
13
+
14
+ def load_uid
15
+ if env_var
16
+ return env_var
17
+ elsif @workspace
18
+ current_uid
19
+ end
20
+ end
21
+
22
+ def generate_uid_to_file
23
+ raise Error.new("No workspace specified; cannot save test run UID") if !@workspace
24
+ generate_uid.tap{ |uid| save_uid uid }
25
+ end
26
+
27
+ def generate_uid_to_env
28
+ raise Error.new("$PROBE_DOCK_TEST_REPORT_UID is already defined") if env_var
29
+ ENV[ENVIRONMENT_VARIABLE] = generate_uid
30
+ end
31
+
32
+ def clean_uid
33
+ ENV.delete ENVIRONMENT_VARIABLE
34
+ FileUtils.remove_entry_secure uid_file if @workspace and File.exists?(uid_file)
35
+ end
36
+
37
+ private
38
+
39
+ def save_uid uid
40
+ FileUtils.mkdir_p File.dirname(uid_file)
41
+ File.open(uid_file, 'w'){ |f| f.write uid }
42
+ end
43
+
44
+ def env_var
45
+ ENV[ENVIRONMENT_VARIABLE]
46
+ end
47
+
48
+ def current_uid
49
+ File.file?(uid_file) ? File.read(uid_file) : nil
50
+ end
51
+
52
+ def uid_file
53
+ @uid_file ||= File.join(@workspace, 'uid')
54
+ end
55
+
56
+ def generate_uid
57
+ "#{Time.now.utc.strftime '%Y%m%d%H%M%S'}-#{SecureRandom.uuid}"
58
+ end
59
+ end
60
+ end
metadata ADDED
@@ -0,0 +1,221 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: probe-dock-rspec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - Simon Oulevay (Alpha Hydrae)
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '3.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: oj
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '2.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: httparty
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: !binary |-
48
+ MC4xMw==
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: !binary |-
56
+ MC4xMw==
57
+ - !ruby/object:Gem::Dependency
58
+ name: paint
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ version: '1.0'
64
+ type: :runtime
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ~>
69
+ - !ruby/object:Gem::Version
70
+ version: '1.0'
71
+ - !ruby/object:Gem::Dependency
72
+ name: rake
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '10.1'
78
+ type: :runtime
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ~>
83
+ - !ruby/object:Gem::Version
84
+ version: '10.1'
85
+ - !ruby/object:Gem::Dependency
86
+ name: jeweler
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ~>
90
+ - !ruby/object:Gem::Version
91
+ version: '2.0'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ~>
97
+ - !ruby/object:Gem::Version
98
+ version: '2.0'
99
+ - !ruby/object:Gem::Dependency
100
+ name: rake-version
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ~>
104
+ - !ruby/object:Gem::Version
105
+ version: !binary |-
106
+ MC40
107
+ type: :development
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ~>
112
+ - !ruby/object:Gem::Version
113
+ version: !binary |-
114
+ MC40
115
+ - !ruby/object:Gem::Dependency
116
+ name: simplecov
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ~>
120
+ - !ruby/object:Gem::Version
121
+ version: '0.10'
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ~>
127
+ - !ruby/object:Gem::Version
128
+ version: '0.10'
129
+ - !ruby/object:Gem::Dependency
130
+ name: fakefs
131
+ requirement: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ~>
134
+ - !ruby/object:Gem::Version
135
+ version: '0.6'
136
+ type: :development
137
+ prerelease: false
138
+ version_requirements: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ~>
141
+ - !ruby/object:Gem::Version
142
+ version: '0.6'
143
+ - !ruby/object:Gem::Dependency
144
+ name: rspec-its
145
+ requirement: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ~>
148
+ - !ruby/object:Gem::Version
149
+ version: '1.2'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ~>
155
+ - !ruby/object:Gem::Version
156
+ version: '1.2'
157
+ - !ruby/object:Gem::Dependency
158
+ name: rspec-collection_matchers
159
+ requirement: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ~>
162
+ - !ruby/object:Gem::Version
163
+ version: '1.1'
164
+ type: :development
165
+ prerelease: false
166
+ version_requirements: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ~>
169
+ - !ruby/object:Gem::Version
170
+ version: '1.1'
171
+ description: RSpec client to publish test results to Probe Dock, a test tracking and
172
+ analysis server.
173
+ email: simon.oulevay@gmail.com
174
+ executables: []
175
+ extensions: []
176
+ extra_rdoc_files:
177
+ - LICENSE.txt
178
+ - README.md
179
+ files:
180
+ - Gemfile
181
+ - LICENSE.txt
182
+ - README.md
183
+ - VERSION
184
+ - lib/probe-dock-rspec.rb
185
+ - lib/probe_dock_rspec.rb
186
+ - lib/probe_dock_rspec/cache.rb
187
+ - lib/probe_dock_rspec/client.rb
188
+ - lib/probe_dock_rspec/config.rb
189
+ - lib/probe_dock_rspec/formatter.rb
190
+ - lib/probe_dock_rspec/project.rb
191
+ - lib/probe_dock_rspec/server.rb
192
+ - lib/probe_dock_rspec/tasks.rb
193
+ - lib/probe_dock_rspec/test_payload.rb
194
+ - lib/probe_dock_rspec/test_result.rb
195
+ - lib/probe_dock_rspec/test_run.rb
196
+ - lib/probe_dock_rspec/uid.rb
197
+ homepage: https://github.com/probe-dock/probe-dock-rspec
198
+ licenses:
199
+ - MIT
200
+ metadata: {}
201
+ post_install_message:
202
+ rdoc_options: []
203
+ require_paths:
204
+ - lib
205
+ required_ruby_version: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - ! '>='
208
+ - !ruby/object:Gem::Version
209
+ version: '0'
210
+ required_rubygems_version: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ! '>='
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ requirements: []
216
+ rubyforge_project:
217
+ rubygems_version: 2.4.5
218
+ signing_key:
219
+ specification_version: 4
220
+ summary: RSpec client to publish test results to Probe Dock.
221
+ test_files: []