rainforest-cli 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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