parallel_report_portal 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ module ParallelReportPortal
2
+ # Common file handling methods
3
+ module FileUtils
4
+
5
+ # Open a file with an exclusive lock and yield the open
6
+ # file to the block. If the system is unable to access the
7
+ # file it will block until the lock is removed. This method
8
+ # guarantees that the lock will be removed.
9
+ #
10
+ # @param [String] filename the name of the file to open
11
+ # @param [String] mode the mode to open the file with (e.g. +r+, +w++)
12
+ # @yieldparam [File] file the opened file
13
+ def file_open_exlock_and_block(filename, mode, &block)
14
+ file = File.new(filename, mode)
15
+ begin
16
+ file.flock(File::LOCK_EX)
17
+ yield(file) if block_given?
18
+ ensure
19
+ file.flock(File::LOCK_UN)
20
+ file.close
21
+ end
22
+ end
23
+
24
+ # Attempts to determin if the current environment is running under a
25
+ # parallel testing environment
26
+ #
27
+ # @return [Boolean] true if parallel
28
+ def parallel?
29
+ !ENV['PARALLEL_PID_FILE'].nil?
30
+ end
31
+
32
+ # Returns a pathname for the pid for this launch, initialising if necesssary.
33
+ #
34
+ # @return [Pathname] the pid pathname
35
+ def launch_id_file
36
+ @lock_file ||= Pathname(Dir.tmpdir) + ("report_portal_tracking_file_#{pid}.lck")
37
+ end
38
+
39
+ # Returns a pathname for the hierarchy of this launch, initialising if necesssary.
40
+ #
41
+ # @return [Pathname] the hierarchy pathname
42
+ def hierarchy_file
43
+ @hierarchy_file ||= Pathname(Dir.tmpdir) + ("report_portal_hierarchy_file_#{pid}.lck")
44
+ end
45
+
46
+ # Gets the pid of this process or the parent pid if running in parallel mode.
47
+ #
48
+ # @return [Integer] the pid
49
+ def pid
50
+ pid = parallel? ? Process.ppid : Process.pid
51
+ end
52
+
53
+ # Helper for deleting a file. It will not throw an exception
54
+ # if the file does not exist.
55
+ #
56
+ # @param [String] the filename to delete
57
+ def delete_file(filename)
58
+ File.delete(filename) if File.exist?(filename)
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,228 @@
1
+ require 'logger'
2
+ require 'tempfile'
3
+
4
+ module ParallelReportPortal
5
+ # A collection of methods for communicating with the ReportPortal
6
+ # REST interface.
7
+ module HTTP
8
+
9
+ # Creating class level logger and setting log level
10
+ @@logger = Logger.new(STDOUT)
11
+ @@logger.level = Logger::ERROR
12
+
13
+ # Construct the Report Portal project URL (as a string) based
14
+ # on the config settings.
15
+ #
16
+ # @return [String] URL the report portal base URL
17
+ def url
18
+ "#{ParallelReportPortal.configuration.endpoint}/#{ParallelReportPortal.configuration.project}"
19
+ end
20
+
21
+ # Helper method for constructing the +Bearer+ header
22
+ #
23
+ # @return [String] header the bearer header value
24
+ def authorization_header
25
+ "Bearer #{ParallelReportPortal.configuration.uuid}"
26
+ end
27
+
28
+ # Get a preconstructed Faraday HTTP connection
29
+ # which has the endpont and headers ready populated.
30
+ # This object is memoized.
31
+ #
32
+ # @return [Faraday::Connection] connection the HTTP connection object
33
+ def http_connection
34
+ @http_connection ||= Faraday.new(
35
+ url: url,
36
+ headers: {
37
+ 'Content-Type' => 'application/json',
38
+ 'Authorization' => authorization_header
39
+ }
40
+ ) do |f|
41
+ f.adapter :net_http_persistent, pool_size: 5 do |http|
42
+ http.idle_timeout = 100
43
+ end
44
+ end
45
+ end
46
+
47
+ # Get a preconstructed Faraday HTTP multipart connection
48
+ # which has the endpont and headers ready populated.
49
+ # This object is memoized.
50
+ #
51
+ # @return [Faraday::Connection] connection the HTTP connection object
52
+ def http_multipart_connection
53
+ @http_multipart_connection ||= Faraday.new(
54
+ url: url,
55
+ headers: {
56
+ 'Authorization' => authorization_header
57
+ }
58
+ ) do |conn|
59
+ conn.request :multipart
60
+ conn.request :url_encoded
61
+ conn.adapter :net_http_persistent, pool_size: 5 do |http|
62
+ # yields Net::HTTP::Persistent
63
+ http.idle_timeout = 100
64
+ end
65
+ end
66
+ end
67
+
68
+ # Send a request to ReportPortal to start a launch.
69
+ # It will bubble up any Faraday connection exceptions.
70
+ def req_launch_started(time)
71
+ resp = http_connection.post('launch') do |req|
72
+ req.body = {
73
+ name: ParallelReportPortal.configuration.launch,
74
+ start_time: time,
75
+ tags: ParallelReportPortal.configuration.tags,
76
+ description: ParallelReportPortal.configuration.description,
77
+ mode: (ParallelReportPortal.configuration.debug ? 'DEBUG' : 'DEFAULT' ),
78
+ attributes: ParallelReportPortal.configuration.attributes
79
+ }.to_json
80
+ end
81
+ if resp.success?
82
+ JSON.parse(resp.body)['id']
83
+ else
84
+ @@logger.error("Launch failed with response code #{resp.status} -- message #{resp.body}")
85
+ end
86
+ end
87
+
88
+ # Send a request to Report Portal to finish a launch.
89
+ # It will bubble up any Faraday connection exceptions.
90
+ def req_launch_finished(launch_id, time)
91
+ ParallelReportPortal.http_connection.put("launch/#{launch_id}/finish") do |req|
92
+ req.body = { end_time: time }.to_json
93
+ end
94
+ end
95
+
96
+ # Send a request to ReportPortal to start a feature.
97
+ # It will bubble up any Faraday connection exceptions.
98
+ #
99
+ # @return [String] id the UUID of the feature
100
+ def req_feature_started(launch_id, parent_id, feature, time)
101
+ description = if feature.description
102
+ feature.description.split("\n").map(&:strip).join(' ')
103
+ else
104
+ feature.file
105
+ end
106
+
107
+ req_hierarchy(launch_id,
108
+ "#{feature.keyword}: #{feature.name}",
109
+ parent_id,
110
+ 'TEST',
111
+ feature.tags.map(&:name),
112
+ description,
113
+ time )
114
+ end
115
+
116
+ # Sends a request to Report Portal to add an item into its hierarchy.
117
+ #
118
+ # @return [String] uuid the UUID of the newly created child
119
+ def req_hierarchy(launch_id, name, parent, type, tags, description, time )
120
+ resource = 'item'
121
+ resource += "/#{parent}" if parent
122
+ resp = ParallelReportPortal.http_connection.post(resource) do |req|
123
+ req.body = {
124
+ start_time: time,
125
+ name: name,
126
+ type: type,
127
+ launch_id: launch_id,
128
+ tags: tags,
129
+ description: description,
130
+ attributes: tags
131
+ }.to_json
132
+ end
133
+
134
+ if resp.success?
135
+ JSON.parse(resp.body)['id']
136
+ else
137
+ @@logger.warn("Starting a heirarchy failed with response code #{resp.status} -- message #{resp.body}")
138
+ end
139
+ end
140
+
141
+ # Send a request to Report Portal that a feature has completed.
142
+ def req_feature_finished(feature_id, time)
143
+ ParallelReportPortal.http_connection.put("item/#{feature_id}") do |req|
144
+ req.body = { end_time: time }.to_json
145
+ end
146
+ end
147
+
148
+ # Send a request to ReportPortal to start a test case.
149
+ #
150
+ # @return [String] uuid the UUID of the test case
151
+ def req_test_case_started(launch_id, feature_id, test_case, time)
152
+ resp = ParallelReportPortal.http_connection.post("item/#{feature_id}") do |req|
153
+
154
+ keyword = if test_case.respond_to?(:feature)
155
+ test_case.feature.keyword
156
+ else
157
+ test_case.keyword
158
+ end
159
+ req.body = {
160
+ start_time: time,
161
+ tags: test_case.tags.map(&:name),
162
+ name: "#{keyword}: #{test_case.name}",
163
+ type: 'STEP',
164
+ launch_id: launch_id,
165
+ description: test_case.location.to_s,
166
+ attributes: test_case.tags
167
+ }.to_json
168
+ end
169
+ if resp.success?
170
+ @test_case_id = JSON.parse(resp.body)['id'] if resp.success?
171
+ else
172
+ @@logger.warn("Starting a test case failed with response code #{resp.status} -- message #{resp.body}")
173
+ end
174
+ end
175
+
176
+ # Request that the test case be finished
177
+ def req_test_case_finished(test_case_id, status, time)
178
+ resp = ParallelReportPortal.http_connection.put("item/#{test_case_id}") do |req|
179
+ req.body = {
180
+ end_time: time,
181
+ status: status
182
+ }.to_json
183
+ end
184
+ end
185
+
186
+
187
+ # Request that Report Portal records a log record
188
+ def req_log(test_case_id, detail, level, time)
189
+ resp = ParallelReportPortal.http_connection.post('log') do |req|
190
+ req.body = {
191
+ item_id: test_case_id,
192
+ message: detail,
193
+ level: level,
194
+ time: time,
195
+ }.to_json
196
+ end
197
+ end
198
+
199
+
200
+ # Request that Report Portal attach a file to the test case.
201
+ #
202
+ # @param status [String] the status level of the log, e.g. info, warn
203
+ # @param path [String] the fully qualified path of the file to attach
204
+ # @param label [String] a label to add to the attachment, defaults to the filename
205
+ # @param time [Integer] the time in milliseconds for the attachment
206
+ # @param mime_type [String] the mimetype of the attachment
207
+ def send_file(status, path, label = nil, time = ParallelReportPortal.clock, mime_type = 'image/png')
208
+ File.open(File.realpath(path), 'rb') do |file|
209
+ label ||= File.basename(file)
210
+
211
+ # where did @test_case_id come from? ok, I know where it came from but this
212
+ # really should be factored out of here and state handled better
213
+ json = { level: status, message: label, item_id: @test_case_id, time: time, file: { name: File.basename(file) } }
214
+
215
+ json_file = Tempfile.new
216
+ json_file << [json].to_json
217
+ json_file.rewind
218
+
219
+ resp = http_multipart_connection.post("log") do |req|
220
+ req.body = {
221
+ json_request_part: Faraday::UploadIO.new(json_file, 'application/json'),
222
+ file: Faraday::UploadIO.new(file, mime_type)
223
+ }
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,3 @@
1
+ module ParallelReportPortal
2
+ VERSION = "2.0.0"
3
+ end
@@ -0,0 +1,45 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "parallel_report_portal/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "parallel_report_portal"
7
+ spec.version = ParallelReportPortal::VERSION
8
+ spec.authors = ["Nigel Brookes-Thomas"]
9
+ spec.email = ["nigel.brookes-thomas@dvla.gov.uk"]
10
+
11
+ spec.summary = %q{Run Cucumber Tests in parallel and with Cucumber 3 and 4+}
12
+ spec.description = %q{A Cucumber formatter which integrates with Report Portal and supports both the parallel_tests gem and cucumber-messages}
13
+ spec.homepage = "https://github.com/dvla/dvla-reportportal-ruby"
14
+
15
+ spec.license = 'MIT'
16
+
17
+ # spec.metadata["allowed_push_host"] = ""
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/dvla/dvla-reportportal-ruby"
21
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_development_dependency "appraisal", '~> 2.4'
33
+ spec.add_development_dependency "bump", "~> 0.8"
34
+ spec.add_development_dependency "bundler", "~> 2.0"
35
+ spec.add_development_dependency "rake", "~> 10.0"
36
+ spec.add_development_dependency "rspec", "~> 3.0"
37
+ spec.add_development_dependency "pry", "~> 0.12"
38
+ spec.add_development_dependency "webmock", "~> 3.12"
39
+
40
+ spec.add_runtime_dependency 'cucumber', '>= 3.2'
41
+ spec.add_runtime_dependency 'faraday', '~> 1.0'
42
+ spec.add_runtime_dependency 'parallel_tests', '>= 2.29.1'
43
+ spec.add_runtime_dependency 'rubytree', '~> 1.0'
44
+ spec.add_runtime_dependency 'net-http-persistent', '~> 4.0'
45
+ end
metadata ADDED
@@ -0,0 +1,241 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parallel_report_portal
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Nigel Brookes-Thomas
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-04-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: appraisal
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bump
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.8'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.12'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.12'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.12'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.12'
111
+ - !ruby/object:Gem::Dependency
112
+ name: cucumber
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '3.2'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '3.2'
125
+ - !ruby/object:Gem::Dependency
126
+ name: faraday
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: parallel_tests
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 2.29.1
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 2.29.1
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubytree
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '1.0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1.0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: net-http-persistent
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '4.0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '4.0'
181
+ description: A Cucumber formatter which integrates with Report Portal and supports
182
+ both the parallel_tests gem and cucumber-messages
183
+ email:
184
+ - nigel.brookes-thomas@dvla.gov.uk
185
+ executables: []
186
+ extensions: []
187
+ extra_rdoc_files: []
188
+ files:
189
+ - ".drone.yml"
190
+ - ".gitignore"
191
+ - ".rspec"
192
+ - Appraisals
193
+ - Gemfile
194
+ - Gemfile.lock
195
+ - LICENSE.txt
196
+ - ParallelReportPortal.drawio
197
+ - README.md
198
+ - Rakefile
199
+ - bin/console
200
+ - bin/setup
201
+ - gemfiles/.bundle/config
202
+ - gemfiles/cucumber_3.2.gemfile
203
+ - gemfiles/cucumber_4.1.gemfile
204
+ - gemfiles/cucumber_5.2.gemfile
205
+ - gemfiles/cucumber_6.0.gemfile
206
+ - lib/parallel_report_portal.rb
207
+ - lib/parallel_report_portal/.DS_Store
208
+ - lib/parallel_report_portal/clock.rb
209
+ - lib/parallel_report_portal/configuration.rb
210
+ - lib/parallel_report_portal/cucumber/formatter.rb
211
+ - lib/parallel_report_portal/cucumber/report.rb
212
+ - lib/parallel_report_portal/file_utils.rb
213
+ - lib/parallel_report_portal/http.rb
214
+ - lib/parallel_report_portal/version.rb
215
+ - parallel_report_portal.gemspec
216
+ homepage: https://github.com/dvla/dvla-reportportal-ruby
217
+ licenses:
218
+ - MIT
219
+ metadata:
220
+ homepage_uri: https://github.com/dvla/dvla-reportportal-ruby
221
+ source_code_uri: https://github.com/dvla/dvla-reportportal-ruby
222
+ post_install_message:
223
+ rdoc_options: []
224
+ require_paths:
225
+ - lib
226
+ required_ruby_version: !ruby/object:Gem::Requirement
227
+ requirements:
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ version: '0'
231
+ required_rubygems_version: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: '0'
236
+ requirements: []
237
+ rubygems_version: 3.1.4
238
+ signing_key:
239
+ specification_version: 4
240
+ summary: Run Cucumber Tests in parallel and with Cucumber 3 and 4+
241
+ test_files: []