rspeed 0.4.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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
data/lib/rspeed/runner.rb CHANGED
@@ -5,24 +5,13 @@ module RSpeed
5
5
  module_function
6
6
 
7
7
  def run(shell)
8
- splitter = ::RSpeed::Splitter.new
9
-
10
- if splitter.first_pipe?
11
- # splitter.destroy "rspeed_*"
12
- splitter.destroy 'rspeed_tmp'
8
+ if RSpeed::Redis.result? || RSpeed::Splitter.first_pipe?
9
+ return shell.call(['bundle exec rspec', RSpeed::Splitter.pipe_files].compact.join(' '))
13
10
  end
14
11
 
15
- if splitter.result?
16
- splitter.save if splitter.first_pipe?
17
-
18
- files = splitter.get("rspeed_#{splitter.pipe}")[0]['files'].map { |item| item['file'] }.join(' ')
19
- end
20
-
21
- shell.call ['bundle exec rspec', files].compact.join(' ')
22
-
23
- splitter.append
12
+ RSpeed::Logger.log(self, __method__, 'Skipped! Only Pipe 1 can warm.')
24
13
 
25
- splitter.rename if splitter.last_pipe?
14
+ RSpeed::Observer.after_suite
26
15
  end
27
16
  end
28
17
  end
@@ -1,112 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RSpeed
4
- class Splitter
5
- DEFAULT_PATTERN = 'rspeed_*'
4
+ module Splitter
5
+ module_function
6
6
 
7
- def initialize(specs_path: './spec/**/*_spec.rb')
8
- @specs_path = specs_path
9
- end
10
-
11
- def actual_examples
12
- @actual_examples ||= begin
13
- [].tap do |examples|
14
- Dir[@specs_path].each do |file|
15
- data = File.open(file).read
16
- lines = data.split("\n")
7
+ require 'json'
17
8
 
18
- lines&.each.with_index do |item, index|
19
- examples << "#{file}:#{index + 1}" if item.gsub(/\s+/, '') =~ /^it/
20
- end
21
- end
9
+ require 'rspeed/differ'
22
10
 
23
- stream(:actual_examples, examples)
24
- end
25
- end
11
+ def append(items:, key:)
12
+ items.each { |item| RSpeed::Redis.client.rpush(key, item) }
26
13
  end
27
14
 
28
- def append(files = CSV.read('rspeed.csv'))
29
- files.each do |time, file|
30
- redis.lpush('rspeed_tmp', { file: file, time: time.to_f }.to_json)
31
- end
32
- end
15
+ def consolidate
16
+ RSpeed::Logger.log(self, __method__, 'Consolidating profiles.')
33
17
 
34
- def destroy(pattern = DEFAULT_PATTERN)
35
- keys(pattern).each { |key| redis.del key }
36
- end
18
+ RSpeed::Redis.destroy(pattern: RSpeed::Variable.result)
37
19
 
38
- def diff
39
- actual_data = rspeed_data.select { |item| actual_examples.include?(item[:file]) }
40
- added_data = added_examples.map { |item| { file: item, time: 0 } }
41
-
42
- removed_examples # called just for stream for now
43
-
44
- actual_data + added_data
20
+ append(items: RSpeed::Redis.profiles_content, key: RSpeed::Variable.result)
45
21
  end
46
22
 
47
23
  def first_pipe?
48
- pipe == 1
24
+ RSpeed::Env.pipe == 1
49
25
  end
50
26
 
51
- def get(pattern)
52
- @get ||= begin
53
- return redis.lrange(pattern, 0, -1) if %w[rspeed rspeed_tmp].include?(pattern)
54
-
55
- keys(pattern).map { |key| JSON.parse(redis.get(key)) }
56
- end
27
+ def need_warm?
28
+ first_pipe? && !RSpeed::Redis.result?
57
29
  end
58
30
 
59
- def keys(pattern = DEFAULT_PATTERN)
60
- cursor = 0
61
- result = []
31
+ def pipe_files
32
+ return unless RSpeed::Redis.result?
62
33
 
63
- loop do
64
- cursor, results = redis.scan(cursor, match: pattern)
65
- result += results
34
+ splitted = split(data: RSpeed::Differ.diff[:actual_files])
66
35
 
67
- break if cursor.to_i.zero?
68
- end
69
-
70
- result
71
- end
72
-
73
- def last_pipe?
74
- pipe == pipes
75
- end
76
-
77
- def pipe
78
- ENV.fetch('RSPEED_PIPE') { 1 }.to_i
36
+ splitted[RSpeed::Variable.key(RSpeed::Env.pipe)][:files].tap do |items|
37
+ RSpeed::Reporter.print_files(items)
38
+ end.map { |item| item[:file] }.join(' ')
79
39
  end
80
40
 
81
- def pipes
82
- result? ? ENV.fetch('RSPEED_PIPES') { 1 }.to_i : 1
83
- end
84
-
85
- def rename
86
- redis.rename('rspeed_tmp', 'rspeed')
87
- end
88
-
89
- def result?
90
- !keys('rspeed').empty?
91
- end
92
-
93
- def save(data = diff)
94
- split(data).each { |key, value| redis.set(key, value.to_json) }
95
- end
96
-
97
- def split(data)
41
+ def split(data:)
98
42
  json = {}
99
43
 
100
- pipes.times do |index|
101
- json["rspeed_#{index + 1}".to_sym] ||= []
102
- json["rspeed_#{index + 1}".to_sym] = { total: 0, files: [], number: index + 1 }
44
+ RSpeed::Env.pipes.times do |index|
45
+ json[RSpeed::Variable.key(index + 1)] ||= []
46
+ json[RSpeed::Variable.key(index + 1)] = { total: 0, files: [], number: index + 1 }
103
47
  end
104
48
 
105
- sorted_data = data.sort_by { |item| item[:time] }.reverse
49
+ sorted_data = data.sort_by { |item| item[:time] || 0.0 }.reverse
106
50
 
107
51
  sorted_data.each do |record|
108
52
  selected_pipe_data = json.min_by { |pipe| pipe[1][:total] }
109
- selected_pipe = json["rspeed_#{selected_pipe_data[1][:number]}".to_sym]
53
+ selected_pipe = json[RSpeed::Variable.key(selected_pipe_data[1][:number])]
110
54
  time = record[:time].to_f
111
55
 
112
56
  selected_pipe[:total] += time
@@ -115,39 +59,5 @@ module RSpeed
115
59
 
116
60
  json
117
61
  end
118
-
119
- private
120
-
121
- def added_examples
122
- @added_examples ||= begin
123
- (actual_examples - rspeed_examples).tap { |examples| stream(:added_examples, examples) }
124
- end
125
- end
126
-
127
- def redis
128
- @redis ||= ::Redis.new(db: ENV['RSPEED_DB'], host: ENV['RSPEED_HOST'], port: ENV.fetch('RSPEED_PORT') { 6379 })
129
- end
130
-
131
- def removed_examples
132
- @removed_examples ||= begin
133
- (rspeed_examples - actual_examples).tap { |examples| stream(:removed_examples, examples) }
134
- end
135
- end
136
-
137
- def removed_time
138
- removed_examples.map { |item| item[0].to_f }.sum
139
- end
140
-
141
- def rspeed_data
142
- @rspeed_data ||= get('rspeed').map { |item| JSON.parse(item, symbolize_names: true) }
143
- end
144
-
145
- def rspeed_examples
146
- rspeed_data.map { |item| item[:file] }
147
- end
148
-
149
- def stream(type, data)
150
- puts "PIPE: #{pipe} with #{type}: #{data}"
151
- end
152
62
  end
153
63
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpeed
4
+ module Variable
5
+ module_function
6
+
7
+ PIPES_PATTERN = 'rspeed:pipe_*'
8
+ PROFILE_PATTERN = 'rspeed:profile_*'
9
+
10
+ def append_app_name(value, plus: nil)
11
+ [value, RSpeed::Env.app, plus].compact.join('_')
12
+ end
13
+
14
+ def key(number)
15
+ append_app_name('rspeed', plus: number).to_sym
16
+ end
17
+
18
+ def result
19
+ append_app_name('rspeed')
20
+ end
21
+
22
+ def pipe
23
+ append_app_name('rspeed:pipe', plus: format('%02d', RSpeed::Env.pipe))
24
+ end
25
+
26
+ def previous_result
27
+ append_app_name('rspeed', plus: 'previous')
28
+ end
29
+
30
+ def profile
31
+ append_app_name('rspeed:profile', plus: format('%02d', RSpeed::Env.pipe))
32
+ end
33
+ end
34
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RSpeed
4
- VERSION = '0.4.0'
4
+ VERSION = '0.7.0'
5
5
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV['RAILS_ENV'] ||= 'test'
4
+
5
+ require 'support/coverage'
6
+
7
+ require 'pry-byebug'
8
+ require 'rspeed'
9
+ require 'support/common'
10
+ require 'support/fakeredis'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'empty' do
4
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe RSpeed::Database, '#list' do
4
+ before do
5
+ redis_object.rpush(RSpeed::Variable.result, { file: '1_spec.rb', time: 1.0 }.to_json)
6
+ redis_object.rpush(RSpeed::Variable.result, { file: '2_spec.rb', time: 2.0 }.to_json)
7
+ end
8
+
9
+ it 'list and parse the given key' do
10
+ expect(described_class.list(RSpeed::Variable.result)).to eq [
11
+ { file: '1_spec.rb', time: 1.0 },
12
+ { file: '2_spec.rb', time: 2.0 },
13
+ ]
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe RSpeed::Database, '#previous_result' do
4
+ let!(:redis) { redis_object }
5
+
6
+ before do
7
+ redis.rpush(RSpeed::Variable.previous_result, { file: '1_spec.rb', time: 1.0 }.to_json)
8
+ redis.rpush(RSpeed::Variable.previous_result, { file: '2_spec.rb', time: 2.0 }.to_json)
9
+ end
10
+
11
+ it 'returns the previous results data as json' do
12
+ expect(described_class.previous_result).to eq [
13
+ { file: '1_spec.rb', time: 1.0 },
14
+ { file: '2_spec.rb', time: 2.0 },
15
+ ]
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe RSpeed::Database, '#result' do
4
+ let!(:redis) { redis_object }
5
+
6
+ before do
7
+ redis.rpush('rspeed', { file: '1_spec.rb', time: 1.0 }.to_json)
8
+ redis.rpush('rspeed', { file: '2_spec.rb', time: 2.0 }.to_json)
9
+ end
10
+
11
+ it 'returns the results data as json' do
12
+ expect(described_class.result).to eq [
13
+ { file: '1_spec.rb', time: 1.0 },
14
+ { file: '2_spec.rb', time: 2.0 },
15
+ ]
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe RSpeed::Differ, '#actual_data' do
4
+ let!(:files) { ['./spec/fixtures/1_spec.rb:4', './spec/fixtures/1_spec.rb:6', './spec/fixtures/new_spec.rb:1'] }
5
+
6
+ let!(:result) do
7
+ [
8
+ { file: './spec/fixtures/1_spec.rb:4', time: 1.4 },
9
+ { file: './spec/fixtures/1_spec.rb:6', time: 1.6 },
10
+ { file: './spec/fixtures/2_spec.rb:4', time: 2.4 },
11
+ ]
12
+ end
13
+
14
+ it 'returns only consolidated data still existent as spec' do
15
+ expect(described_class.actual_data(files: files, result: result)).to eq [
16
+ { file: './spec/fixtures/1_spec.rb:4', time: 1.4 },
17
+ { file: './spec/fixtures/1_spec.rb:6', time: 1.6 },
18
+ ]
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe RSpeed::Differ, '#actual_files' do
4
+ it 'returns all examples' do
5
+ expect(described_class.actual_files(spec_path: './spec/fixtures/**/*_spec.rb')).to eq [
6
+ './spec/fixtures/1_spec.rb:4',
7
+ './spec/fixtures/1_spec.rb:6',
8
+ './spec/fixtures/1_spec.rb:8',
9
+ './spec/fixtures/2_spec.rb:4',
10
+ ]
11
+ end
12
+
13
+ it 'does not raise when no file match' do
14
+ expect(described_class.actual_files(spec_path: './spec/fixtures/**/*_missing.rb')).to eq []
15
+ end
16
+
17
+ it 'does not raise when file is empty' do
18
+ expect(described_class.actual_files(spec_path: './spec/fixtures/**/empty.rb')).to eq []
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe RSpeed::Differ, '#added_data' do
4
+ let!(:files) { ['./spec/fixtures/1_spec.rb:4', './spec/fixtures/1_spec.rb:6', './spec/fixtures/new_spec.rb:4'] }
5
+
6
+ let!(:result) do
7
+ [
8
+ { file: './spec/fixtures/1_spec.rb:4', time: 1.4 },
9
+ { file: './spec/fixtures/1_spec.rb:6', time: 1.6 },
10
+ { file: './spec/fixtures/2_spec.rb:4', time: 2.4 },
11
+ ]
12
+ end
13
+
14
+ it 'returns the added data files' do
15
+ expect(described_class.added_data(files: files, result: result)).to eq [
16
+ { file: './spec/fixtures/new_spec.rb:4', time: nil },
17
+ ]
18
+ end
19
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'support/env_mock'
4
+
5
+ RSpec.describe RSpeed::Differ, '#diff' do
6
+ let!(:redis) { redis_object }
7
+
8
+ before do
9
+ redis.rpush('rspeed', { file: './spec/fixtures/1_spec.rb:4', time: 1.4 }.to_json)
10
+ redis.rpush('rspeed', { file: './spec/fixtures/1_spec.rb:6', time: 1.6 }.to_json)
11
+ redis.rpush('rspeed', { file: './spec/fixtures/1_spec.rb:8', time: 1.8 }.to_json)
12
+ redis.rpush('rspeed', { file: './spec/fixtures/2_spec.rb:4', time: 2.4 }.to_json)
13
+ redis.rpush('rspeed', { file: './spec/fixtures/x_spec.rb:1', time: 3 }.to_json)
14
+ redis.rpush('rspeed', { file: './spec/fixtures/2_spec.rb:666', time: 6 }.to_json)
15
+
16
+ File.open('spec/fixtures/new_spec.rb', 'a') { |file| file.write('it') }
17
+ end
18
+
19
+ after { delete_file('spec/fixtures/new_spec.rb') }
20
+
21
+ it 'removes removed specs and adds new spec and keeps keeped specs based on rspeed key values' do
22
+ EnvMock.mock(rspeed_spec_path: './spec/fixtures/*_spec.rb') do
23
+ expect(described_class.diff).to eq(
24
+ actual_files: [
25
+ { file: './spec/fixtures/1_spec.rb:4', time: 1.4 },
26
+ { file: './spec/fixtures/1_spec.rb:6', time: 1.6 },
27
+ { file: './spec/fixtures/1_spec.rb:8', time: 1.8 },
28
+ { file: './spec/fixtures/2_spec.rb:4', time: 2.4 },
29
+ { file: './spec/fixtures/new_spec.rb:1', time: nil },
30
+ ],
31
+
32
+ actual_time: 7.2,
33
+ added_files: [{ file: './spec/fixtures/new_spec.rb:1', time: nil }],
34
+ added_time: '?',
35
+
36
+ removed_files: [
37
+ { file: './spec/fixtures/x_spec.rb:1', time: 3 },
38
+ { file: './spec/fixtures/2_spec.rb:666', time: 6 },
39
+ ],
40
+
41
+ removed_time: 9.0
42
+ )
43
+ end
44
+ end
45
+
46
+ context 'when has duplicated example' do
47
+ before do
48
+ redis.rpush('rspeed', { file: './spec/fixtures/1_spec.rb:4', time: 1.4 }.to_json)
49
+ redis.rpush('rspeed', { file: './spec/fixtures/1_spec.rb:4', time: 1.4 }.to_json)
50
+ end
51
+
52
+ it 'is removed' do
53
+ EnvMock.mock(rspeed_spec_path: './spec/fixtures/*_spec.rb') do
54
+ expect(described_class.diff).to eq(
55
+ actual_files: [
56
+ { file: './spec/fixtures/1_spec.rb:4', time: 1.4 },
57
+ { file: './spec/fixtures/1_spec.rb:6', time: 1.6 },
58
+ { file: './spec/fixtures/1_spec.rb:8', time: 1.8 },
59
+ { file: './spec/fixtures/2_spec.rb:4', time: 2.4 },
60
+ { file: './spec/fixtures/new_spec.rb:1', time: nil },
61
+ ],
62
+
63
+ actual_time: 7.2,
64
+ added_files: [{ file: './spec/fixtures/new_spec.rb:1', time: nil }],
65
+ added_time: '?',
66
+
67
+ removed_files: [
68
+ { file: './spec/fixtures/x_spec.rb:1', time: 3 },
69
+ { file: './spec/fixtures/2_spec.rb:666', time: 6 },
70
+ ],
71
+
72
+ removed_time: 9.0
73
+ )
74
+ end
75
+ end
76
+ end
77
+ end