rox-client-rspec 0.3.0

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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NTNlNmE4ZTM2MjhmZDg1M2VlMWI3NDM1NTE2NzhhYTgzYmZjYTNkNQ==
5
+ data.tar.gz: !binary |-
6
+ MjlmNTViYTI1OWMzOTQ1M2QyNzcwOTRiNThhNTM2ZGVhOTdjMTQ2Yg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ N2FjYWE5ZTI3NjRkODM0NGFmNDc5Mjk5OGM5Yjg0MzkyMzY2NTEzMmExYWFm
10
+ ZmY4ZDk4ZTRjMzBkZDg0YzQ0NjUyZjlkMmYzNWJjYTBkMDFmYzFmNmU3MzQ1
11
+ NzZmMWIwZGUxMWIwYTg4NWIyZDRmNDhmNTljZWRiMGE5N2RjMDc=
12
+ data.tar.gz: !binary |-
13
+ Y2U2OTQ0M2Y2YTE5ODBlYjI3NmM2MDUxMTA3M2RiZTM2M2VhMzhiOTcxZjVh
14
+ YjQ5YjFjN2EyYjA0MjliZWQ3M2ZlMzQ0YzcwYTZlZjE2OGU4ZDBhYzY1MWNm
15
+ NzJiY2IzZGM5NTEwZTU0NTQ1YWZlYjZiZmIwMTkxYzFiODRhMGU=
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', '~> 2.14'
7
+ gem 'oj', '~> 2.1'
8
+ gem 'httparty', '~> 0.11.0'
9
+ gem 'paint', '~> 0.8.6'
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 'bundler'
16
+ gem 'jeweler'
17
+ gem 'geminabox'
18
+ gem 'rake-version'
19
+ gem 'simplecov'
20
+ gem 'fakefs', require: 'fakefs/safe'
21
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012-2013 Lotaris SA
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,148 @@
1
+ # RSpec ROX Client
2
+
3
+ **RSpec client for [ROX Center](https://github.com/lotaris/rox-center).**
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/rox-client-rspec.png)](http://badge.fury.io/rb/rox-client-rspec)
6
+
7
+ ## Requirements
8
+
9
+ * RSpec 2.14
10
+
11
+ ## Installation
12
+
13
+ In your Gemfile:
14
+
15
+ ```rb
16
+ gem 'rox-client-rspec', '~> 0.3.0'
17
+ ```
18
+
19
+ Manually:
20
+
21
+ gem install rox-client-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, you must assign it a ROX test key generated from your ROX Center server.
28
+
29
+ **NOTE: currently, all the tests in your test suite must be assigned a test key for the client to work.**
30
+
31
+ Test keys are assigned to test using RSpec metadata:
32
+
33
+ ```rb
34
+ it "should work", rox: { key: 'abcdefghijkl' } do
35
+ expect(true).to be_true
36
+ end
37
+
38
+ it(nil, rox: { key: 'bcdefghijklm' }){ should validate_presence_of(:name) }
39
+ ```
40
+
41
+ <a name="setup"></a>
42
+ ## Setup
43
+
44
+ ROX clients use [YAML](http://yaml.org) files for configuration.
45
+ To use the RSpec ROX Client, you need two configuration files and you must set up the client in your spec helper file.
46
+
47
+ In your home folder, you must create the `~/.rox/config.yml` configuration file.
48
+
49
+ ```yml
50
+ # List of ROX Center servers you can submit test results to.
51
+ servers:
52
+ - name: rox.example.com # A custom name for your ROX Center server.
53
+ # You will use this in the client configuration file.
54
+ # We recommend using the domain name where you deployed it.
55
+
56
+ apiUrl: https://rox.example.com/api # The URL of your ROX Center server's API.
57
+ # This is the domain where you deployed it with /api.
58
+
59
+ apiKeyId: 39fuc7x85lsoy9c0ek2d # Your user credentials on this server.
60
+ apiKeySecret: mwpqvvmagzoegxnqptxdaxkxonjmvrlctwcrfmowibqcpnsdqd
61
+
62
+ # If true, test results will be uploaded to ROX Center.
63
+ # Set to false to temporarily disable publishing.
64
+ # You can change this at runtime from the command line by setting the
65
+ # ROX_PUBLISH environment variable to 0 (false) or 1 (true).
66
+ publish: true
67
+ ```
68
+
69
+ In the project directory where you run RSpec, you must add the `rox.yml` client configuration file:
70
+
71
+ ```yml
72
+ # Configuration specific to your project.
73
+ project:
74
+ name: My Project
75
+ version: 1.2.3
76
+ apiId: 154sic93pxs0 # The API key of your project in the ROX Center server.
77
+
78
+ # Where the client should store its temporary files.
79
+ # The client will work without it but it is required for some advanced features.
80
+ workspace: tmp/rox
81
+
82
+ # Client advanced features.
83
+ payload:
84
+
85
+ # Saves a copy of the test payload sent to the ROX Center server for debugging.
86
+ # The file will be saved in rspec/servers/<SERVER_NAME>/payload.json.
87
+ save: false
88
+
89
+ # If you track a large number of tests (more than a thousand), enabling this
90
+ # will reduce the size of the test payloads sent to ROX Center server by caching
91
+ # test information that doesn't change often such as the name.
92
+ cache: false
93
+
94
+ # Prints a copy of the test payload sent to the ROX Center server for debugging.
95
+ # Temporarily enable at runtime by setting the ROX_PRINT_PAYLOAD environment variable to 1.
96
+ print: false
97
+
98
+ # The name of the ROX Center server to upload test results to.
99
+ # This name must be one of the server names in the ~/.rox/config.yml file.
100
+ # You can change this at runtime from the command line by setting the
101
+ # ROX_SERVER environment variable.
102
+ server: rox.example.com
103
+ ```
104
+
105
+ Finally, you must enable the client in your spec helper file (usually `spec/spec_helper.rb`).
106
+
107
+ ```yml
108
+ RoxClient::RSpec.configure do |config|
109
+
110
+ # Optional ROX Center category to add to all the tests sent with this client.
111
+ config.project.category = 'RSpec'
112
+ end
113
+ ```
114
+
115
+ The next time you run your test suite, the RSpec ROX Client will send the results to your ROX Center server.
116
+
117
+ ## Contributing
118
+
119
+ * [Fork](https://help.github.com/articles/fork-a-repo)
120
+ * Create a topic branch - `git checkout -b my_feature`
121
+ * Push to your branch - `git push origin my_feature`
122
+ * Create a [pull request](http://help.github.com/pull-requests/) from your branch
123
+
124
+ Please add a changelog entry with your name for new features and bug fixes.
125
+
126
+ ## License
127
+
128
+ The RSpec ROX Client is licensed under the [MIT License](http://opensource.org/licenses/MIT).
129
+
130
+ Copyright (c) 2012-2013 Lotaris SA
131
+
132
+ Permission is hereby granted, free of charge, to any person obtaining a copy
133
+ of this software and associated documentation files (the "Software"), to deal
134
+ in the Software without restriction, including without limitation the rights
135
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
136
+ copies of the Software, and to permit persons to whom the Software is
137
+ furnished to do so, subject to the following conditions:
138
+
139
+ The above copyright notice and this permission notice shall be included in
140
+ all copies or substantial portions of the Software.
141
+
142
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
143
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
144
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
145
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
146
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
147
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
148
+ THE SOFTWARE.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
@@ -0,0 +1,14 @@
1
+ # encoding: UTF-8
2
+ require 'rspec'
3
+
4
+ module RoxClient
5
+
6
+ module RSpec
7
+ VERSION = '0.3.0'
8
+
9
+ class Error < StandardError; end
10
+ class PayloadError < Error; end
11
+ end
12
+ end
13
+
14
+ Dir[File.join File.dirname(__FILE__), File.basename(__FILE__, '.*'), '*.rb'].each{ |lib| require lib }
@@ -0,0 +1,60 @@
1
+
2
+ module RoxClient::RSpec
3
+
4
+ class Cache
5
+
6
+ class Error < RoxClient::RSpec::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.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
+ self
34
+ end
35
+
36
+ def known? test_result
37
+ @tests[@project_api_id] && !!@tests[@project_api_id][test_result.key]
38
+ end
39
+
40
+ def stale? test_result
41
+ @tests[@project_api_id] && test_result_hash(test_result) != @tests[@project_api_id][test_result.key]
42
+ end
43
+
44
+ private
45
+
46
+ def validate!
47
+ required = { "workspace" => @workspace, "server name" => @server_name, "project API identifier" => @project_api_id }
48
+ missing = required.keys.select{ |k| !required[k] }
49
+ raise Error.new("Missing cache options: #{missing.join ', '}") if missing.any?
50
+ end
51
+
52
+ def test_result_hash r
53
+ Digest::SHA2.hexdigest "#{r.name} || #{r.category} || #{r.tags.collect(&:to_s).sort.join(' ')} || #{r.tickets.collect(&:to_s).sort.join(' ')}"
54
+ end
55
+
56
+ def cache_file
57
+ @cache_file ||= File.join(@workspace, 'rspec', 'servers', @server_name, 'cache.json')
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,119 @@
1
+
2
+ module RoxClient::RSpec
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
+ puts
22
+ return fail "No server to publish results to" if !@server
23
+
24
+ test_run.uid = @uid.load_uid
25
+
26
+ payload_options = @server.payload_options
27
+
28
+ cache_enabled = @cache_payload && load_cache
29
+ payload_options[:cache] = @cache if cache_enabled
30
+
31
+ return false unless payload = build_payload(test_run, payload_options)
32
+
33
+ published = if !@publish
34
+ puts Paint["ROX - Publishing disabled", :yellow]
35
+ false
36
+ elsif publish_payload payload
37
+ @cache.save test_run if cache_enabled
38
+ true
39
+ else
40
+ false
41
+ end
42
+
43
+ save_payload payload if @save_payload
44
+ print_payload payload if @print_payload
45
+
46
+ published
47
+ end
48
+
49
+ private
50
+
51
+ def build_payload test_run, options = {}
52
+ begin
53
+ TestPayload.new(test_run).to_h options
54
+ rescue PayloadError => e
55
+ fail e.message
56
+ end
57
+ end
58
+
59
+ def fail msg, type = :error
60
+ styles = { warning: [ :yellow ], error: [ :bold, :red ] }
61
+ warn Paint["ROX - #{msg}", *styles[type]]
62
+ false
63
+ end
64
+
65
+ def load_cache
66
+ begin
67
+ @cache.load
68
+ rescue Cache::Error => e
69
+ warn Paint["ROX - #{e.message}", :yellow]
70
+ false
71
+ end
72
+ end
73
+
74
+ def print_payload payload
75
+ puts Paint['ROX - Printing payload...', :yellow]
76
+ begin
77
+ puts JSON.pretty_generate(payload)
78
+ rescue
79
+ puts payload.inspect
80
+ end
81
+ end
82
+
83
+ def save_payload payload
84
+
85
+ missing = { "workspace" => @workspace, "server" => @server }.inject([]){ |memo,(k,v)| !v ? memo << k : memo }
86
+ return fail "Cannot save payload without a #{missing.join ' and '}" if missing.any?
87
+
88
+ FileUtils.mkdir_p File.dirname(payload_file)
89
+ File.open(payload_file, 'w'){ |f| f.write Oj.dump(payload, mode: :strict) }
90
+ end
91
+
92
+ def payload_file
93
+ @payload_file ||= File.join(@workspace, 'rspec', 'servers', @server.name, 'payload.json')
94
+ end
95
+
96
+ def publish_payload payload
97
+
98
+ puts Paint["ROX - Sending payload to #{@server.api_url}...", :magenta]
99
+
100
+ begin
101
+ if @local_mode
102
+ puts Paint['ROX - LOCAL MODE: not actually sending payload.', :yellow]
103
+ else
104
+ @server.upload payload
105
+ end
106
+ puts Paint["ROX - Done!", :green]
107
+ true
108
+ rescue Server::Error => e
109
+ warn Paint["ROX - Upload failed!", :red, :bold]
110
+ warn Paint["ROX - #{e.message}", :red, :bold]
111
+ if e.response
112
+ warn Paint["ROX - Dumping response body...", :red, :bold]
113
+ warn e.response.body
114
+ end
115
+ false
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,178 @@
1
+ require 'yaml'
2
+
3
+ # Utilities to send test results to ROX Center.
4
+ module RoxClient::RSpec
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["ROX - #{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 < RoxClient::RSpec::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 ROX 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
+ servers = {}
96
+
97
+ default_server_options = { project_api_id: config[:project][:api_id] }
98
+ config[:servers].each do |server_options|
99
+
100
+ name = server_options[:name].to_s.strip
101
+ if name.empty?
102
+ @load_warnings << "Ignoring unnamed server"
103
+ next
104
+ elsif servers[name]
105
+ servers[name].merge! server_options
106
+ next
107
+ end
108
+
109
+ servers[name] = server_options
110
+ end
111
+
112
+ servers.each_key{ |name| servers[name] = Server.new default_server_options.merge(servers[name]) }
113
+
114
+ [ servers.values, servers[@server_name.to_s.strip] ]
115
+ end
116
+
117
+ def load_config_files
118
+
119
+ configs = [ home_config_file, working_config_file ]
120
+ actual_configs = configs.select{ |f| File.exists? f }
121
+
122
+ if actual_configs.empty?
123
+ @load_warnings << %|no config file found, looking for:\n #{configs.join "\n "}|
124
+ return false
125
+ end
126
+
127
+ actual_configs.collect!{ |f| YAML.load_file f }
128
+
129
+ actual_configs.inject({}) do |memo,yml|
130
+ memo.merge! parse_general_options(yml)
131
+ memo[:servers] = (memo[:servers] || []) + (yml['servers'].kind_of?(Array) ? yml['servers'].collect{ |s| parse_server_options s } : [])
132
+ memo[:payload] = (memo[:payload] || {}).merge parse_payload_options(yml['payload'])
133
+ memo[:project] = (memo[:project] || {}).merge parse_project_options(yml['project'])
134
+ memo
135
+ end
136
+ end
137
+
138
+ def home_config_file
139
+ File.join File.expand_path('~'), '.rox', 'config.yml'
140
+ end
141
+
142
+ def working_config_file
143
+ File.expand_path ENV['ROX_CONFIG'] || 'rox.yml', Dir.pwd
144
+ end
145
+
146
+ def parse_env_flag name, default = false
147
+ val = parse_env_option name
148
+ val ? !!val.to_s.strip.match(/\A(1|t|true)\Z/i) : default
149
+ end
150
+
151
+ def parse_env_option name
152
+ var = "ROX_#{name.upcase}"
153
+ ENV.key?(var) ? ENV[var] : nil
154
+ end
155
+
156
+ def parse_general_options h
157
+ parse_options h, %w(publish server local workspace)
158
+ end
159
+
160
+ def parse_server_options h
161
+ parse_options h, %w(name apiUrl apiKeyId apiKeySecret apiVersion projectApiId)
162
+ end
163
+
164
+ def parse_payload_options h
165
+ parse_options h, %w(save cache print)
166
+ end
167
+
168
+ def parse_project_options h
169
+ # TODO: remove project name once API v0 is dead
170
+ parse_options h, %w(name version apiId category tags tickets)
171
+ end
172
+
173
+ def parse_options h, keys
174
+ return {} unless h.kind_of? Hash
175
+ keys.inject({}){ |memo,k| memo[k.gsub(/(.)([A-Z])/, '\1_\2').downcase.to_sym] = h[k] if h.key?(k); memo }
176
+ end
177
+ end
178
+ end