rox-client-rspec 0.3.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
+ 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