rainforest-cli 1.2.0 → 1.2.1

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +5 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +8 -0
  7. data/Gemfile +7 -1
  8. data/README.md +2 -0
  9. data/Rakefile +6 -4
  10. data/circle.yml +3 -0
  11. data/lib/rainforest/cli.rb +20 -16
  12. data/lib/rainforest/cli/constants.rb +4 -0
  13. data/lib/rainforest/cli/csv_importer.rb +6 -6
  14. data/lib/rainforest/cli/git_trigger.rb +2 -1
  15. data/lib/rainforest/cli/http_client.rb +50 -13
  16. data/lib/rainforest/cli/options.rb +71 -39
  17. data/lib/rainforest/cli/remote_tests.rb +49 -0
  18. data/lib/rainforest/cli/runner.rb +19 -17
  19. data/lib/rainforest/cli/test_files.rb +32 -14
  20. data/lib/rainforest/cli/test_importer.rb +35 -155
  21. data/lib/rainforest/cli/test_parser.rb +38 -14
  22. data/lib/rainforest/cli/uploader.rb +107 -0
  23. data/lib/rainforest/cli/validator.rb +158 -0
  24. data/lib/rainforest/cli/version.rb +2 -1
  25. data/rainforest-cli.gemspec +14 -12
  26. data/spec/cli_spec.rb +84 -90
  27. data/spec/csv_importer_spec.rb +13 -8
  28. data/spec/git_trigger_spec.rb +28 -15
  29. data/spec/http_client_spec.rb +57 -0
  30. data/spec/options_spec.rb +72 -70
  31. data/spec/rainforest-example/example_test.rfml +2 -1
  32. data/spec/remote_tests_spec.rb +22 -0
  33. data/spec/runner_spec.rb +17 -16
  34. data/spec/spec_helper.rb +16 -9
  35. data/spec/test_files_spec.rb +20 -24
  36. data/spec/uploader_spec.rb +54 -0
  37. data/spec/validation-examples/circular_embeds/test1.rfml +5 -0
  38. data/spec/validation-examples/circular_embeds/test2.rfml +5 -0
  39. data/spec/validation-examples/correct_embeds/embedded_test.rfml +6 -0
  40. data/spec/validation-examples/correct_embeds/test_with_embedded.rfml +8 -0
  41. data/spec/validation-examples/missing_embeds/correct_test.rfml +8 -0
  42. data/spec/validation-examples/missing_embeds/incorrect_test.rfml +8 -0
  43. data/spec/validation-examples/parse_errors/no_parse_errors.rfml +6 -0
  44. data/spec/validation-examples/parse_errors/no_question.rfml +5 -0
  45. data/spec/validation-examples/parse_errors/no_question_mark.rfml +6 -0
  46. data/spec/validation-examples/parse_errors/no_rfml_id.rfml +5 -0
  47. data/spec/validator_spec.rb +119 -0
  48. metadata +96 -16
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ class RainforestCli::RemoteTests
3
+ def initialize(api_token = nil)
4
+ Rainforest.api_key = api_token
5
+ end
6
+
7
+ def api_token_set?
8
+ !Rainforest.api_key.nil?
9
+ end
10
+
11
+ def rfml_ids
12
+ @rfml_ids ||= tests.map(&:rfml_id)
13
+ end
14
+
15
+ def tests
16
+ @tests ||= fetch_tests
17
+ end
18
+
19
+ def fetch_tests
20
+ if api_token_set?
21
+ begin
22
+ logger.info 'Syncing tests...'
23
+ tests = Rainforest::Test.all(page_size: 1000)
24
+ logger.info 'Syncing completed.'
25
+ tests
26
+ rescue Rainforest::ApiError => e
27
+ logger.error "Encountered API Error: #{e.message}"
28
+ exit 4
29
+ end
30
+ else
31
+ logger.info ''
32
+ logger.info 'No API Token set. Using local tests only...'
33
+ logger.info ''
34
+ []
35
+ end
36
+ end
37
+
38
+ def primary_key_dictionary
39
+ @primary_key_dictionary ||= {}.tap do |primary_key_dictionary|
40
+ tests.each do |rf_test|
41
+ primary_key_dictionary[rf_test.rfml_id] = rf_test.id
42
+ end
43
+ end
44
+ end
45
+
46
+ def logger
47
+ RainforestCli.logger
48
+ end
49
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module RainforestCli
2
3
  class Runner
3
4
  attr_reader :options, :client
@@ -16,7 +17,7 @@ module RainforestCli
16
17
  post_opts = make_create_run_options
17
18
 
18
19
  logger.debug "POST options: #{post_opts.inspect}"
19
- logger.info "Issuing run"
20
+ logger.info 'Issuing run'
20
21
 
21
22
  response = client.post('/runs', post_opts)
22
23
 
@@ -26,7 +27,7 @@ module RainforestCli
26
27
  end
27
28
 
28
29
  if options.foreground?
29
- run_id = response.fetch("id")
30
+ run_id = response.fetch('id')
30
31
  wait_for_run_completion(run_id)
31
32
  else
32
33
  true
@@ -37,12 +38,13 @@ module RainforestCli
37
38
  running = true
38
39
  while running
39
40
  Kernel.sleep 5
40
- response = client.get("/runs/#{run_id}")
41
+ response = client.get("/runs/#{run_id}", {}, retries_on_failures: true)
41
42
  if response
42
43
  state_details = response.fetch('state_details')
43
- unless state_details.fetch("is_final_state")
44
- logger.info "Run #{run_id} is #{response['state']} and is #{response['current_progress']['percent']}% complete"
45
- running = false if response["result"] == 'failed' && options.failfast?
44
+ unless state_details.fetch('is_final_state')
45
+ state, current_progress = response.values_at('state', 'current_progress')
46
+ logger.info "Run #{run_id} is #{state} and is #{current_progress['percent']}% complete"
47
+ running = false if response['result'] == 'failed' && options.failfast?
46
48
  else
47
49
  logger.info "Run #{run_id} is now #{response["state"]} and has #{response["result"]}"
48
50
  running = false
@@ -50,11 +52,11 @@ module RainforestCli
50
52
  end
51
53
  end
52
54
 
53
- if url = response["frontend_url"]
54
- logger.info "The detailed results are available at #{url}"
55
+ if response['frontend_url']
56
+ logger.info "The detailed results are available at #{response['frontend_url']}"
55
57
  end
56
58
 
57
- if response["result"] != "passed"
59
+ if response['result'] != 'passed'
58
60
  exit 1
59
61
  end
60
62
  end
@@ -62,28 +64,28 @@ module RainforestCli
62
64
  def make_create_run_options
63
65
  post_opts = {}
64
66
  if options.git_trigger?
65
- logger.debug "Checking last git commit message:"
67
+ logger.debug 'Checking last git commit message:'
66
68
  commit_message = GitTrigger.last_commit_message
67
69
  logger.debug commit_message
68
70
 
69
71
  # Show some messages to users about tests/tags being overriden
70
72
  unless options.tags.empty?
71
- logger.warn "Specified tags are ignored when using --git-trigger"
73
+ logger.warn 'Specified tags are ignored when using --git-trigger'
72
74
  else
73
- logger.warn "Specified tests are ignored when using --git-trigger"
75
+ logger.warn 'Specified tests are ignored when using --git-trigger'
74
76
  end
75
77
 
76
78
  if GitTrigger.git_trigger_should_run?(commit_message)
77
79
  tags = GitTrigger.extract_hashtags(commit_message)
78
80
  if tags.empty?
79
- logger.error "Triggered via git, but no hashtags detected. Please use commit message format:"
81
+ logger.error 'Triggered via git, but no hashtags detected. Please use commit message format:'
80
82
  logger.error "\t'some message. @rainforest #tag1 #tag2"
81
83
  exit 2
82
84
  else
83
85
  post_opts[:tags] = [tags.join(',')]
84
86
  end
85
87
  else
86
- logger.info "Not triggering as @rainforest was not mentioned in last commit message."
88
+ logger.info 'Not triggering as @rainforest was not mentioned in last commit message.'
87
89
  exit 0
88
90
  end
89
91
  else
@@ -116,7 +118,7 @@ module RainforestCli
116
118
  end
117
119
 
118
120
  def list_generators
119
- client.get("/generators")
121
+ client.get('/generators')
120
122
  end
121
123
 
122
124
  def delete_generator(name)
@@ -133,12 +135,12 @@ module RainforestCli
133
135
 
134
136
  def get_environment_id url
135
137
  unless url_valid?(url)
136
- logger.fatal "The custom URL is invalid"
138
+ logger.fatal 'The custom URL is invalid'
137
139
  exit 2
138
140
  end
139
141
 
140
142
  env_post_body = { name: 'temporary-env-for-custom-url-via-CLI', url: url }
141
- environment = client.post("/environments", env_post_body)
143
+ environment = client.post('/environments', env_post_body)
142
144
 
143
145
  if environment['error']
144
146
  # I am talking about a URL here because the environments are pretty
@@ -1,32 +1,50 @@
1
+ # frozen_string_literal: true
1
2
  class RainforestCli::TestFiles
2
- DEFAULT_TEST_FOLDER = './spec/rainforest'.freeze
3
- EXT = ".rfml".freeze
3
+ DEFAULT_TEST_FOLDER = './spec/rainforest'
4
+ FILE_EXTENSION = '.rfml'
4
5
 
5
- attr_reader :test_folder, :test_paths, :test_data
6
+ attr_reader :test_folder, :test_data
6
7
 
7
8
  def initialize(test_folder = nil)
8
- @test_folder = test_folder || DEFAULT_TEST_FOLDER
9
-
10
- unless Dir.exists?(@test_folder)
11
- Dir.mkdir(@test_folder)
9
+ if test_folder.nil?
10
+ RainforestCli.logger.info "No test folder supplied. Using default folder: #{DEFAULT_TEST_FOLDER}"
11
+ @test_folder = File.expand_path(DEFAULT_TEST_FOLDER)
12
+ else
13
+ @test_folder = File.expand_path(test_folder)
12
14
  end
13
- @test_paths = "#{@test_folder}/**/*#{EXT}"
14
- @test_data = [].tap do |all_tests|
15
- Dir.glob(@test_paths) do |file_name|
16
- all_tests << RainforestCli::TestParser::Parser.new(File.read(file_name)).process
15
+ end
16
+
17
+ def test_paths
18
+ "#{@test_folder}/**/*#{FILE_EXTENSION}"
19
+ end
20
+
21
+ def test_data
22
+ if @test_data.nil?
23
+ @test_data = []
24
+ if Dir.exist?(@test_folder)
25
+ Dir.glob(test_paths) do |file_name|
26
+ @test_data << RainforestCli::TestParser::Parser.new(file_name).process
27
+ end
17
28
  end
18
29
  end
30
+ @test_data
19
31
  end
20
32
 
21
33
  def file_extension
22
- EXT
34
+ FILE_EXTENSION
23
35
  end
24
36
 
25
37
  def rfml_ids
26
- @test_data.map(&:rfml_id)
38
+ test_data.map(&:rfml_id)
27
39
  end
28
40
 
29
41
  def count
30
- @test_data.count
42
+ test_data.count
43
+ end
44
+
45
+ def test_dictionary
46
+ {}.tap do |dictionary|
47
+ test_data.each { |rfml_test| dictionary[rfml_test.rfml_id] = rfml_test }
48
+ end
31
49
  end
32
50
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'securerandom'
2
3
  require 'rainforest'
3
4
  require 'parallel'
@@ -5,7 +6,6 @@ require 'ruby-progressbar'
5
6
 
6
7
  class RainforestCli::TestImporter
7
8
  attr_reader :options, :client, :test_files
8
- THREADS = 32.freeze
9
9
 
10
10
  SAMPLE_FILE = <<EOF
11
11
  #! %s (Test ID - only edit if this test has not yet been uploaded)
@@ -33,13 +33,17 @@ EOF
33
33
  RainforestCli.logger
34
34
  end
35
35
 
36
+ def threads
37
+ RainforestCli::THREADS
38
+ end
39
+
36
40
  def export
37
41
  tests = Rainforest::Test.all(page_size: 1000)
38
42
  p = ProgressBar.create(title: 'Rows', total: tests.count, format: '%a %B %p%% %t')
39
- Parallel.each(tests, in_threads: THREADS, finish: lambda { |item, i, result| p.increment }) do |test|
43
+ Parallel.each(tests, in_threads: threads, finish: lambda { |_item, _i, _result| p.increment }) do |test|
40
44
 
41
45
  # File name
42
- file_name = sprintf('%010d', test.id) + "_" + test.title.strip.gsub(/[^a-z0-9 ]+/i, '').gsub(/ +/, '_').downcase
46
+ file_name = sprintf('%010d', test.id) + '_' + test.title.strip.gsub(/[^a-z0-9 ]+/i, '').gsub(/ +/, '_').downcase
43
47
  file_name = create_new(file_name)
44
48
  File.truncate(file_name, 0)
45
49
 
@@ -64,7 +68,7 @@ EOF
64
68
  index = _process_element(file, sub_element, index)
65
69
  end
66
70
  when 'step'
67
- file.puts "" unless index == 0
71
+ file.puts '' unless index == 0
68
72
  file.puts "# step #{index + 1}" if @options.debug
69
73
  file.puts element[:element][:action]
70
74
  file.puts element[:element][:response]
@@ -81,18 +85,26 @@ EOF
81
85
  out = []
82
86
 
83
87
  has_id = false
84
- test.description.to_s.strip.lines.map(&:chomp).each_with_index do |line, line_no|
88
+ test.description.to_s.strip.lines.map(&:chomp).each_with_index do |line, _line_no|
85
89
  line = line.gsub(/\#+$/, '').strip
86
90
 
87
91
  # make sure the test has an ID
88
- has_id = true if line[0] == "!"
92
+ has_id = true if line[0] == '!'
89
93
 
90
- out << "#" + line
94
+ out << '#' + line
91
95
  end
92
96
 
93
97
  unless has_id
94
- browsers = test.browsers.map {|b| b[:name] if b[:state] == "enabled" }.compact
95
- out = ["#! #{SecureRandom.uuid}", "# title: #{test.title}", "# start_uri: #{test.start_uri}", "# tags: #{test.tags.join(", ")}", "# browsers: #{browsers.join(", ")}", "#", " "] + out
98
+ browsers = test.browsers.map {|b| b[:name] if b[:state] == 'enabled' }.compact
99
+ out = [
100
+ "#! #{SecureRandom.uuid}",
101
+ "# title: #{test.title}",
102
+ "# start_uri: #{test.start_uri}",
103
+ "# tags: #{test.tags.join(", ")}",
104
+ "# browsers: #{browsers.join(", ")}",
105
+ '#',
106
+ ' ',
107
+ ] + out
96
108
  end
97
109
 
98
110
  out.compact.join("\n")
@@ -100,9 +112,9 @@ EOF
100
112
 
101
113
  def _get_id test
102
114
  id = nil
103
- test.description.to_s.strip.lines.map(&:chomp).each_with_index do |line, line_no|
115
+ test.description.to_s.strip.lines.map(&:chomp).each_with_index do |line, _line_no|
104
116
  line = line.gsub(/\#+$/, '').strip
105
- if line[0] == "!"
117
+ if line[0] == '!'
106
118
  id = line[1..-1].split(' ').first
107
119
  break
108
120
  end
@@ -110,57 +122,24 @@ EOF
110
122
  id
111
123
  end
112
124
 
113
- def upload
114
- # Prioritize embedded tests before other tests
115
- upload_groups = []
116
- unordered_tests = []
117
- queued_tests = test_files.test_data.dup
118
-
119
- until queued_tests.empty?
120
- new_ordered_group = []
121
- ordered_ids = upload_groups.flatten.map(&:rfml_id)
122
-
123
- queued_tests.each do |rfml_test|
124
- if (rfml_test.embedded_ids - ordered_ids).empty?
125
- new_ordered_group << rfml_test
126
- else
127
- unordered_tests << rfml_test
128
- end
129
- end
130
-
131
- upload_groups << new_ordered_group
132
- queued_tests = unordered_tests
133
- unordered_tests = []
134
- end
135
-
136
- logger.info "Uploading tests..."
137
-
138
- # Upload in parallel if order doesn't matter
139
- if upload_groups.count > 1
140
- upload_groups_sequentially(upload_groups)
141
- else
142
- upload_group_in_parallel(upload_groups.first)
143
- end
144
- end
145
-
146
125
  def validate
147
126
  tests = {}
148
127
  has_errors = []
149
128
 
150
129
  Dir.glob(test_files.test_paths).each do |file_name|
151
- out = RainforestCli::TestParser::Parser.new(File.read(file_name)).process
130
+ out = RainforestCli::TestParser::Parser.new(file_name).process
152
131
 
153
132
  tests[file_name] = out
154
133
  has_errors << file_name if out.errors != {}
155
134
  end
156
135
 
157
136
  if !has_errors.empty?
158
- logger.error "Parsing errors:"
159
- logger.error ""
137
+ logger.error 'Parsing errors:'
138
+ logger.error ''
160
139
  has_errors.each do |file_name|
161
- logger.error " " + file_name
162
- tests[file_name].errors.each do |line, error|
163
- logger.error "\t#{error.to_s}"
140
+ logger.error ' ' + file_name
141
+ tests[file_name].errors.each do |_line, error|
142
+ logger.error "\t#{error}"
164
143
  end
165
144
  end
166
145
 
@@ -168,7 +147,7 @@ EOF
168
147
  end
169
148
 
170
149
  if @options.debug
171
- tests.each do |file_name,test|
150
+ tests.each do |file_name, test|
172
151
  logger.debug test.inspect
173
152
  logger.debug "#{file_name}"
174
153
  logger.debug test.description
@@ -177,7 +156,7 @@ EOF
177
156
  end
178
157
  end
179
158
  else
180
- logger.info "[VALID]"
159
+ logger.info '[VALID]'
181
160
  end
182
161
 
183
162
  return tests
@@ -191,112 +170,13 @@ EOF
191
170
  uuid = SecureRandom.uuid
192
171
  name = "#{uuid}#{ext}" unless name
193
172
  name += ext unless name[-ext.length..-1] == ext
194
- name = File.join([@test_files.test_folder, name])
195
173
 
196
- File.open(name, "w") { |file| file.write(sprintf(SAMPLE_FILE, uuid)) }
174
+ FileUtils.mkdir_p(test_files.test_folder) unless Dir.exist?(test_files.test_folder)
175
+ name = File.join([test_files.test_folder, name])
176
+
177
+ File.open(name, 'w') { |file| file.write(sprintf(SAMPLE_FILE, uuid)) }
197
178
 
198
179
  logger.info "Created #{name}" if file_name.nil?
199
180
  name
200
181
  end
201
-
202
- private
203
-
204
- def upload_groups_sequentially(upload_groups)
205
- progress_bar = ProgressBar.create(title: 'Rows', total: test_files.count, format: '%a %B %p%% %t')
206
- upload_groups.each_with_index do |rfml_tests, idx|
207
- if idx == (rfml_tests.length - 1)
208
- upload_group_in_parallel(rfml_tests, progress_bar)
209
- else
210
- rfml_tests.each { |rfml_test| upload_test(rfml_test) }
211
- progress_bar.increment
212
- end
213
- end
214
- end
215
-
216
- def upload_group_in_parallel(rfml_tests, progress_bar = nil)
217
- progress_bar ||= ProgressBar.create(title: 'Rows', total: rfml_tests.count, format: '%a %B %p%% %t')
218
- Parallel.each(rfml_tests, in_threads: THREADS, finish: lambda { |item, i, result| progress_bar.increment }) do |rfml_test|
219
- upload_test(rfml_test)
220
- end
221
- end
222
-
223
- def upload_test(rfml_test)
224
- return unless rfml_test.steps.count > 0
225
-
226
- if @options.debug
227
- logger.debug "Starting: #{rfml_test.rfml_id}"
228
- logger.debug "\t#{rfml_test.start_uri || "/"}"
229
- end
230
-
231
- test_obj = create_test_obj(rfml_test)
232
- # Upload the test
233
- begin
234
- if rfml_id_mappings[rfml_test.rfml_id]
235
- t = Rainforest::Test.update(rfml_id_mappings[rfml_test.rfml_id], test_obj)
236
-
237
- logger.info "\tUpdated #{rfml_test.rfml_id} -- ##{t.id}" if @options.debug
238
- else
239
- t = Rainforest::Test.create(test_obj)
240
-
241
- logger.info "\tCreated #{rfml_test.rfml_id} -- ##{t.id}" if @options.debug
242
- rfml_id_mappings[rfml_test.rfml_id] = t.id
243
- end
244
- rescue => e
245
- logger.fatal "Error: #{rfml_test.rfml_id}: #{e}"
246
- exit 2
247
- end
248
- end
249
-
250
- def rfml_id_mappings
251
- if @_id_mappings.nil?
252
- @_id_mappings = {}.tap do |id_mappings|
253
- Rainforest::Test.all(page_size: 1000, rfml_ids: test_files.rfml_ids).each do |rf_test|
254
- rfml_id = rf_test.rfml_id
255
- next if rfml_id.nil?
256
-
257
- id_mappings[rfml_id] = rf_test.id
258
- end
259
- end
260
- end
261
- @_id_mappings
262
- end
263
-
264
- def create_test_obj(rfml_test)
265
- test_obj = {
266
- start_uri: rfml_test.start_uri || "/",
267
- title: rfml_test.title,
268
- description: rfml_test.description,
269
- tags: (["ro"] + rfml_test.tags).uniq,
270
- rfml_id: rfml_test.rfml_id,
271
- elements: rfml_test.steps.map do |step|
272
- case step.type
273
- when :step
274
- {
275
- type: 'step',
276
- redirection: true,
277
- element: {
278
- action: step.action,
279
- response: step.response
280
- }
281
- }
282
- when :test
283
- {
284
- type: 'test',
285
- redirection: true,
286
- element: {
287
- id: rfml_id_mappings[step.rfml_id]
288
- }
289
- }
290
- end
291
- end
292
- }
293
-
294
- unless rfml_test.browsers.empty?
295
- test_obj[:browsers] = rfml_test.browsers.map {|b|
296
- {'state' => 'enabled', 'name' => b}
297
- }
298
- end
299
-
300
- test_obj
301
- end
302
182
  end