appmap 0.41.2 → 0.45.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.releaserc.yml +11 -0
  3. data/.travis.yml +23 -2
  4. data/CHANGELOG.md +40 -0
  5. data/README.md +36 -6
  6. data/README_CI.md +29 -0
  7. data/Rakefile +4 -2
  8. data/appmap.gemspec +5 -3
  9. data/lib/appmap.rb +4 -2
  10. data/lib/appmap/class_map.rb +7 -10
  11. data/lib/appmap/config.rb +98 -28
  12. data/lib/appmap/cucumber.rb +1 -1
  13. data/lib/appmap/event.rb +18 -0
  14. data/lib/appmap/handler/function.rb +19 -0
  15. data/lib/appmap/handler/net_http.rb +107 -0
  16. data/lib/appmap/hook.rb +42 -22
  17. data/lib/appmap/hook/method.rb +5 -7
  18. data/lib/appmap/minitest.rb +35 -30
  19. data/lib/appmap/rails/request_handler.rb +30 -17
  20. data/lib/appmap/record.rb +1 -1
  21. data/lib/appmap/rspec.rb +32 -96
  22. data/lib/appmap/trace.rb +2 -1
  23. data/lib/appmap/util.rb +39 -2
  24. data/lib/appmap/version.rb +2 -2
  25. data/release.sh +17 -0
  26. data/spec/abstract_controller_base_spec.rb +76 -29
  27. data/spec/class_map_spec.rb +3 -11
  28. data/spec/config_spec.rb +33 -1
  29. data/spec/fixtures/hook/custom_instance_method.rb +11 -0
  30. data/spec/fixtures/hook/method_named_call.rb +11 -0
  31. data/spec/fixtures/rails5_users_app/Gemfile +7 -3
  32. data/spec/fixtures/rails5_users_app/app/controllers/api/users_controller.rb +2 -0
  33. data/spec/fixtures/rails5_users_app/app/controllers/users_controller.rb +9 -1
  34. data/spec/fixtures/rails5_users_app/config/application.rb +2 -0
  35. data/spec/fixtures/rails5_users_app/create_app +8 -2
  36. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
  37. data/spec/fixtures/rails5_users_app/spec/controllers/users_controller_spec.rb +2 -2
  38. data/spec/fixtures/rails5_users_app/spec/models/user_spec.rb +2 -12
  39. data/spec/fixtures/rails5_users_app/spec/rails_helper.rb +3 -9
  40. data/spec/fixtures/rails6_users_app/Gemfile +5 -4
  41. data/spec/fixtures/rails6_users_app/app/controllers/api/users_controller.rb +1 -0
  42. data/spec/fixtures/rails6_users_app/app/controllers/users_controller.rb +9 -1
  43. data/spec/fixtures/rails6_users_app/config/application.rb +2 -0
  44. data/spec/fixtures/rails6_users_app/create_app +8 -2
  45. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_api_spec.rb +16 -3
  46. data/spec/fixtures/rails6_users_app/spec/controllers/users_controller_spec.rb +2 -2
  47. data/spec/fixtures/rails6_users_app/spec/models/user_spec.rb +2 -12
  48. data/spec/fixtures/rails6_users_app/spec/rails_helper.rb +3 -9
  49. data/spec/hook_spec.rb +135 -18
  50. data/spec/record_net_http_spec.rb +160 -0
  51. data/spec/record_sql_rails_pg_spec.rb +1 -1
  52. data/spec/spec_helper.rb +16 -0
  53. data/test/expectations/openssl_test_key_sign1.json +2 -4
  54. data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +3 -3
  55. data/test/fixtures/rspec_recorder/spec/plain_hello_spec.rb +1 -1
  56. data/test/gem_test.rb +1 -1
  57. data/test/minitest_test.rb +1 -2
  58. data/test/rspec_test.rb +1 -20
  59. metadata +17 -13
  60. data/exe/appmap +0 -154
  61. data/spec/rspec_feature_metadata_spec.rb +0 -31
  62. data/test/cli_test.rb +0 -116
@@ -2,7 +2,7 @@ require 'rspec'
2
2
  require 'appmap/rspec'
3
3
  require 'hello'
4
4
 
5
- describe Hello, appmap: true do
5
+ describe Hello do
6
6
  it 'says hello' do
7
7
  expect(Hello.new.say_hello).to eq('Hello!')
8
8
  end
data/test/gem_test.rb CHANGED
@@ -26,7 +26,7 @@ class MinitestTest < Minitest::Test
26
26
  assert_equal 2, events.size
27
27
  assert_equal 'call', events.first['event']
28
28
  assert_equal 'default_parser', events.first['method_id']
29
- assert_equal "#{Gem.loaded_specs['parser'].gem_dir}/lib/parser/base.rb", events.first['path']
29
+ assert_match /\lib\/parser\/base\.rb$/, events.first['path']
30
30
  assert_equal 'return', events.second['event']
31
31
  assert_equal 1, events.second['parent_id']
32
32
  end
@@ -30,9 +30,8 @@ class MinitestTest < Minitest::Test
30
30
  assert_equal 'minitest_recorder', metadata['app']
31
31
  assert_equal 'minitest', metadata['recorder']['name']
32
32
  assert_equal 'ruby', metadata['language']['name']
33
- assert_equal 'Hello', metadata['feature_group']
34
- assert_equal 'hello', metadata['feature']
35
33
  assert_equal 'Hello hello', metadata['name']
34
+ assert_equal 'test/hello_test.rb:9', metadata['source_location']
36
35
  end
37
36
  end
38
37
  end
data/test/rspec_test.rb CHANGED
@@ -18,21 +18,6 @@ class RSpecTest < Minitest::Test
18
18
  end
19
19
  end
20
20
 
21
- def test_inventory
22
- perform_test 'plain_hello_spec' do
23
- appmap_file = 'tmp/appmap/rspec/Inventory.appmap.json'
24
-
25
- assert File.file?(appmap_file), 'appmap output file does not exist'
26
- appmap = JSON.parse(File.read(appmap_file))
27
- assert_equal AppMap::APPMAP_FORMAT_VERSION, appmap['version']
28
- assert_includes appmap.keys, 'metadata'
29
- metadata = appmap['metadata']
30
- assert_equal 'Inventory', metadata['name']
31
- assert_includes metadata.keys, 'labels'
32
- assert_equal metadata['labels'], %w[inventory]
33
- end
34
- end
35
-
36
21
  def test_record_decorated_rspec
37
22
  perform_test 'decorated_hello_spec' do
38
23
  appmap_file = 'tmp/appmap/rspec/Hello_says_hello.appmap.json'
@@ -42,9 +27,8 @@ class RSpecTest < Minitest::Test
42
27
  assert_equal AppMap::APPMAP_FORMAT_VERSION, appmap['version']
43
28
  assert_includes appmap.keys, 'metadata'
44
29
  metadata = appmap['metadata']
45
- assert_equal 'Saying hello', metadata['feature_group']
46
- assert_equal 'Speak hello', metadata['feature']
47
30
  assert_equal 'Hello says hello', metadata['name']
31
+ assert_equal 'spec/decorated_hello_spec.rb', metadata['source_location']
48
32
  assert_includes metadata.keys, 'client'
49
33
  assert_equal({ name: 'appmap', url: AppMap::URL, version: AppMap::VERSION }.stringify_keys, metadata['client'])
50
34
  assert_includes metadata.keys, 'recorder'
@@ -63,8 +47,6 @@ class RSpecTest < Minitest::Test
63
47
  appmap = JSON.parse(File.read(appmap_file))
64
48
  assert_includes appmap.keys, 'metadata'
65
49
  metadata = appmap['metadata']
66
- assert_equal 'Hello', metadata['feature_group']
67
- assert_equal 'Hello', metadata['feature']
68
50
  assert_equal 'Hello says hello', metadata['name']
69
51
  end
70
52
  end
@@ -76,7 +58,6 @@ class RSpecTest < Minitest::Test
76
58
  appmap = JSON.parse(File.read(appmap_file))
77
59
  assert_includes appmap.keys, 'metadata'
78
60
  metadata = appmap['metadata']
79
- assert_equal %w[hello speak], metadata['labels'].sort
80
61
  end
81
62
  end
82
63
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.41.2
4
+ version: 0.45.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
- autorequire:
9
- bindir: exe
8
+ autorequire:
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-10 00:00:00.000000000 Z
11
+ date: 2021-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -304,11 +304,10 @@ dependencies:
304
304
  - - ">="
305
305
  - !ruby/object:Gem::Version
306
306
  version: '0'
307
- description:
307
+ description:
308
308
  email:
309
309
  - kgilpin@gmail.com
310
- executables:
311
- - appmap
310
+ executables: []
312
311
  extensions:
313
312
  - ext/appmap/extconf.rb
314
313
  extra_rdoc_files: []
@@ -316,6 +315,7 @@ files:
316
315
  - ".dockerignore"
317
316
  - ".gitignore"
318
317
  - ".rbenv-gemsets"
318
+ - ".releaserc.yml"
319
319
  - ".rubocop.yml"
320
320
  - ".travis.yml"
321
321
  - CHANGELOG.md
@@ -324,6 +324,7 @@ files:
324
324
  - Gemfile
325
325
  - LICENSE.txt
326
326
  - README.md
327
+ - README_CI.md
327
328
  - Rakefile
328
329
  - appmap.gemspec
329
330
  - appmap.yml
@@ -334,7 +335,6 @@ files:
334
335
  - examples/mock_webapp/lib/mock_webapp/controller.rb
335
336
  - examples/mock_webapp/lib/mock_webapp/request.rb
336
337
  - examples/mock_webapp/lib/mock_webapp/user.rb
337
- - exe/appmap
338
338
  - ext/appmap/appmap.c
339
339
  - ext/appmap/extconf.rb
340
340
  - lib/appmap.rb
@@ -346,6 +346,8 @@ files:
346
346
  - lib/appmap/config.rb
347
347
  - lib/appmap/cucumber.rb
348
348
  - lib/appmap/event.rb
349
+ - lib/appmap/handler/function.rb
350
+ - lib/appmap/handler/net_http.rb
349
351
  - lib/appmap/hook.rb
350
352
  - lib/appmap/hook/method.rb
351
353
  - lib/appmap/metadata.rb
@@ -379,16 +381,19 @@ files:
379
381
  - lore/public/stylesheets/style.css
380
382
  - package-lock.json
381
383
  - package.json
384
+ - release.sh
382
385
  - spec/abstract_controller_base_spec.rb
383
386
  - spec/class_map_spec.rb
384
387
  - spec/config_spec.rb
385
388
  - spec/fixtures/hook/attr_accessor.rb
386
389
  - spec/fixtures/hook/compare.rb
387
390
  - spec/fixtures/hook/constructor.rb
391
+ - spec/fixtures/hook/custom_instance_method.rb
388
392
  - spec/fixtures/hook/exception_method.rb
389
393
  - spec/fixtures/hook/exclude.rb
390
394
  - spec/fixtures/hook/instance_method.rb
391
395
  - spec/fixtures/hook/labels.rb
396
+ - spec/fixtures/hook/method_named_call.rb
392
397
  - spec/fixtures/hook/singleton_method.rb
393
398
  - spec/fixtures/rack_users_app/.dockerignore
394
399
  - spec/fixtures/rack_users_app/.gitignore
@@ -546,12 +551,11 @@ files:
546
551
  - spec/open_spec.rb
547
552
  - spec/rails_spec_helper.rb
548
553
  - spec/railtie_spec.rb
554
+ - spec/record_net_http_spec.rb
549
555
  - spec/record_sql_rails_pg_spec.rb
550
556
  - spec/remote_recording_spec.rb
551
- - spec/rspec_feature_metadata_spec.rb
552
557
  - spec/spec_helper.rb
553
558
  - spec/util_spec.rb
554
- - test/cli_test.rb
555
559
  - test/cucumber_test.rb
556
560
  - test/expectations/openssl_test_key_sign1.json
557
561
  - test/expectations/openssl_test_key_sign2.json
@@ -601,7 +605,7 @@ homepage: https://github.com/applandinc/appmap-ruby
601
605
  licenses:
602
606
  - MIT
603
607
  metadata: {}
604
- post_install_message:
608
+ post_install_message:
605
609
  rdoc_options: []
606
610
  require_paths:
607
611
  - lib
@@ -616,8 +620,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
616
620
  - !ruby/object:Gem::Version
617
621
  version: '0'
618
622
  requirements: []
619
- rubygems_version: 3.0.3
620
- signing_key:
623
+ rubygems_version: 3.0.8
624
+ signing_key:
621
625
  specification_version: 4
622
626
  summary: Record the operation of a Ruby program, using the AppLand 'AppMap' format.
623
627
  test_files: []
data/exe/appmap DELETED
@@ -1,154 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'gli'
5
-
6
- ENV['APPMAP_INITIALIZE'] = 'false'
7
-
8
- require 'appmap'
9
- require 'appmap/version'
10
-
11
- # AppMap CLI.
12
- module AppMap
13
- class App
14
- extend GLI::App
15
-
16
- program_desc 'AppMap client'
17
-
18
- version AppMap::VERSION
19
-
20
- subcommand_option_handling :normal
21
- arguments :strict
22
- preserve_argv true
23
-
24
- class << self
25
- protected
26
-
27
- def default_appmap_file
28
- ENV['APPMAP_FILE'] || 'appmap.json'
29
- end
30
-
31
- def output_file_flag(c, default_value: nil)
32
- c.desc 'Name of the output file'
33
- c.long_desc <<~DESC
34
- Use a single dash '-' for stdout.
35
- DESC
36
- c.default_value default_value if default_value
37
- c.arg_name 'filename'
38
- c.flag %i[o output]
39
- end
40
- end
41
-
42
- desc 'AppMap configuration file name'
43
- default_value ENV['APPMAP_CONFIG'] || 'appmap.yml'
44
- arg_name 'filename'
45
- flag %i[c config]
46
-
47
- desc 'Record the execution of a program and generate an AppMap.'
48
- arg_name 'program'
49
- command :record do |c|
50
- output_file_flag(c, default_value: default_appmap_file)
51
-
52
- c.action do |_, _, args|
53
- # My subcommand name
54
- ARGV.shift
55
-
56
- # Consume the :output option, if provided
57
- if %w[-o --output].find { |arg_name| ARGV[0] == arg_name.to_s }
58
- ARGV.shift
59
- ARGV.shift
60
- end
61
-
62
- # Name of the program to execute. GLI will ensure that it's present.
63
- program = args.shift or help_now!("'program' argument is required")
64
-
65
- # Also pop the program name from ARGV, because the command will use raw ARGV
66
- # to load the extra arguments into this Ruby process.
67
- ARGV.shift
68
-
69
- require 'appmap/command/record'
70
- AppMap::Command::Record.new(@config, program).perform do |version, metadata, class_map, events|
71
- @output_file.write JSON.generate(version: version,
72
- metadata: metadata,
73
- classMap: class_map,
74
- events: events)
75
- end
76
- end
77
- end
78
-
79
- desc 'Calculate and print statistics of scenario files.'
80
- arg_name 'filename'
81
- command :stats do |c|
82
- output_file_flag(c, default_value: '-')
83
-
84
- c.desc 'Display format for the result (text | json)'
85
- c.default_value 'text'
86
- c.flag %i[f format]
87
-
88
- c.desc 'Maximum number of lines to display for each stat'
89
- c.flag %i[l limit]
90
-
91
- c.action do |_, options, args|
92
- require 'appmap/command/stats'
93
-
94
- limit = options[:limit].to_i if options[:limit]
95
-
96
- # Name of the file to analyze. GLI will ensure that it's present.
97
- filenames = args
98
- help_now!("'filename' argument is required") if filenames.empty?
99
-
100
- require 'appmap/algorithm/stats'
101
- result = filenames.inject(::AppMap::Algorithm::Stats::Result.new([], [])) do |stats_result, filename|
102
- appmap = begin
103
- JSON.parse(File.read(filename))
104
- rescue JSON::ParserError
105
- STDERR.puts "#{filename} is not valid JSON : #{$!}"
106
- nil
107
- end
108
- stats_result.tap do
109
- if appmap
110
- limit = options[:limit].to_i if options[:limit]
111
- stats_for_file = AppMap::Command::Stats.new(appmap).perform(limit: limit)
112
- stats_result.merge!(stats_for_file)
113
- end
114
- end
115
- end
116
-
117
- result.sort!
118
- result.limit!(limit) if limit
119
-
120
- display = case options[:format]
121
- when 'json'
122
- JSON.pretty_generate(result.as_json)
123
- else
124
- result.as_text
125
- end
126
- @output_file.write display
127
- end
128
- end
129
-
130
- pre do |global, _, options, _|
131
- @config = interpret_config_option(global[:config])
132
- @output_file = interpret_output_file_option(options[:output])
133
-
134
- true
135
- end
136
-
137
- class << self
138
- protected
139
-
140
- def interpret_config_option(fname)
141
- AppMap.initialize fname
142
- end
143
-
144
- def interpret_output_file_option(file_name)
145
- Hash.new { |_, fname| -> { File.new(fname, 'w') } }.tap do |open_output_file|
146
- open_output_file[nil] = -> { nil }
147
- open_output_file['-'] = -> { $stdout }
148
- end[file_name].call
149
- end
150
- end
151
- end
152
- end
153
-
154
- exit AppMap::App.run(ARGV)
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rails_spec_helper'
4
-
5
- describe 'RSpec feature and feature group metadata' do
6
- include_examples 'Rails app pg database', 'spec/fixtures/rails5_users_app' do
7
- around(:each) do |example|
8
- FileUtils.rm_rf tmpdir
9
- FileUtils.mkdir_p tmpdir
10
- cmd = "docker-compose run --rm -e RAILS_ENV=test -e APPMAP=true -v #{File.absolute_path(tmpdir).shellescape}:/app/tmp app ./bin/rspec spec/models/user_spec.rb"
11
- run_cmd cmd, chdir: fixture_dir
12
-
13
- example.run
14
- end
15
-
16
- let(:tmpdir) { 'tmp/spec/RSpec feature and feature group metadata' }
17
- let(:appmap_json) { File.join(tmpdir, %(appmap/rspec/User_creation_creates_charles.appmap.json)) }
18
-
19
- describe do
20
- it 'are recorded in the appmap' do
21
- expect(File).to exist(appmap_json)
22
- appmap = JSON.parse(File.read(appmap_json)).to_yaml
23
-
24
- expect(appmap).to include(<<-METADATA.strip)
25
- feature: Create a user
26
- feature_group: User
27
- METADATA
28
- end
29
- end
30
- end
31
- end
data/test/cli_test.rb DELETED
@@ -1,116 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'test_helper'
5
- require 'English'
6
-
7
- class CLITest < Minitest::Test
8
- OUTPUT_FILENAME = File.expand_path('../tmp/appmap.json', __dir__)
9
- STATS_OUTPUT_FILENAME = File.expand_path('../tmp/stats.txt', __dir__)
10
-
11
- def setup
12
- FileUtils.rm_f OUTPUT_FILENAME
13
- FileUtils.rm_f STATS_OUTPUT_FILENAME
14
- end
15
-
16
- def test_record
17
- output = Dir.chdir 'test/fixtures/cli_record_test' do
18
- `#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`.strip
19
- end
20
-
21
- assert_equal 0, $CHILD_STATUS.exitstatus
22
- assert File.file?(OUTPUT_FILENAME), "#{OUTPUT_FILENAME} does not exist"
23
- assert_equal 'Hello', output
24
- output = JSON.parse(File.read(OUTPUT_FILENAME))
25
- assert output['classMap'], 'Output should contain classMap'
26
- assert output['events'], 'Output should contain events'
27
- end
28
-
29
- def test_stats_to_file
30
- Dir.chdir 'test/fixtures/cli_record_test' do
31
- `#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`.strip
32
- end
33
- assert_equal 0, $CHILD_STATUS.exitstatus
34
-
35
- output = Dir.chdir 'test/fixtures/cli_record_test' do
36
- `#{File.expand_path '../exe/appmap', __dir__} stats -o #{STATS_OUTPUT_FILENAME} #{OUTPUT_FILENAME}`.strip
37
- end
38
- assert_equal 0, $CHILD_STATUS.exitstatus
39
- assert_equal '', output
40
- assert File.file?(OUTPUT_FILENAME), "#{OUTPUT_FILENAME} does not exist"
41
- end
42
-
43
-
44
- def test_stats_text
45
- Dir.chdir 'test/fixtures/cli_record_test' do
46
- `#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`.strip
47
- end
48
- assert_equal 0, $CHILD_STATUS.exitstatus
49
-
50
- output = Dir.chdir 'test/fixtures/cli_record_test' do
51
- `#{File.expand_path '../exe/appmap', __dir__} stats -o - #{OUTPUT_FILENAME}`.strip
52
- end
53
-
54
- assert_equal 0, $CHILD_STATUS.exitstatus
55
- assert_equal <<~OUTPUT.strip, output.strip
56
- Class frequency:
57
- ----------------
58
- 1 Main
59
-
60
- Method frequency:
61
- ----------------
62
- 1 Main.say_hello
63
- OUTPUT
64
- end
65
-
66
- def test_stats_json
67
- Dir.chdir 'test/fixtures/cli_record_test' do
68
- `#{File.expand_path '../exe/appmap', __dir__} record -o #{OUTPUT_FILENAME} ./lib/cli_record_test/main.rb`.strip
69
- end
70
- assert_equal 0, $CHILD_STATUS.exitstatus
71
-
72
- output = Dir.chdir 'test/fixtures/cli_record_test' do
73
- `#{File.expand_path '../exe/appmap', __dir__} stats -f json -o - #{OUTPUT_FILENAME}`.strip
74
- end
75
-
76
- assert_equal 0, $CHILD_STATUS.exitstatus
77
- assert_equal <<~OUTPUT.strip, output.strip
78
- {
79
- "class_frequency": [
80
- {
81
- "name": "Main",
82
- "count": 1
83
- }
84
- ],
85
- "method_frequency": [
86
- {
87
- "name": "Main.say_hello",
88
- "count": 1
89
- }
90
- ]
91
- }
92
- OUTPUT
93
- end
94
-
95
- def test_record_to_default_location
96
- Dir.chdir 'test/fixtures/cli_record_test' do
97
- system({ 'APPMAP_FILE' => OUTPUT_FILENAME }, "#{File.expand_path '../exe/appmap', __dir__} record ./lib/cli_record_test/main.rb")
98
- end
99
-
100
- assert_equal 0, $CHILD_STATUS.exitstatus
101
- assert File.file?(OUTPUT_FILENAME), 'appmap.json does not exist'
102
- end
103
-
104
- def test_record_to_stdout
105
- output = Dir.chdir 'test/fixtures/cli_record_test' do
106
- `#{File.expand_path '../exe/appmap', __dir__} record -o - ./lib/cli_record_test/main.rb`
107
- end
108
-
109
- assert_equal 0, $CHILD_STATUS.exitstatus
110
- # Event path
111
- assert_includes output, %("path":"lib/cli_record_test/main.rb")
112
- # Function location
113
- assert_includes output, %("location":"lib/cli_record_test/main.rb:3")
114
- assert !File.file?(OUTPUT_FILENAME), "#{OUTPUT_FILENAME} should not exist"
115
- end
116
- end