rspeed 0.4.0 → 0.7.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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +54 -0
  3. data/README.md +10 -7
  4. data/lib/generators/rspeed/install_generator.rb +1 -1
  5. data/lib/rspeed.rb +10 -7
  6. data/lib/rspeed/database.rb +19 -0
  7. data/lib/rspeed/differ.rb +71 -0
  8. data/lib/rspeed/env.rb +39 -0
  9. data/lib/rspeed/extension.rb +3 -2
  10. data/lib/rspeed/logger.rb +13 -0
  11. data/lib/rspeed/observer.rb +19 -7
  12. data/lib/rspeed/redis.rb +78 -0
  13. data/lib/rspeed/reporter.rb +38 -0
  14. data/lib/rspeed/runner.rb +4 -15
  15. data/lib/rspeed/splitter.rb +25 -115
  16. data/lib/rspeed/variable.rb +34 -0
  17. data/lib/rspeed/version.rb +1 -1
  18. data/spec/common_helper.rb +10 -0
  19. data/spec/fixtures/empty.rb +4 -0
  20. data/spec/models/rspeed/database/list_spec.rb +15 -0
  21. data/spec/models/rspeed/database/previous_result_spec.rb +17 -0
  22. data/spec/models/rspeed/database/result_spec.rb +17 -0
  23. data/spec/models/rspeed/differ/actual_data_spec.rb +20 -0
  24. data/spec/models/rspeed/differ/actual_files_spec.rb +20 -0
  25. data/spec/models/rspeed/differ/added_data_spec.rb +19 -0
  26. data/spec/models/rspeed/differ/diff_spec.rb +77 -0
  27. data/spec/models/rspeed/differ/final_diff_spec.rb +46 -0
  28. data/spec/models/rspeed/differ/removed_data_spec.rb +20 -0
  29. data/spec/models/rspeed/differ/sum_time_spec.rb +19 -0
  30. data/spec/models/rspeed/env/app_spec.rb +17 -0
  31. data/spec/models/rspeed/env/db_spec.rb +17 -0
  32. data/spec/models/rspeed/env/host_spec.rb +17 -0
  33. data/spec/models/rspeed/env/pipe_spec.rb +19 -0
  34. data/spec/models/rspeed/env/pipes_spec.rb +19 -0
  35. data/spec/models/rspeed/env/port_spec.rb +17 -0
  36. data/spec/models/rspeed/env/rspeed_spec.rb +43 -0
  37. data/spec/models/rspeed/env/spec_path_spec.rb +19 -0
  38. data/spec/models/rspeed/observer/after_spec.rb +5 -5
  39. data/spec/models/rspeed/observer/after_suite_spec.rb +43 -0
  40. data/spec/models/rspeed/observer/before_spec.rb +0 -2
  41. data/spec/models/rspeed/observer/before_suite_spec.rb +18 -6
  42. data/spec/models/rspeed/redis/clean_spec.rb +20 -0
  43. data/spec/models/rspeed/redis/client_spec.rb +7 -0
  44. data/spec/models/rspeed/redis/destroy_spec.rb +21 -0
  45. data/spec/models/rspeed/redis/get_spec.rb +11 -0
  46. data/spec/models/rspeed/redis/keys_spec.rb +13 -0
  47. data/spec/models/rspeed/redis/list_spec.rb +19 -0
  48. data/spec/models/rspeed/redis/profiles_content_spec.rb +19 -0
  49. data/spec/models/rspeed/redis/result_spec.rb +13 -0
  50. data/spec/models/rspeed/redis/set_spec.rb +11 -0
  51. data/spec/models/rspeed/redis/specs_finished_spec.rb +19 -0
  52. data/spec/models/rspeed/redis/specs_initiated_spec.rb +13 -0
  53. data/spec/models/rspeed/redis/version_the_result_spec.rb +21 -0
  54. data/spec/models/rspeed/reporter/call_spec.rb +42 -0
  55. data/spec/models/rspeed/reporter/print_files_spec.rb +27 -0
  56. data/spec/models/rspeed/reporter/print_table_spec.rb +29 -0
  57. data/spec/models/rspeed/runner/run_spec.rb +73 -0
  58. data/spec/models/rspeed/splitter/append_spec.rb +8 -28
  59. data/spec/models/rspeed/splitter/consolidate_spec.rb +23 -0
  60. data/spec/models/rspeed/splitter/first_pipe_spec.rb +4 -8
  61. data/spec/models/rspeed/splitter/need_warm_question_spec.rb +35 -0
  62. data/spec/models/rspeed/splitter/pipe_files_spec.rb +30 -0
  63. data/spec/models/rspeed/splitter/split_spec.rb +85 -45
  64. data/spec/models/rspeed/variable/append_app_name_spec.rb +33 -0
  65. data/spec/models/rspeed/variable/key_spec.rb +15 -0
  66. data/spec/models/rspeed/variable/pipe_spec.rb +23 -0
  67. data/spec/models/rspeed/variable/pipes_pattern_spec.rb +5 -0
  68. data/spec/models/rspeed/variable/previous_result_spec.rb +19 -0
  69. data/spec/models/rspeed/variable/profile_pattern_spec.rb +5 -0
  70. data/spec/models/rspeed/variable/profile_spec.rb +23 -0
  71. data/spec/models/rspeed/variable/result_spec.rb +19 -0
  72. data/spec/spec_helper.rb +27 -0
  73. data/spec/support/common.rb +13 -0
  74. data/spec/support/coverage.rb +14 -0
  75. data/spec/support/env_mock.rb +3 -0
  76. data/spec/support/fakeredis.rb +3 -0
  77. metadata +187 -30
  78. data/spec/fixtures/new_spec.rb.csv +0 -1
  79. data/spec/models/rspeed/splitter/actual_examples_spec.rb +0 -22
  80. data/spec/models/rspeed/splitter/destroy_spec.rb +0 -33
  81. data/spec/models/rspeed/splitter/diff_spec.rb +0 -32
  82. data/spec/models/rspeed/splitter/get_spec.rb +0 -76
  83. data/spec/models/rspeed/splitter/keys_spec.rb +0 -33
  84. data/spec/models/rspeed/splitter/last_pipe_spec.rb +0 -21
  85. data/spec/models/rspeed/splitter/pipe_spec.rb +0 -21
  86. data/spec/models/rspeed/splitter/pipes_spec.rb +0 -27
  87. data/spec/models/rspeed/splitter/rename_spec.rb +0 -18
  88. data/spec/models/rspeed/splitter/result_spec.rb +0 -19
  89. data/spec/models/rspeed/splitter/save_spec.rb +0 -57
  90. data/spec/rails_helper.rb +0 -47
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f8fbbef694a5ee56e51580e9969e9e8372d4d93d0e5562f4efbad08fb8df854
4
- data.tar.gz: b9cefcd95a86536bcd139ef8df7f47674ad0f4205ecfb0c7bc7dd2b5c33b4efd
3
+ metadata.gz: 121d5558331de345c2147e1b1ee8b6679c09ac8cb83b33af4ad5673d3e534806
4
+ data.tar.gz: b739938586f9ce82c94c9fe74f79583153ad268e5443c54602f32650482595dd
5
5
  SHA512:
6
- metadata.gz: 66a0fba65b3130c9454abad42a8a0e4554fd49711194a74cf7d5d77d37274469500e29f0d6210c53d92f33aa529799d416281c5556f193b9a9c0a96d3b7babbd
7
- data.tar.gz: 59399ef9100cc2353a7dc93b53a152289b79aaac9a5c2ace44925a50c517a94196193ebe25ae5a4148861ace0317dfdd2def59b332ab9c717b6d073dad5df0c0
6
+ metadata.gz: 4109a7ca7ccc74e43c09eafd0aabf5fba559f9dbc5350a8439cbfc47cd400c03de3d862405059e60adb6ee11b83592985a89b877982c3094bcecbf3f69c363df
7
+ data.tar.gz: 6f706fe116da661fe858fc85979e5ee718ec4fe4da19cb13cd06ee6bbabad4688e6182f8504db93215a549fa9efe18991314faf97d8ca8e0f55125771b2de772
data/CHANGELOG.md CHANGED
@@ -1,3 +1,57 @@
1
+ ## master
2
+
3
+ - None;
4
+
5
+ ## v0.7.0
6
+
7
+ #### News
8
+
9
+ - Prints pipe info into a table on the console;
10
+ - The previous result is keeped alive for further checks;
11
+ - Adds final report show actual, added and removed;
12
+
13
+ ## v0.6.0
14
+
15
+ #### Break Change
16
+
17
+ - Drops `RSPEED_RESULT_KEY` key in favor of `RSPEED_NAME`;
18
+ - Renames env `RSPEED_NAME` to `RSPEED_APP`;
19
+
20
+ #### News
21
+
22
+ - Adds env `RSPEED_SPEC_PATH` to indicate the spec folders path;
23
+ - Saves pipes and profiles with a zero at the beginning;
24
+ - Makes sure examples is unique;
25
+
26
+ ## v0.5.2
27
+
28
+ #### Fix
29
+
30
+ - Avoid duplicate entries in the consolidated result;
31
+
32
+ #### Update
33
+
34
+ - CSV dependency dropped;
35
+ - Drops tmp key in favor of profile keys fetch;
36
+
37
+ ## v0.5.1
38
+
39
+ #### Fix
40
+
41
+ - Only pipe number 1 will warm up avoiding duplicated spec entries;
42
+
43
+ ## v0.5.0
44
+
45
+ #### Fix
46
+
47
+ - Add env `RSPEED_NAME` to specify the application name and avoid result conflicts between multiple runs;
48
+ - No more depends on pipe sequence to generate ou aggregate the resul;
49
+ - rake `rspeed:install`;
50
+
51
+ #### Update
52
+
53
+ - The result of the pipes are no more saved on Redis. It's now calculated based on result key `rspeed`;
54
+
1
55
  ## v0.4.0
2
56
 
3
57
  - Now we make diff to discover removed and added examples;
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # RSpeed
2
2
 
3
- [![Build Status](https://github.com/wbotelhos/rspeed/workflows/CI/badge.svg)](https://github.com/wbotelhos/rspeed/actions)
3
+ [![CI](https://github.com/wbotelhos/rspeed/workflows/CI/badge.svg)](https://github.com/wbotelhos/rspeed/actions)
4
4
  [![Gem Version](https://badge.fury.io/rb/rspeed.svg)](https://badge.fury.io/rb/rspeed)
5
- [![Maintainability](https://api.codeclimate.com/v1/badges/cc5efe8b06bc1d5e9e8a/maintainability)](https://codeclimate.com/github/wbotelhos/rspeed/maintainability)
6
- [![Patreon](https://img.shields.io/badge/donate-%3C3-brightgreen.svg)](https://www.patreon.com/wbotelhos)
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/f312587b4f126bb13e85/maintainability)](https://codeclimate.com/github/wbotelhos/rspeed/maintainability)
6
+ [![Coverage](https://codecov.io/gh/wbotelhos/rspeed/branch/main/graph/badge.svg)](https://codecov.io/gh/wbotelhos/rspeed)
7
+ [![Sponsor](https://img.shields.io/badge/sponsor-%3C3-green)](https://www.patreon.com/wbotelhos)
7
8
 
8
9
  RSpeed splits your specs to you run parallels tests.
9
10
 
@@ -25,19 +26,21 @@ rake rspeed:install
25
26
 
26
27
  ## Usage
27
28
 
28
- - `RSPEED`: Enables RSpeed
29
- - `RSPEED_PIPES`: Quantity of pipes
29
+ - `RSPEED_APP`: You app name to avoid data override
30
30
  - `RSPEED_PIPE`: Current pipe
31
+ - `RSPEED_PIPES`: Quantity of pipes
32
+ - `RSPEED_SPEC_PATH`: The specs folders path
33
+ - `RSPEED`: Enables RSpeed
31
34
 
32
35
  ```sh
33
- RSPEED=true RSPEED_PIPES=3 RSPEED_PIPE=1 bundle exec rake rspeed:run
36
+ RSPEED=true RSPEED_APP=blog RSPEED_PIPE=1 RSPEED_PIPES=3 bundle exec rake rspeed:run
34
37
  ```
35
38
 
36
39
  ## How it Works
37
40
 
38
41
  ### First run
39
42
 
40
- 1. Since we has no statistics on the first time, we run all specs and collect it;
43
+ 1. Since we have no statistics on the first time, we run all specs and collect it;
41
44
 
42
45
  ```json
43
46
  { "file": "./spec/models/1_spec.rb", "time": 0.01 }
@@ -6,7 +6,7 @@ module RSpeed
6
6
 
7
7
  desc 'Creates RSpeed task'
8
8
 
9
- def copy_initializer
9
+ def create_initializer
10
10
  copy_file 'lib/tasks/rspeed.rake', 'lib/tasks/rspeed.rake'
11
11
  end
12
12
  end
data/lib/rspeed.rb CHANGED
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RSpeed
4
- require 'csv'
5
- require 'redis'
4
+ require 'rspeed/database'
5
+ require 'rspeed/env'
6
+ require 'rspeed/extension'
7
+ require 'rspeed/logger'
8
+ require 'rspeed/observer'
9
+ require 'rspeed/redis'
10
+ require 'rspeed/reporter'
11
+ require 'rspeed/runner'
12
+ require 'rspeed/splitter'
13
+ require 'rspeed/variable'
6
14
  end
7
-
8
- require 'rspeed/extension'
9
- require 'rspeed/observer'
10
- require 'rspeed/runner'
11
- require 'rspeed/splitter'
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpeed
4
+ module Database
5
+ module_function
6
+
7
+ def previous_result
8
+ list(RSpeed::Variable.previous_result)
9
+ end
10
+
11
+ def result
12
+ list(RSpeed::Variable.result)
13
+ end
14
+
15
+ def list(key)
16
+ RSpeed::Redis.list(key).map { |item| JSON.parse(item, symbolize_names: true) }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpeed
4
+ module Differ
5
+ module_function
6
+
7
+ def actual_files(spec_path: RSpeed::Env.spec_path)
8
+ [].tap do |data|
9
+ Dir[spec_path].sort.each do |file|
10
+ lines = File.open(file).read.split("\n")
11
+
12
+ lines&.each&.with_index do |item, index|
13
+ data << "#{file}:#{index + 1}" if /^it/.match?(item.gsub(/\s+/, ''))
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ def actual_data(files:, result:)
20
+ result.select { |item| files.include?(item[:file]) }
21
+ end
22
+
23
+ def added_data(files:, result:)
24
+ (files - result.map { |item| item[:file] }).map { |file| { file: file, time: nil } }
25
+ end
26
+
27
+ def diff(from: actual_files, to: RSpeed::Database.result)
28
+ to = to.uniq { |item| item[:file] }
29
+ added = added_data(files: from, result: to)
30
+ actual = actual_data(files: from, result: to) + added_data(files: from, result: to)
31
+ removed = removed_data(files: from, result: to)
32
+
33
+ {
34
+ actual_files: actual,
35
+ actual_time: sum_time(data: actual),
36
+ added_files: added,
37
+ added_time: sum_time(data: added),
38
+ removed_files: removed,
39
+ removed_time: sum_time(data: removed),
40
+ }
41
+ end
42
+
43
+ def final_diff(from: RSpeed::Database.previous_result, to: RSpeed::Database.result)
44
+ from = from.uniq { |item| item[:file] }
45
+ to = to.uniq { |item| item[:file] }
46
+ previous_files = from.map { |item| item[:file] }
47
+ actual_files = to.map { |item| item[:file] }
48
+ added = to.reject { |item| previous_files.include?(item[:file]) }
49
+ removed = from.reject { |item| actual_files.include?(item[:file]) }
50
+
51
+ {
52
+ actual_files: to,
53
+ actual_time: sum_time(data: to),
54
+ added_files: added,
55
+ added_time: sum_time(data: added),
56
+ removed_files: removed,
57
+ removed_time: sum_time(data: removed),
58
+ }
59
+ end
60
+
61
+ def removed_data(files:, result:)
62
+ result.reject { |item| files.include?(item[:file]) }
63
+ end
64
+
65
+ def sum_time(data:)
66
+ return '?' if data.all? { |item| item[:time].nil? }
67
+
68
+ data.sum { |item| item[:time].to_f }
69
+ end
70
+ end
71
+ end
data/lib/rspeed/env.rb ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpeed
4
+ module Env
5
+ module_function
6
+
7
+ def app
8
+ ENV['RSPEED_APP']
9
+ end
10
+
11
+ def db
12
+ ENV['RSPEED_DB']&.to_i
13
+ end
14
+
15
+ def host
16
+ ENV['RSPEED_HOST']
17
+ end
18
+
19
+ def pipe
20
+ ENV.fetch('RSPEED_PIPE', 1).to_i
21
+ end
22
+
23
+ def pipes
24
+ ENV.fetch('RSPEED_PIPES', 1).to_i
25
+ end
26
+
27
+ def port
28
+ ENV['RSPEED_PORT']&.to_i
29
+ end
30
+
31
+ def rspeed
32
+ ENV['RSPEED'] == 'true'
33
+ end
34
+
35
+ def spec_path
36
+ ENV.fetch('RSPEED_SPEC_PATH', './spec/**/*spec.rb')
37
+ end
38
+ end
39
+ end
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- if ENV['RSPEED'] == 'true'
4
- require 'rspec/rails'
3
+ if RSpeed::Env.rspeed
4
+ require 'rspec'
5
5
 
6
6
  RSpec.configure do |config|
7
7
  config.before(:suite) { RSpeed::Observer.before_suite }
8
8
  config.before { |example| RSpeed::Observer.before(example) }
9
9
  config.after { |example| RSpeed::Observer.after(example) }
10
+ config.after(:suite) { RSpeed::Observer.after_suite }
10
11
  end
11
12
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpeed
4
+ module Logger
5
+ module_function
6
+
7
+ def log(context, method, message)
8
+ clazz = context.ancestors.join('::')
9
+
10
+ puts("[#{clazz}##{method}.#{RSpeed::Env.pipe}] #{message}")
11
+ end
12
+ end
13
+ end
@@ -9,9 +9,22 @@ module RSpeed
9
9
  line_number = example.metadata[:line_number]
10
10
  spent_time = example.clock.now - example.metadata[:start_at]
11
11
 
12
- File.open('rspeed.csv', 'a') do |file|
13
- file.write "#{spent_time},#{file_path}:#{line_number}\n"
14
- end
12
+ json = { file: "#{file_path}:#{line_number}", time: spent_time }.to_json
13
+
14
+ RSpeed::Redis.client.rpush(RSpeed::Variable.profile, json)
15
+ end
16
+
17
+ def after_suite
18
+ RSpeed::Redis.set(RSpeed::Variable.pipe, true)
19
+
20
+ return unless RSpeed::Redis.specs_finished?
21
+
22
+ RSpeed::Redis.version_the_result if RSpeed::Redis.result?
23
+ RSpeed::Splitter.consolidate
24
+ RSpeed::Reporter.call
25
+ RSpeed::Redis.clean
26
+
27
+ RSpeed::Logger.log(self, __method__, 'RSpeed finished.')
15
28
  end
16
29
 
17
30
  def before(example)
@@ -19,11 +32,10 @@ module RSpeed
19
32
  end
20
33
 
21
34
  def before_suite
22
- truncate_csv_file
23
- end
35
+ RSpeed::Logger.log(self, __method__, 'Cleanning current flag and profile.')
24
36
 
25
- def truncate_csv_file
26
- File.open('rspeed.csv', 'w') { |file| file.truncate(0) }
37
+ RSpeed::Redis.destroy(pattern: RSpeed::Variable.pipe)
38
+ RSpeed::Redis.destroy(pattern: RSpeed::Variable.profile)
27
39
  end
28
40
  end
29
41
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpeed
4
+ module Redis
5
+ require 'redis'
6
+
7
+ module_function
8
+
9
+ def clean
10
+ RSpeed::Logger.log(self, __method__, 'Cleaning pipes and profiles.')
11
+
12
+ destroy(pattern: RSpeed::Variable::PIPES_PATTERN)
13
+ destroy(pattern: RSpeed::Variable::PROFILE_PATTERN)
14
+ end
15
+
16
+ def client
17
+ @client ||= ::Redis.new(db: RSpeed::Env.db, host: RSpeed::Env.host, port: RSpeed::Env.port)
18
+ end
19
+
20
+ def destroy(pattern:)
21
+ RSpeed::Logger.log(self, __method__, %(Destroying pattern "#{pattern}".))
22
+
23
+ keys(pattern: pattern).each { |key| client.del(key) }
24
+ end
25
+
26
+ def get(key)
27
+ client.get(key)
28
+ end
29
+
30
+ def keys(pattern:)
31
+ cursor = 0
32
+ result = []
33
+
34
+ loop do
35
+ cursor, results = client.scan(cursor, match: pattern)
36
+ result += results
37
+
38
+ break if cursor.to_i.zero?
39
+ end
40
+
41
+ result
42
+ end
43
+
44
+ def list(key)
45
+ client.lrange(key, 0, -1)
46
+ end
47
+
48
+ def profiles_content(pattern: 'rspeed:profile_*')
49
+ client.keys(pattern).map { |key| list(key) }.flatten
50
+ end
51
+
52
+ def result?
53
+ keys(pattern: RSpeed::Variable.result).any?
54
+ end
55
+
56
+ def set(key, value)
57
+ client.set(key, value)
58
+ end
59
+
60
+ def specs_finished?
61
+ (RSpeed::Redis.keys(pattern: RSpeed::Variable::PIPES_PATTERN).size == RSpeed::Env.pipes).tap do |boo|
62
+ RSpeed::Logger.log(self, __method__, "Specs #{boo ? 'finished.' : 'not fineshed yet.'}")
63
+ end
64
+ end
65
+
66
+ def specs_initiated?
67
+ RSpeed::Redis.keys(pattern: RSpeed::Variable::PIPES_PATTERN).any?.tap do |boo|
68
+ RSpeed::Logger.log(self, __method__, "Specs #{boo ? 'initialized.' : 'not initialized yet.'}")
69
+ end
70
+ end
71
+
72
+ def version_the_result
73
+ RSpeed::Logger.log(self, __method__, 'Versioning the result.')
74
+
75
+ client.rename(RSpeed::Variable.result, RSpeed::Variable.previous_result)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpeed
4
+ module Reporter
5
+ module_function
6
+
7
+ require 'terminal-table'
8
+
9
+ def call
10
+ diff = RSpeed::Differ.final_diff
11
+
12
+ print_table(
13
+ headings: %w[Global Value],
14
+
15
+ rows: [
16
+ ['Actual Time', diff[:actual_time]],
17
+ ['Removed Time', diff[:removed_time]],
18
+ ['Added Time', diff[:added_time]],
19
+ ]
20
+ )
21
+ end
22
+
23
+ def print_files(items)
24
+ total_specs = items.size
25
+ headings = ["#{total_specs} specs", "Pipe #{RSpeed::Env.pipe}/#{RSpeed::Env.pipes}", 'Last Time']
26
+
27
+ rows = items.map.with_index do |item, index|
28
+ [format('%02d', index + 1), item[:file], item[:time].to_s]
29
+ end
30
+
31
+ print_table(headings: headings, rows: rows)
32
+ end
33
+
34
+ def print_table(headings:, rows:)
35
+ puts(Terminal::Table.new(headings: headings, rows: rows, title: 'RSpeed'))
36
+ end
37
+ end
38
+ end